8ee3da9dcf
Reconciles two independent lines of work that both touched Review.svelte, MultiSelect.svelte, and the thickness band: - main's PR #97 (client-side thicknessBand.ts + MultiSelect names/commit + syncSelects DOM race fix + box-sizing CSS) - work/merge-89-90 (server-side thickness band in thickness_band.py, Create-string #89, metadata_locked dropped, photo strip, wood primary) Canonical resolution: the band is SERVER-side (hidden from UI, sent to Woo on publish), so the client-side thicknessBand.ts + read-only band display are removed from Review.svelte; PR #97's syncSelects() and box-sizing CSS are preserved (orthogonal fixes). MultiSelect takes selectedSummary from woodPrimary.ts (names in closed state) + the ms-count testid + onDestroy timer cleanup; duplicate close()/summary definitions dropped. Gates: backend 117/117, unit 88/88, tsc clean, e2e 6/6 (m3-create + photo-strip).
110 lines
6.3 KiB
Markdown
110 lines
6.3 KiB
Markdown
# UX contract — mill-owner flow
|
||
|
||
Status: SPEC · 2026-09-18. Presentation spec for the mill-owner flow on phone and PC browsers.
|
||
|
||
## Requirements impact
|
||
|
||
| Requirement | Impact on this design |
|
||
|---|---|
|
||
| CAPTURE-10 | supported browsers, hard stop |
|
||
| CAPTURE-15 | online only |
|
||
| CAPTURE-20, CAPTURE-25, CAPTURE-30 | photo intake and calibration choice |
|
||
| CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, CAPTURE-55 | sheet/sensitivity controls, overlay, retake |
|
||
| CAPTURE-60, CAPTURE-65, CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 | axis, entry, derived numbers |
|
||
| CAPTURE-105, CAPTURE-130, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | crop guard, lineup order, upload, preview/controls, failure |
|
||
| METADATA-10, METADATA-15, METADATA-30, QUALITY-95 | required taxonomy fields and cardinality; the thickness band is derived from the measurement, not picked |
|
||
| METADATA-45, METADATA-55, METADATA-110 | pre-fill, Generate text availability |
|
||
| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38, PUBLISH-50 | publish sequence, outcomes and messaging |
|
||
| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-40, REVIEW-45, REVIEW-50 | ready set, gating, fields, limits |
|
||
| SETTINGS-65 | create-status default |
|
||
|
||
## 1. Shape
|
||
|
||
One column of screens: capture → review → publish. No dashboard and no batch view. The reader is a
|
||
mill owner photographing one slab at a time, online. Capture often starts on a phone; the same
|
||
flow runs on a PC browser.
|
||
|
||
## 2. Capture
|
||
|
||
Cites CAPTURE-20, CAPTURE-25, CAPTURE-40, CAPTURE-130.
|
||
|
||
1. **Photos.** Take one or more photos; the first is top-down with the lens parallel to the broad
|
||
face. The owner chooses which photo is the calibration photo.
|
||
2. **Mask overlay and controls.** The app draws the mask on the source photo. The controls are
|
||
exactly **Sheet** (auto, green, black) and **Sensitivity** (0–100). A Sheet change runs
|
||
immediately; a Sensitivity change debounces 2000 ms; a worker recomputes and the last good
|
||
overlay stays visible. There is no apply step and no server round trip; values persist until
|
||
reset.
|
||
3. **Confirm the mask.** The owner confirms the edge; approval is blocked when the mask has no
|
||
slab pixels. A broken or non-contiguous mask resolves as a retake.
|
||
4. **Length axis.** The app draws the bounding rectangle and the length axis; the owner rotates it
|
||
to the prevailing length and confirms. On a nearly square slab the owner chooses the axis.
|
||
5. **Numbers.** The owner enters length and thickness in 1/8-inch steps and the SKU. The browser
|
||
computes widths, sqft, and bdft. These are display only; the server never recomputes them.
|
||
6. **Continue.** Uploads originals, processed PNGs, and the numbers as a calibrated draft; status
|
||
becomes `calibrated`.
|
||
|
||
The preview is display-only and never drives the math. Failures such as a broken mask or an
|
||
undersized source never guess; the resolution is retake.
|
||
|
||
## 3. Review
|
||
|
||
Cites REVIEW-5, REVIEW-15, REVIEW-20, METADATA-45.
|
||
|
||
- **Vision pre-fill.** At or above the threshold (default 0.7) a field is pre-filled and shown
|
||
with an "AI" badge; below it the field starts empty. The badge is presentation only and clears
|
||
on edit.
|
||
- **Required set.** Exactly one species and at least one wood-category leaf; one Edge Type, at
|
||
least one Figure, exactly one Grade, one Moisture; fig-* and feat-* tags optional; price;
|
||
title, short_description, and description. The **Thickness band is not an owner control** — it
|
||
is derived deterministically from `thickness_in` (the value rounds up into the synced Woo band,
|
||
`TV-9` / `QUALITY-95`), displayed read-only, and satisfies the readiness requirement
|
||
(`REVIEW-5`) via that derived value.
|
||
- **Primary category.** The owner declares one primary wood category and any secondary ones; the
|
||
default is the first selected.
|
||
- **Thickness band is derived and not shown.** The measured thickness bands up into the synced Woo
|
||
band server-side (`METADATA-15`, `QUALITY-95`); Review carries no thickness-band control and no
|
||
read-only echo of it. The derived band is stored on the slab and sent to the store at publish.
|
||
- **Inline nudges.** Every empty required field shows an inline nudge as the owner works; there is
|
||
no single submit-time gap list.
|
||
- **Calculated fields.** Widths, sqft, and bdft are read-only and not overridable.
|
||
- **Generate text.** The control appears only when content generation is enabled and the endpoint
|
||
is reachable; it is disabled when the text service is down; it never runs automatically.
|
||
|
||
## 4. Publish
|
||
|
||
Cites PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-25.
|
||
|
||
- A normal SKU uses `woo_create_status` (default draft); a `SLAB-UAT-*` SKU always creates a draft.
|
||
"Published" means the store create succeeded, not that it is live.
|
||
- **Success.** The local draft and photos are gone; the store product reference is shown.
|
||
- **Duplicate SKU.** "This SKU already exists in the store (any status)." with Edit SKU and Open
|
||
Existing.
|
||
- **Stale value.** "This value is no longer valid. Pick from the current list," with current
|
||
options.
|
||
- **Failure.** A plain message on the affected field or action, classified recoverable or
|
||
unrecoverable, with Retry while the draft and photos are retained; no raw code.
|
||
|
||
## 5. Browser gate
|
||
|
||
Supported families are iOS Safari and Android Chrome, plus desktop Chrome, Edge, and Firefox. An
|
||
unsupported client gets a hard stop with an update or switch link; there are no polyfills.
|
||
|
||
## 6. Canonical error and empty copy
|
||
|
||
| Situation | Copy |
|
||
|---|---|
|
||
| Mask failure | "Cannot isolate the slab edge. Adjust the controls or retake." |
|
||
| Axis check | "Rotate the axis to match the prevailing length of the slab." |
|
||
| Source too small | "Photo will be too small after cropping. Please retake with a closer shot." |
|
||
| Duplicate SKU | "This SKU already exists in the store (any status)." |
|
||
| Stale value | "This value is no longer valid. Pick from the current list." |
|
||
| Store unreachable | "Cannot reach the store. Check your connection and settings." |
|
||
| Missing field nudge | per-field, in place (e.g. "Select species") |
|
||
| Browser gate | "This app requires a recent browser. Please update or switch, then try again." |
|
||
|
||
## 7. Presentation-only details
|
||
|
||
First-run guidance card, "AI" badge styling, and the separate title/description step are
|
||
presentation detail, not requirements.
|