docs(#10): UX interaction spec plus remaining POC issue write-back

UAT is POC, so #10 is in DOC COMPLETE. Capture knobs live on-screen, review
confidence presentation, 409/422 field recovery, taxonomy cache, portable
Call 1→Call 2, shared validation. U2Net Try harder deferred, not deleted.
This commit is contained in:
2026-09-02 00:30:47 +00:00
parent 3921a9dfa2
commit 7b7ed712c2
10 changed files with 329 additions and 79 deletions
+172
View File
@@ -0,0 +1,172 @@
# SlabUploader UX — POC interaction spec
Status: SPEC · 2026-09-01 · issue #10 (presentation) · functional rules in #9 / AGENTS
This is the UAT-facing interface contract. Wireframes are described as screens and states, not pixels. Implementers and testers use this file plus `AGENTS.md` and `docs/TECH-SPEC-PIPELINE.md`.
Functional numbers (0.7 threshold, empty vs prefill) live in AGENTS §6. This file only says how those rules look and feel.
---
## 0. Principles
- One-handed phone, outdoor or mill-floor lighting, thumb reach.
- The user always sees what is missing **on the field**, not only after Submit.
- Manual values always beat inference. Empty is honest; fake prefill is not.
- No raw HTTP codes or stack traces. Recovery stays on the same screen as the field.
Mandatory before **ready** (continue to publishable):
- exactly **one species**
- **at least one wood category**
- **at least one figure** (`fig-*` or the Figure attribute — same locked set as DATA-MODEL)
Issue #9 named species + ≥1 figure as the confidence-gate floor. Wood category is also required to list (original product). Presentation treats all three as mandatory with inline nudges (#10).
---
## 1. First-run (calibration trick)
Not OpenCV ruler detection (that path is deferred). First launch of capture shows a short overlay, dismissible, remembered in localStorage:
1. Lay the slab on a **green or black sheet**.
2. Shoot **top-down**, lens parallel to the broad face.
3. Put a tape or known length along the prevailing grain so *you* can type length in inches. The app does not read the tape.
4. Take 1–5 photos. Confirm the mask before trusting numbers.
Skip control: “Don’t show again.” Reset lives in Settings, not on the capture chrome.
---
## 2. Capture — mask is the load-bearing step
Background removal defines the pixel footprint. Length, widths, sqft, and bdft all sit on that mask. A clipped or fat mask silently moves the bounding rectangle and the 6" width samples.
### Layout (one screen)
- Full-bleed photo with **mask overlay** on the original (included vs excluded), not only the cropped PNG.
- Four knobs **on this screen**, next to the preview — never a trip to Settings:
- Sheet: auto / green / black
- Sensitivity
- Edge offset
- Feather
- Confirm edge. Then length-axis overlay (rotate / confirm). Then length, thickness, SKU.
- Retake photo. POC does **not** show “Try harder” (server U2Net). That control is deferred, not deleted — do not remove it from architecture docs.
### Live re-run
Changing any knob re-runs client background removal on the current photo and refreshes the overlay immediately. No save button for knobs. No server round trip for mask tuning in POC.
### Empty / error states (capture)
| Situation | What the user sees |
|---|---|
| Sheet color not auto-detected | Prompt: pick green or black. Stay on this screen. |
| Mask not one contiguous slab | Banner on the photo: “Cut is broken — retake or tighten the sliders.” Confirm stays disabled. |
| Axis looks wrong | Rotate control; if still wrong, retake. |
| After crop, shorter side would be < 1600px | Warn: this photo cannot list until retake. No fake upscale. |
| User tries to continue without confirming mask | Confirm is the gate; Continue is disabled until confirm. |
Knobs persist in localStorage (reset-to-default in Settings). They are not server secrets.
---
## 3. Review screen
After calibrated upload (and Call 1 if inference is on).
### Field rendering
| Call 1 confidence | Species, wood categories, edge, figure, grade, feat-* |
|---|---|
| **≥ 0.7** (default; configurable in server settings later, not a POC slider) | Prefill from Call 1. Every value is editable. Show a quiet “suggested” affordance, not a lock. |
| **< 0.7** | Leave **empty**. Do not stash the low-confidence guess in a hidden field. |
| Inference OFF | Empty; user fills by hand. Same mandatory set. |
Pickers for species, wood category, and figure list **only** Woo-synced leaves. No “other”, no hardcoded mill list. A species that appeared in Woo after last sync is invisible until the next successful sync.
Price: recommendation from species $/bdft × bdft when a rate exists; otherwise empty. Override always allowed.
Title / description: empty until the user taps **Generate text** (Call 2) or types. Call 2 never auto-runs.
### Per-field nudges (not submit-only)
While a mandatory field is empty, that control shows an inline state (mark + short line under the field):
- Species: “Pick the species from the store list.”
- Wood category: “Pick at least one wood category.”
- Figure: “Pick at least one figure.”
Nudges appear as soon as the screen loads with empties, and clear when the field is valid. Submit/Continue to ready stays disabled while any of the three is empty — **and** the user can still see which one without scrolling to a toast after tap.
Do not use a single post-submit modal as the only signal.
### Overrides
Changing a prefilled species/figure is a normal edit. No confirm dialog. Manual value is what publishes.
---
## 4. Settings (relevant bits)
- **Store URL**: read-only text from `WOO_BASE_URL` (compose). Not an input.
- Woo username + application password: inputs. Password never round-trips on GET.
- Inference endpoint + key + model; enable flags.
- Species $/bdft table (seeded from synced species only).
- `woo_create_status` draft | publish.
- Reset capture knobs to factory.
- Manual “Refresh taxonomy” is optional for POC; test-connection always syncs.
---
## 5. Validation (field exit and submit)
Shared module on FastAPI (`docs/OPENAPI.md`). Client caches it; hash checked **on submit**, not on every blur.
- Blur: local check with cached rules. Required fields block; optional empty is valid; optional with content must pass.
- Length: hard cap at the Woo limit (cannot type past it).
- Character set: invalid characters rejected on exit.
- Submit: full pass + hash check. Mismatch → “Updating rules” → re-validate → retry. No data loss.
Presentation: errors sit under the field. No top-of-screen dump for field problems.
---
## 6. Publish errors (after commit)
Draft stays on the phone.
**409 duplicate SKU** — inline on SKU:
- “This SKU already exists in the store (any status).”
- Actions: **Edit SKU** (focus the field) · **Open existing listing** (admin URL if the server returned it; otherwise copy SKU and a one-line “look this SKU up in Woo admin”).
- Never show `409` as the message.
**422 stale value** — inline on the field Woo rejected (category, attribute, price, …):
- “This value is no longer valid. Pick from the current list.”
- Options from the synced cache after the submission-time refresh.
- Never show `422` as the message.
Server 5xx / timeout / auth: one recoverable banner (“Couldn’t reach the store — try again”), draft intact. Not a field nudge.
---
## 7. UAT can write cases against
1. First-run overlay appears once; skip persists.
2. Knobs on capture; each slider live-reruns mask overlay on the source photo.
3. Confirm disabled until mask is contiguous.
4. Review with inference ON and confidence ≥ 0.7: fields filled, editable.
5. Review with confidence < 0.7: species/wood category/figure empty; inline nudges; cannot mark ready until all three satisfied.
6. Species picker contains only synced names; typing a non-synced name is impossible.
7. Duplicate SKU: inline SKU recovery, two actions, no raw code.
8. Stale category after sync: 422 copy on that field with current options.
---
## 8. Non-goals (this spec)
- Visual branding, type scale, color tokens.
- Server U2Net “Try harder” control (deferred).
- Configurable 0.7 slider in the UI.
- Offline / airplane-mode chrome.