# Phase 1b — Scope: priced options + logo upload + size-run matrix

Status: **scoped, not started.** Target plugin version **0.4.0**. One combined release
(Shane: "all three together"). Builds directly on the Phase 3 cart/order pipeline (v0.3.3).

## Decisions locked (2026-07-24)

| Decision | Choice |
|---|---|
| Sequencing | **All three together** in one Phase 1b release (single store upgrade) |
| Logo placement | **Live drag + scale on the canvas** (customer positions within a print area) |
| Size run | **Quantities per size, no names** (size → qty grid; total = line quantity) |

## Key finding — no schema changes

Every table this phase needs already exists (created by `class-sc-install.php` in Phase 1,
currently unused): `sc_option_group`, `sc_option`, `sc_visibility_rule`, plus `zone.print_area`
(nullable JSON rect for logo placement) and `design.selections_json` (longtext — holds option
picks, size runs and logo transforms with no new columns). **`sc_db_version` stays at `1`.**

Confirmed columns:
- `option_group(id, shoe_id, label, type, required, sort)` — `type` ∈ select|radio|swatch|text|upload|size
- `option(id, group_id, label, price_delta, target_zone_id, colour_hex, asset, sort)`
- `visibility_rule(id, option_group_id, depends_group_id, equals_option_id)`
- `zone(… print_area text NULL …)` — JSON `{x,y,w,h}` in canvas coords

---

## Feature A — Priced option groups

Paid add-ons beyond the base colour path: e.g. *Lace colour +$10*, *Premium sole +$25*,
*Add embroidered initials (text) +$8*. Types shipped in 1b: **select, radio, swatch, text**
(`upload` = logo feature B; `size` = feature C).

**Builder** (`views/edit.php` + `class-sc-admin.php`): an **Options** section under a shoe — a
repeater of groups (label, type, required, sort), each with a nested options repeater (label,
`price_delta`, `target_zone_id`, `colour_hex`, optional `asset` overlay, sort). One optional
visibility rule per group ("show only when [group] = [option]") — single-rule UI now,
multi-rule later.

**Config/REST** (`SC_Repo::config()`): add
```
optionGroups: [ { id, label, type, required, rule?:{group,option},
                  options: [ { id, label, priceDelta, zone, hex, asset } ] } ]
```

**Frontend** (`customizer.js`): render each group as a control under the zone swatches. A
**swatch** option carrying `zone`+`hex` flows through the existing multiply-tint pipeline (paid
colourways and the base palette unify). **text** = input (e.g. a name to embroider). Running
price = `basePrice + Σ selected priceDelta`. Respect `required` + visibility rules in validation.

**Server** (`SC_Cart`): validate every selected option id belongs to the shoe, required groups
are satisfied, then **recompute price from the DB deltas** (never the client's numbers). Persist
selected options in cart data + order-item meta.

## Feature B — Logo upload + live placement

**Builder — print areas:** a small canvas tool on the shoe editor to **draw a rectangle** on the
base image per logo-capable zone; saved to `zone.print_area` as `{x,y,w,h}`. Defines where a
logo may sit and its bounds.

**Frontend:** a **Logo** control (upload button). On select: validate client-side (raster only —
PNG/JPG; size cap), load into an `Image`, draw as the **top canvas layer** clamped to the chosen
zone's print area. **Drag to move, corner handle to scale** (rotation deferred unless Shane wants
it). Transform `{zone, x, y, scale}` + the asset reference are stored in the design; the composed
mockup already bakes in the placed logo.

**Upload endpoint** (new `class-sc-upload.php`, `sc_upload_logo`, nonce-gated): hard validation —
`wp_check_filetype` + `getimagesize` (reject anything that isn't a real raster image), size cap,
**re-encode via WP image editor to strip metadata**, randomized filename, store under
`uploads/shoe-customizer/logos/`. Returns the URL. **SVG disallowed by default** (XSS surface); can
add a sanitizer later if required. This upload path is the main security surface of the phase.

**Cart/order:** store the **full-res original** logo URL + placement transform in the design JSON
and `_sc_logo` order meta (production needs the original, not the mockup). Feeds the Phase 4 spec
sheet.

## Feature C — Size-run matrix (quantities per size)

**Model:** a size scale is an `option_group` of `type='size'` whose `option` rows are the sizes
("US 6"…"US 13", `price_delta` normally 0). Per-shoe, seeded with a sensible default scale.

**Builder:** manage the size list per shoe (labels + order).

**Frontend:** a grid of sizes, each with a **quantity input** (default 0); **total pairs = Σ qty**
shown live. Optional minimum-order-quantity gate (see open decisions).

**Pricing/quantity:** WooCommerce **line quantity = total pairs**; unit price = base + Σ option
deltas. The size→qty breakdown rides as structured order meta `_sc_sizes = [{size, qty}]` for
fulfilment. Cart thumbnail stays the mockup.

**Server:** validate size codes ∈ the shoe's size scale, quantities are non-negative integers,
total ≥ 1 (or MOQ). Set the cart line quantity to the total.

---

## Cross-cutting work

- **`SC_Repo`**: `option_groups($shoe)`, `options($group)`, `visibility_rules($shoe)`,
  `save_option_group()`, `save_option()`, size-scale helpers; extend `config()`.
- **`customizer.js`**: option controls + delta pricing; logo layer + pointer drag/scale within
  print area; size qty-grid + total; `design()` payload gains `options[]`, `sizes[]`, `logo{}`;
  add-to-cart FormData carries all three.
- **`SC_Cart`**: `sanitize_options()`, `sanitize_sizes()`, `sanitize_logo()`; `price()` = base +
  Σ DB deltas; cart data + order meta expanded; line qty from size total.
- **`SC_Upload`** (new): the secure logo endpoint above.
- **Docs + release notes** (policy [[feature_release_docs_policy]]): a customer-facing docs page +
  release notes entry are required for the release. Retire the stray "Shoes Required / Options"
  add-on fields on the live product as part of the cutover.

## Build order (one release, internal sequencing)

1. `SC_Repo` + `config()` extension (data plumbing first — unblocks frontend + builder).
2. Builder UI: options editor, size-scale editor, print-area drawer.
3. `SC_Upload` secure logo endpoint.
4. Frontend engine: options + pricing, size grid, logo upload + live placement.
5. `SC_Cart` extension: validate/price/persist options + sizes + logo; qty from size total.
6. Docs page + release notes + version bump (0.4.0) + build + deploy + browser verify.

## Testing

- Reuse the Playwright harness (`tests/sc-addtocart-test.mjs`) extended to: pick a paid
  option and assert the price delta; enter a size run and assert line qty + `_sc_sizes`; upload a
  logo, drag/scale it, add to cart, assert `_sc_logo` + mockup. Server-side: assert tampered
  option ids / hostile uploads / bad size codes are rejected. `php -l` + `node --check` gates as
  before. Any prod write (test order) needs Shane's per-action greenlight.

## Open decisions — RESOLVED (2026-07-24)

1. **Logo formats** — **PNG + JPG only.** No SVG (no sanitizer needed).
2. **Logo transform** — **move + scale only.** No rotation in 1b.
3. **Max logo size** — **5 MB** hard cap (client pre-check + server enforce).
4. **Size scale** — **per-shoe, seeded with a default scale** (US 6–13). *(taken as recommended.)*
5. **MOQ** — **minimum 2 total pairs** per order line. Block add-to-cart below 2, server-enforced.
6. **Legacy fields** — **yes, strip** the old "Shoes Required / Options" add-on from product 8928
   at cutover.
