docs: signed UX + PC browsers + UAT practice walk

Replay local tip onto origin/docs/refactor-v2. Keeps remote UX-DESIGN
wireframes. Adds signed UX, browser matrix (phone+PC), and UAT-PRACTICE.
This commit is contained in:
2026-09-06 22:49:32 +00:00
parent 3f3eff24a5
commit 8d757283d7
12 changed files with 477 additions and 109 deletions
+181 -87
View File
@@ -1,146 +1,240 @@
# Phone flow
# Mill-owner UX — phone and PC browsers
What the mill owner does on the phone, from first photo to a store listing. Math and constants live in `docs/TECH-SPEC-PIPELINE.md`. Field names and the ready set live in `docs/DATA-MODEL.md`. Store payload and create steps live in `docs/CONTENT-WOO.md`. Wireframes and interaction patterns: `docs/UX-DESIGN.md`.
Status: SIGNED · 2026-09-06 PT · Ty approved with PC browsers in the compatibility matrix · SlabUploader
Values you type are kept as-is. Vision fills store fields per the confidence gating rules in `AGENTS.md` §6.
This is the presentation spec for the mill-owner flow. It says what a mill owner sees and does, in the
order they do it. The rules behind each screen live elsewhere: `AGENTS.md` locks the product
decisions, `docs/TECH-SPEC-PIPELINE.md` owns the math and image prep, `docs/DATA-MODEL.md` owns
the store row and its statuses, `docs/CONTENT-WOO.md` owns listing copy and the Woo payload,
and `docs/OPENAPI.md` owns the wire. This file names what shows up on screen; it does not restate
those rules.
---
The reader is a mill owner photographing one slab at a time, online, from a supported browser on a
phone or a PC. The app is a single column of screens, top to bottom: capture, then review, then
publish. There is no dashboard and no batch view in this version. Capture often starts on a phone
camera; the same flow must also run on a PC browser.
## Main path
## Jobs
### Settings once
Five jobs cover the whole flow. Each section below names what the owner does, what they see, and
the lock that governs it.
The store URL shows read-only from the deploy (`WOO_BASE_URL`). You always know which store this phone talks to.
1. **Phone photo to a store listing.** Photograph the slab, confirm the cut and the numbers on
the browser, review the listing, create the WooCommerce product. The happy path stays in the
browser until the owner confirms the mask and the numbers.
2. **Practice drafts customers never see.** A practice run creates a WooCommerce draft with an SKU
that starts `SLAB-UAT-`. Customers cannot open it. It is always a draft, even when Settings say
publish.
3. **Retake when the overlay cut looks wrong.** The mask overlay on the source photo is the only
quality check. If the edge is off after tuning the four knobs, the owner retakes the photo.
4. **Fill or override store fields.** On review, the owner fills every required field by hand, or
keeps what vision filled when it was confident enough. Typed values always win.
5. **Publish.** The owner taps publish. The server creates the product in the store and reports the
result back on the same screen.
Enter the WordPress username and application password for the low-privilege store user, under Users → Profile in WordPress. The password is write-only: you type it once. To rotate, revoke the old one in WordPress and enter a new one here.
## Capture: photo to numbers
On the same screen: species $/bdft rates, vision on/off with endpoint and key, draft vs publish for new listings (default draft), reset capture knobs to factory, and an optional refresh of the store lists. **Test connection** always pulls fresh species, attributes, and tags from the store when it succeeds.
The capture flow is one continuous screen with a few confirmations, not separate pages. The owner
stays on it from first photo through confirmed mask and axis.
### First run on the floor
### Photos
One short card, then dismiss. Lay a green or black sheet under the slab. Shoot from above with the lens parallel to the face. Put a tape measure in the shot so you can **type** the length later. Your typed number is what the app measures against.
The owner takes 1 to 5 inventory photos of the slab. The first should be top-down, lens parallel
to the broad face; a tilted shot makes square feet and board feet wrong, and retake is the fix.
There are no calibration photos in this design. Only inventory photos exist.
### Capture
### Mask overlay and four knobs
Take 1 to 5 inventory photos.
The app removes the background sheet and draws the mask as an overlay on the source photo, showing
which pixels count as slab and which do not. The owner looks at that edge against the real wood.
This is the only quality check in the pipeline. There is no confidence score, no auto-gate, and no
photo-quality checker. A clipped or over-inclusive mask shifts every downstream number with nothing
later to catch it, so the eye on the overlay is what stands behind the measurement.
The source photo stays on screen, and the mask draws over that original: kept wood versus dropped sheet. Four knobs sit next to the overlay:
Four knobs sit on this same screen, next to the overlay:
1. Sheet — auto, green, or black
2. Sensitivity
3. Edge offset
4. Feather
- **Sheet** — auto, green, or black. Auto samples the borders and picks; the owner overrides when
the sheet is an odd color.
- **Sensitivity** — one slider from tighter to looser detection.
- **Edge offset** — pulls the cut in or out a few pixels so it sits on the wood, not in the sheet
or into the grain.
- **Feather** — hard edge versus a couple of pixels of blend.
Move a knob and background removal re-runs on this photo; the overlay refreshes immediately. Nothing round-trips to the server for mask tuning. The knobs stick in local storage until you reset them to factory.
Changing any knob re-runs background removal immediately and refreshes the overlay. There is no
apply step and no server round trip for mask tuning in this version. The four values persist on the
browser until reset to default; they are not Settings fields.
Confirm the mask edge by eye. If the cut sits on the wood edge, confirm it. If it clips grain or still holds sheet after you tune, retake the photo. The one-photo server U2Net path stays in the architecture for post-POC; it is deferred from POC.
If the edge still looks wrong after tuning, the owner retakes the photo. That is the resolution in
this version. A server-side matting path exists in the architecture for awkward backgrounds later,
but it is deferred and has no control here.
### Length axis
The bounding rectangle and length axis draw over the confirmed mask, with width sample lines every 6 inches along that axis. Rotate until the axis matches the slab's prevailing length, then confirm. On a nearly square piece you pick which side is length.
After the mask is confirmed, the app draws the bounding rectangle and the length axis over the slab,
with width sample lines perpendicular to that axis. The owner confirms the axis follows the
slab's prevailing length. If it is skewed, they rotate it; samples and square feet update live. On a
nearly square slab the owner says which way is length.
### Type length, thickness, SKU
### Numbers
Manual values win. The phone computes square feet, board feet (`bdft = sqft × thickness_in`, no `/12`), and width min / max / avg from the mask and your typed length.
The owner enters length (in 1/8" steps), thickness (a 1/8" pick), and the SKU by hand. The browser
then computes square feet, board feet, and width samples from the confirmed mask and axis:
It builds 3:4 transparent PNG inventory images at 80% fill on the extremes. After that crop, if the source does not have enough pixels for a shorter side of 1600 px, that photo is not publishable until you retake it. The floor and the math are in `docs/TECH-SPEC-PIPELINE.md` (constants and TV-7).
- Board feet = square feet × thickness in inches. One square foot at one inch thick is one board
foot. There is no divide-by-12.
- Widths are sampled every 6" along the length axis; the listing carries min, max, and average.
- Square feet and board feet show to two decimals.
### Draft upload (calibrated)
These numbers are computed in the browser and stored as sent. The server does not recompute them.
When mask, axis, length, thickness, and SKU are set and the numbers and PNGs are ready, the phone uploads originals + processed PNGs + length / thickness / SKU / sqft / bdft / widths. App status becomes **calibrated**.
### Photo prep
### Review (store fields)
Each inventory photo is cropped to a 3:4 portrait frame with the slab centered at 80% fill, saved as
a transparent PNG. If the source has enough pixels, the shorter side lands at 1600 or more after
crop. If it cannot reach that, the app warns and asks for a retake; it does not invent pixels. The
owner sees the processed result alongside the original while the draft is retained.
Everything the listing needs lands here. A required control that is still empty shows an inline empty state as you work.
### Continue
**Always on the form from capture and defaults**
When the mask, axis, length, thickness, and SKU are set, the owner continues. That uploads the
originals plus the processed PNGs plus the numbers to the server as a calibrated draft, and moves to
review. The slab status becomes `calibrated`.
- Inventory photos as processed transparent PNGs (at least one)
- SKU, length, thickness
- Computed sqft, bdft, width min / max / avg
- Thickness store band from your typed thickness, rounded **up** into the synced Woo thickness range
- Moisture starts at **kiln-dried**. Vision does not set moisture. Change it for green or air-dried slabs.
- Price: species $/bdft × bdft prefills when a rate exists for that species. If there is no rate, the field starts empty and you type the price. Override anytime; your override sticks for this slab.
## Review: fill or override store fields
**Species and store taxonomy**
Review is where the listing row gets filled. Every field below maps to a column in the `slabs` table
in `docs/DATA-MODEL.md`. The owner can type any of them; vision may have pre-filled some, but typed
values always win and every value stays editable.
Pickers and any vision lists are exactly the last pull from the store. A new Woo species becomes selectable only after a sync lands it in the cache.
### What vision fills, and when
Call 1 prefill behavior: see CONTENT-WOO §2.3. Every pre-fill is editable. Typed values win.
When inference is on, Call 1 runs after the calibrated draft uploads. It sends all inventory photos
downscaled to 1024 plus the store's synced species, attribute, and tag lists, and returns a guess
with a confidence per field. The threshold is 0.7 by default:
**Required before the slab is ready to list**
- A field at or above 0.7 pre-fills on review. It stays editable; the owner can change or clear it.
- A field below 0.7 starts empty. The app does not show the model's low-confidence guess as a
default, and an empty start is not the same as a finished listing.
- Exactly one species
- At least one wood category
- One edge type
- At least one figure term
- At least one `fig-*` tag
- At least one grade
- Thickness band (the round-up above)
- Moisture (kiln-dried unless you changed it)
- Price
- SKU, length, thickness, client sqft / bdft / widths, at least one inventory PNG
With inference off, every field starts empty and the owner fills all of them by hand. Inference
never blocks publish and never auto-publishes. Call 2 (Generate text) does not run on its own; it
fires only when the owner taps it.
Feature (`feat-*`) tags are optional; zero is fine.
### The fields
### Title, short title, description
The required set for a list-ready slab is the full store row, not species plus one figure:
Type them, or tap **Generate text**. Generate text is off unless content generation is enabled in Settings. Templates still work when the text service is down. The model writes prose only. Templates inject the measured values into the final text.
- **SKU** — `[A-Z0-9-]{3,24}`. Practice runs use `SLAB-UAT-*`.
- **Length and thickness** — from capture; still editable here.
- **Square feet, board feet, widths** — shown as the client-computed values, with a manual override
path. The owner's typed number wins over the computed one.
- **Species** — exactly one leaf category from the synced store list. No hardcoded or invented
species; a new store species appears only after the next sync.
- **Wood categories** — at least one, from the synced list.
- **Edge type** — one term from the synced attributes.
- **Figure** — at least one term.
- **Grade** — at least one term.
- **Thickness band** — the Woo range that the measured thickness rounds up into (for example, a 1"
pick lands in the `1"–1 1/2"` band). The exact labels come from the synced store attribute.
- **Moisture** — one term, defaulting to kiln-dried. It is never inferred; the owner sets or keeps it.
- **fig-* tags** — at least one.
- **feat-* tags** — zero or more (character, inclusions, voids, checks map to existing `feat-*`
tags only).
- **Price** — a recommendation pre-fills from the species $/bdft rule times board feet; the owner may
override and that sticks. With no rule for the species, the price starts empty and the owner types
it. Publish is still allowed with a manual price.
If title, short title, or description are still blank at publish, templates fill them from the measured facts (species, lengths, widths, thickness, bdft). You can edit any of it afterward.
Title, short title, and description are not required to reach ready. They can be typed by hand,
generated from templates at publish, or written by Generate text (Call 2) when enabled. Templates
always run; they inject the deterministic numbers so the model never touches dimensions.
### Publish
### How missing fields show up
When the ready set above is complete, tap publish. App status moves through **publishing** while you wait for the result.
A field that is still empty and required shows its nudge inline, in place, the moment it is empty.
The owner sees each gap where it sits rather than learning about a problem after scrolling past it.
There is no single submit-time check that lists everything at once; the nudges are per-field as the
owner works. The slab cannot reach `ready` until the full set above is filled, whether by hand or by
a confident Call 1 the owner kept.
Create status comes from Settings (`draft` or `publish`, default `draft`). SKUs that match `SLAB-UAT-*` always create as Woo **draft**, even when Settings say publish. Practice listings stay draft so customers do not see them.
### Generate text
On success the server deletes originals and processed images. A slab marked published in the app means the store create succeeded; that is not the same word as WooCommerce "publish."
When content generation is enabled, a **Generate text** button runs Call 2 and fills title, short
title, and description with model prose plus template numbers. The owner can edit the result freely.
If the endpoint is missing or down, the button is disabled; templates still work and publish is never
blocked by a failed Generate text.
---
## Publish: create the store listing
## Practice path (UAT)
The owner taps **Publish** when review is complete. What happens next depends on the SKU and Settings.
Same capture and review as production, with a `SLAB-UAT-*` SKU. The listing is forced to Woo draft regardless of the draft/publish setting. Change the SKU when you are ready for a real listing.
### Draft versus live
---
- A normal SKU publishes according to the `woo_create_status` setting, which defaults to draft.
- Any SKU matching `SLAB-UAT-*` always creates a WooCommerce draft, even if Settings say publish.
That is how practice runs stay invisible to customers.
## Usual failures
The app's "published" means the store create succeeded and the local row plus photos were cleaned up.
That is not the same as the product being live in the store; a draft is created but hidden until Ty
promotes it.
**Mask clips wood or still holds sheet.** Turn the four knobs; confirm by eye. If it is still wrong, retake. A bad edge shifts square feet and widths without a later numeric check.
### What the owner sees on each outcome
**Axis skewed or on the wrong length side.** Rotate the axis, or retake. Widths and sqft follow whatever axis you confirm.
- **Success.** The screen shows that the listing was created, with the store's product reference when
available. The local draft and its photos are gone from the app.
- **SKU already exists.** Under the SKU field: "This SKU already exists in the store (any status)."
Two paths appear: edit the SKU to a new value, or open the existing listing. This version does not
update the other product in place. The draft stays in the browser; nothing is lost.
- **A value no longer valid.** If a category, attribute, or price was removed from the store between
sync and publish, that field shows "This value is no longer valid. Pick from the current list," with
the current options. The owner re-picks and retries.
- **Store unreachable or other failure.** A plain message on the screen says the listing could not be
created and to try again. No status number, no stack trace, no raw error code reaches the owner.
**Source too small after the 3:4 / 80% crop.** The app warns and asks for a retake of that photo. See `docs/TECH-SPEC-PIPELINE.md` TV-7.
The server keeps a log of every attempt either way, so a failed publish can be retried from the same
screen without re-photographing.
**Required store field still empty.** Inline empty state on that control. Publish stays blocked until the full ready set in Main path is filled.
**SKU already exists in the store under any status** (draft, private, publish, and the rest). The message sits on the SKU field: it already exists in the store. Actions there: edit the SKU, or open the existing listing when a link is available. This version does not edit the other product in place. Raw status codes do not appear on screen.
**Category, attribute, tag, or price no longer valid after a list refresh.** The message sits on that field: the value is no longer valid; pick from the current list, and the current options come with it. Your draft stays on the phone.
**Rules copy on the phone went stale at submit.** Brief "Updating rules." The phone refetches the shared rules, checks again, and retries.
**Store unreachable, auth failure, or other hard Woo failure.** Plain failure message. The slab can move to failed with detail. Draft and photos stay so you can retry.
**Vision off or below threshold.** Fill the required set by hand. Moisture starts kiln-dried, and the thickness band comes from your typed thickness. Inference is assist-only.
**Generate text unavailable.** Button disabled. Type copy or let templates write at publish.
Leaving a field: SKU is `A-Z`, `0-9`, and hyphen, length 3–24. Title, short title, and description cannot exceed the store max length.
## Browser compatibility
The app requires **iOS Safari 17** or **Android Chrome 110** (or newer). These versions provide the APIs the image pipeline depends on: OffscreenCanvas, MediaDevices, Web Workers, File System Access, and structuredClone.
Ty signed this walk on 2026-09-06 PT with one amendment: the compatibility matrix includes PC
browsers as well as phones. This app is not mobile-only.
When an older browser loads the app, the landing page shows:
Supported families (matrix):
> This app requires a recent browser. Please update Safari or Chrome, then try again.
- **Phone:** iOS Safari, Android Chrome
- **PC:** desktop Chrome, desktop Edge, desktop Firefox (Windows and macOS at minimum)
The message includes a link to the browser's update page..
Exact minimum version pins are still tracked under issue #4. The matrix above is the product lock
for which classes of client are in scope.
---
Unsupported browser behavior: hard stop. Show a clear message that this browser is not supported,
with a link to update or switch to a supported browser. Do not attempt a broken mask. No polyfill
strategy in this version.
## Out of scope for this POC UI
Load-bearing client surfaces (do not silently drop): canvas-based mask overlay and live knob re-run,
image crop/prep to 3:4 transparent PNG, file/photo intake for inventory shots, and the online API
calls for draft upload, review, and publish. Depth for APIs stays in `docs/TECH-SPEC-PIPELINE.md`
and `docs/OPENAPI.md`.
Server U2Net "Try harder" on capture (deferred from POC). A user slider for the 0.7 vision threshold. Offline / airplane capture. Any image-quality classifier or score. Strings painted on the live photo about lighting, steadiness, mask confidence, or a pixel requirement.
## What this version does not show
These are locked out for now; they do not appear on any screen:
- No photo-quality checker and no banner over the photo telling the owner it is bad. The overlay edge
plus the owner's eye is the only gate.
- No OCR of a printed SKU label. SKU is typed by hand.
- No ruler or scale detection. Length is entered by hand; the browser derives its own pixel-to-inch
scale from that length and the confirmed axis.
- No server-side measuring or cropping as the source of truth. The browser owns the happy path.
- No U2Net "try harder" control in this version. It stays in the architecture for later.
- No offline capture, no airplane-mode flow. Online only.
## Where each job lives
| Job | Screen(s) | Governing lock |
|---|---|---|
| Photo to listing | Capture through review to publish | AGENTS §1, TECH-SPEC, ARCHITECTURE swimlane |
| Practice drafts (`SLAB-UAT-*`) | Review (SKU field), Publish outcome | AGENTS §5, CONTENT-WOO payload status |
| Retake on bad cut | Capture mask overlay | TECH-SPEC §3.4–3.5, issue #15 |
| Fill or override fields | Review | DATA-MODEL `slabs`, AGENTS §6/7, issue #9 |
| Publish and recovery | Publish outcome states | CONTENT-WOO §2.5–2.6, OPENAPI 409/422, issue #18 |