← back to Dw Photo Capture

DEVICE-TEST.md

137 lines

# dw-photo-capture — real-device test checklist (iPhone)

**Open in SAFARI** (not Chrome/in-app browsers — iOS only gives Safari the camera):
`photo.designerwallcoverings.com` → login `admin` / `DW2024!`

Everything below is deployed and headless-verified; this pass confirms the *physical* camera +
measure paths that only a real device can exercise.

---

## 1. Home → camera-first
- [ ] Home shows 3 white pills: **Add New SKU · Update SKU · Check Sample In**.
- [ ] Tap **Add New SKU** → the camera opens *immediately* (that's the camera-first flow).

## 2. Add New SKU — capture + specs
- [ ] Shoot a **sample label** → within a couple seconds the fields auto-fill (Mfr#, name, color)
      and a **Captured specs** list appears (material/width/repeat/match/contents/etc.).
- [ ] Tap a **🎤 mic** on a field → speak → it fills that field.
- [ ] **📷 Photo** and **🎥 Video** buttons add media; counters climb (N/10 · N/6); ✕ removes one.
      *(This is the HEIC path — the one thing never tested on-device. If a photo ever fails to
      load, it should toast "Could not read that photo," NOT freeze.)*
- [ ] **👁 Preview** → shows the draft summary → **✅ Create draft** → "Created DRAFT · <sku>".
      Confirm it's a **draft** in Shopify admin (not live).

## 3. 📐 Measure — calibrate once, then card-free
- [ ] Tap **📐 Measure** → first time shows the red 💳 box + "One-time setup."
- [ ] Lay a **credit card** in view, size the red box to it, tap **✓ Save calibration**.
- [ ] Card box disappears → size the green 📦 box to some goods → live **W × L** reads out.
- [ ] Close and reopen Measure → it goes **straight to the goods box, no card** (calibration saved).
- [ ] Sanity: measure something you can check with a ruler; expect ~±10% (hold the phone at a
      similar distance to your calibration).

## 4. Update SKU — LIVE writes (careful — real products)
- [ ] Tap **Update SKU** → camera opens. Shoot a **label** of a product that exists in Shopify.
- [ ] It resolves: "✓ Updating <title>". Add a photo → **⬆ Push media to SKU (LIVE)** →
      "✅ LIVE: N photo(s) → <sku>". Confirm the photo appears on that product in Shopify.
- [ ] **Visual-ID path:** with the mfr# blank, shoot the **front pattern** → it should match by
      design ("🔍 Visually matched <title>" once the re-embed is done). If it auto-matched,
      the **first Push asks "tap Push again to confirm"** — that's the safety gate; a 2nd Push writes.
- [ ] **Multi-photo tip (matters):** for visual-ID, take **2-3 pattern shots** from slightly different
      angles/distances before it resolves — the app **fuses all views** (a pattern matching across
      several shots outranks single-shot noise). Single handheld shots are only ~1-in-3 top-1;
      multiple views is the reliable way to ID by design. Same applies to the Add-mode dup warning.
- [ ] Edit the mfr# after a match → the target should reset ("mfr# changed — re-find") so you never
      push to a stale product.

## 5. Video → Shopify
- [ ] In Add or Update, add a short **🎥 video** → save/push → watch the note:
      "🎥 Uploading… → transcoding N/M… → N video(s) ready ✓" (Shopify transcodes async, ~1–2 min).
- [ ] Confirm the video appears on the product in Shopify admin.

## 6. Check Sample In
- [ ] Tap **Check Sample In** → front/back identify modal opens; shoot back label + front pattern →
      it fuses code + pattern into a best match.

---

## Known-good expectations
- **Visual-ID confidence** rises to **high** once the fine-tuned re-embed finishes (during the
  re-embed it may say "medium" — that's coverage, not quality; the model is 2.3× better on held-out).
- **Draft vs live:** Add = draft (safe); Update = **live immediately** (your explicit choice) — but
  a visual/ambiguous match always needs the 2nd-Push confirm before it writes.

## If something's off
- Camera won't open → you're not in Safari, or camera permission denied.
- Measure wildly off → Recalibrate (↺) and hold at a consistent distance.
- Video "N failed" → clip too big (>150MB) or a transient upload error; retry.

---

# Batch Shoot Mode — real-device test checklist (iPad, TK-11909)

Production-line capture for 400+ samples on a **fixed copy-stand with fixed light**. Open
`photo.designerwallcoverings.com/batch` in **Safari** (login `admin` / `DW2024!`), iPad plugged in,
auto-lock set to Never.

> **Headless-verified already (server side):** `/batch` serves · `/api/batch-shot` writes
> `<SKU>_original/_master/_web.jpg` under `photos/batch/<session>/` · idempotent re-shoot ·
> per-session `data/batch-sessions/<session>.jsonl` manifest · nested `/photos/…` read-back ·
> path-traversal guard (404) · existing `/api/photo` + `/cam` untouched.
> **Everything below needs a real iPad camera — it cannot be exercised headless.**

## 1. Session + calibrate
- [ ] `/batch` → fill **Vendor / Collection / Date / target count** → **Start batch**.
- [ ] Calibrate screen: lay a **neutral gray card** filling the green box, gray patch in the center
      square → **Capture calibration**. Toast shows WB gains + luma. (Re-open the batch → it skips
      straight to shooting; calibration persisted per session in localStorage.)

## 2. Shoot — PSku photo, then Info photo (two shots per sample, one code)
- [ ] Camera fills the **whole iPad screen** (100dvh, no letterbox strip at the bottom). The top shows
      **BATCH: <vendor> <collection>**, the **N / count** counter, and status chips
      (CAM / WB·LOCK / EXP·LOCK / FOCUS / CAL / SKU / Δ).
- [ ] **Tools panel** sits center-bottom on load with a **📷 PSku Photo** badge. Drag it by the
      header (touch) anywhere on screen; tap **▾** to collapse to just the header and again to expand.
      Reload → panel position AND collapsed state are remembered.
- [ ] **Live colour tools (WYSIWYG):** move **Brightness / Warmth / Hue** — the live preview changes
      *exactly* as the saved photo will (same pixel pipeline). **Verify:** shoot with a strong Warmth
      shift, open the saved `_master.jpg` from `/photos/batch/<session>/` next to the live preview —
      they must match; `_original.jpg` must NOT carry the shift. **↺ Reset colour** zeroes all three.
      Values are sticky (localStorage `dwbatch.tune`) — reload → same slider positions.
- [ ] **Shot 1 — PSku (product) photo, no code required:** place sample face-up → **shutter**.
      Badge flips to **Info** and the state line asks for the label.
      *Short-circuit:* if the code was already readable on the front, it fills the SKU and the pair
      completes with ONE photo (no Info shot asked).
- [ ] **Shot 2 — Info (label) photo:** flip to the label → shutter. OCR (`/api/identify`, Gemini
      ≈ $0.0006/read) fills **code / vendor / pattern / colour**; all four fields are editable. The
      **queue · cost $** line climbs. Low confidence shows `?` — type to override.
- [ ] **Auto-snap is OFF by default** (operator-paced). Tick it → removing one sample and placing the
      next auto-fires once the frame settles + passes the gate (≥1.5 s cadence governor). A **BT
      shutter** (volume/space/enter) also fires.
- [ ] **Quality gate:** deliberately shoot blurry / too-dark / empty-stand → a big red **RETAKE**
      appears and it does **not** enqueue. A good shot flips the state to green **READY**.
- [ ] **Software lock / drift:** nudge the light or the stand → CAL chip flips **DRIFT** and a
      **RE-CALIBRATE** overlay blocks capture until you re-shoot the gray card. *(This is the
      substitute for the hardware WB/exposure lock iOS Safari can't give.)*

## 3. Outputs + durability
- [ ] After ~10 samples, confirm on the server: `photos/batch/<session>/` holds, per sample,
      `<SKU>_psku_original.jpg` + `<SKU>_info_original.jpg` (untouched), matching `_master.jpg`
      (WB+exposure+slider-corrected, cropped) and `_web.jpg` (~1600px). A single-shot short-circuit
      keeps the legacy `<SKU>_original/_master/_web.jpg` names. The session `.jsonl` manifest has one
      line per shot (side recorded).
- [ ] **Reload mid-batch** (durable queue): pull-to-refresh while a few are still uploading →
      the queue count survives the reload and keeps draining (IndexedDB). Nothing is lost offline.
- [ ] **Thermal:** during a long run, if the device warms the cadence should visibly back off
      (toast "Slowing cadence") rather than stutter/crash.

## Batch — known limits (v1)
- **No RAW/DNG, no 16-bit TIF** — Safari gives only 8-bit JPEG frames; masters are corrected JPEGs.
- **WB/exposure/focus "LOCK" is software** (enforced against the calibration frame), not hardware.
- **Master crop is axis-aligned** (copy-stand square-on). True 4-corner perspective warp + 24-patch
  ColorChecker CCM/ΔE + a Shopify batch-attach are the **v1.1** backlog.
- Two *different* samples that OCR to the **same SKU** in one session overwrite each other's files
  for that side (the manifest still records both by seq+timestamp) — resolve duplicate SKUs at the
  keypad. (PSku vs Info of the *same* sample no longer collide: they are side-named.)
- Slider corrections are baked into `_master`/`_web` only; `_original` is always the raw frame.