diff --git a/deploy/README.md b/deploy/README.md index 7d41def..083a7ae 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -6,3 +6,12 @@ Where the container runs is chosen at runtime. No FastAPI and no Woo client in this milestone. `docker-compose.yml`, `Caddyfile.fragment`, and `.env.example` describe an older two-service stack. They are not the Milestone 1 contract. + +## Milestone 1 UAT — deployment POR + +- **One container.** Build a single Docker image that serves the SvelteKit capture app. No FastAPI, no Woo client, no Caddy TLS edge in M1. (Caddy/compose/Woo are Milestone 3.) +- **Host chosen at runtime.** Record the machine and URL in `INVENTORY.md` when the container starts. Do not put a host name in the product spec. +- **LAN-reachable UAT URL.** For UAT the container binds a port on the LAN interface (not only `127.0.0.1`), so other machines on the LAN — e.g. a phone — reach the app at `http://:`. +- **No backend.** The app is browser-only in M1 (see `docs/CAPTURE.md`). There is no `/api` route and no inference. + +Reference for the current UAT host: see `INVENTORY.md` (filled in when the container starts). diff --git a/docs/CAPTURE.md b/docs/CAPTURE.md index 150bd1d..d003822 100644 --- a/docs/CAPTURE.md +++ b/docs/CAPTURE.md @@ -45,6 +45,12 @@ Phone: iOS Safari, Android Chrome. PC: Chrome, Edge, Firefox. Other clients get a hard stop. +## Input photo + +Accepted input: JPEG. Phones typically save JPEG; Android Chrome delivers JPEG by default. + +If the file is HEIC and the browser cannot decode it, show a one-tap notice to retake the photo as JPEG. HEIC is never silently dropped and never decoded server-side in Milestone 1. + ## States One photo component is reused. Each state advances on approve. Back returns to the previous state. A device draft restores the last approved state. @@ -126,3 +132,5 @@ The first inventory photo is the measurement reference. Later inventory photos u Source below that floor: warn and require a new photo. No upscale from an undersized source. Editor preview of alpha is a light gray checkerboard. The storefront uses the theme background. + +File size: aim under 200KB per image (soft goal, not a hard gate in Milestone 1). diff --git a/docs/M1-POR.md b/docs/M1-POR.md new file mode 100644 index 0000000..d57c7f6 --- /dev/null +++ b/docs/M1-POR.md @@ -0,0 +1,96 @@ +# Milestone 1 — Plan of Record (POR) + +Decisions captured as they are made. This is the working plan for implementing and UAT-ing Milestone 1. It is the source of truth for scope, order, and dispatch; the spec detail lives in `docs/CAPTURE.md` and `docs/PRODUCT.md`. + +## Scope + +Implement **Milestone 1 — Capture** only (see `AGENTS.md`). Browser-only, inference-free, no backend, no Woo create. Milestones 2–4 are out of scope. + +## Method — TDD (non-negotiable) + +Every card is delivered **test-first** per the `test-driven-development` skill: + +``` +NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST +``` + +- **Red-Green-Refactor**, tracer-bullet: one behavior slice at a time (RED→GREEN→REFACTOR), not all-tests-then-all-code. +- **Verify RED** (watch it fail for the right reason) and **Verify GREEN** (it passes + full suite stays green) are mandatory before the card is done. +- Code written before a failing test is **deleted and redone** — not kept as "reference." +- The deterministic core (math + PNG contract) is unit-tested in a browser-runnable JS suite; UI behaviors are tested with Playwright against the fixtures. +- A card is not complete until: every new function has a test, each test was seen failing first, the full suite passes with pristine output. + +## Card breakdown (implementation order) + +Each card follows TDD. "Define" = write the failing tests (RED); "Build" = minimal code to pass (GREEN); "Refactor" = clean up keeping green. + +1. **Scaffold + Dockerfile + test harness** — SvelteKit project with the browser-only M1 runtime (Web Worker, canvas/OffscreenCanvas, IndexedDB via `idb-keyval`), `frontend/Dockerfile`, and the test harness (JS unit runner + **Playwright** web). + - *TDD note:* this card establishes the harness itself (a smoke test that runs and fails, then passes) so every later card has a place to be RED/GREEN. +2. **Deterministic core** — the pure math + PNG-encode functions. + - *RED:* unit tests for `scale`, `sqft`, `bdft`, width sampling, 1/8″ stepping, rounding, and the PNG contract (3:4, 8-bit RGBA, 80% fill, ≥1600px floor, undersized→retake). Run on the fixtures' calibration records. + - *GREEN/REFACTOR:* implement to pass; core is authoritative and server-less. +3. **Capture + Mask** — Add photo (JPEG; HEIC→"retake as JPEG" notice), green/black key, four knobs, debounced recompute, mask approve. + - *RED:* Playwright — load fixture, key sheet, approve; empty-mask blocks approve; HEIC shows the notice. + - *GREEN/REFACTOR:* build the Mask state to pass. +4. **Length axis** — min-area bounding rectangle + long-side axis, user rotate, approve. + - *RED:* Playwright — rect drawn from the approved mask, axis on long side, rotate, approve. + - *GREEN/REFACTOR:* build the axis state. +5. **Measure + PNG** — length/thickness (1/8″), compute sqft/bdft/widths, write the 3:4 inventory PNG. M1 stops here. + - *RED:* Playwright — enter L/thickness → Calculate; assert PNG is 3:4 with alpha, and numbers equal the fixture's expected values. + - *GREEN/REFACTOR:* build Measure + PNG encode. +6. **M1 UAT** — serve the container per `deploy/README.md` §Milestone 1 UAT; run the full Playwright suite in the container; then the manual phone pass. Record host + URL in `deploy/INVENTORY.md`. + +## Deployment (UAT) + +See `deploy/README.md` §Milestone 1 UAT — deployment POR: one container, host chosen at runtime, LAN-reachable URL, no backend. + +## Dev / QA worker dispatch + +Two local inference endpoints are available as dispatchable workers for test / dev / QA: + +- **`.202:8081`** — `gemma-4-12B-it-q4_k_m` (12B, Q4) — **completion only, no vision.** Use for text/code tasks: drafting, code review, test-writing help. +- **`.202:8082`** — `Qwen3.8-27B-UD-IQ3_XXS` (27B, IQ3) — **completion + multimodal (vision).** This is the **vision worker**: any task that must look at a slab photo (bg-removal tuning, mask/axis calibration review, fixture sanity checks) goes here. + +Coordinator: Hermes (me) — plans, decomposes, dispatches, verifies, and does work directly when a card is small or judgment-heavy. + +**Rules:** +- **Vision tasks → `.202:8082` only.** +- **Never more than one task on an endpoint at a time.** Two concurrent jobs on the same endpoint back up and overheat. Serialize dispatch; one endpoint = one active task. +- Both run low-quant (Q4 / IQ3). Fine for dispatch-style assist; deep sustained reasoning is better done by the coordinator directly or on a higher-quant model. + +## Test plan (M1) + +Three layers. The deterministic core is unit-tested; the end-to-end path is Playwright; a real phone is the final UAT gate. + +### 1. Unit (deterministic core) — must pass before any UI consumes it +- `bdft = sqft × thickness_in`; `sqft = (mask_px / scale²) / 144`; `scale = longest_axis_px / L`. +- 1/8 in stepping; rounding (sqft/bdft 2 decimals). +- Width sampling every 6 in along the approved axis. +- PNG encode contract: 3:4 aspect, 8-bit RGBA, 80% fill, shorter side ≥ 1600px when source allows; undersized source → warn + retake (no upscale). +- Mask empty → approve blocked. + +### 2. Playwright (automated end-to-end, runs in the UAT container's browser) +Walks the 5 states against a **sample photo**: +- Add photo → Mask (set sheet + sensitivity) → approve → Length axis → approve → Measure (enter L, thickness) → Calculate. +- Assert: PNG rendered, dimensions = 3:4, alpha present; sqft/bdft/widths equal the expected values for that fixture (within tolerance); numbers visible on screen. +- HEIC input → "retake as JPEG" notice shown (not a crash). +- Device draft: reload → Resume → Continue restores last approved state. + +### 3. Manual phone UAT (final gate) +On a real phone (iOS Safari / Android Chrome) over the LAN UAT URL: repeat the happy path with one real slab; confirm PNG + numbers on screen. Record host + URL in `deploy/INVENTORY.md`. + +## Test fixtures (sample pics) + +Real slab photos are the fixtures (supplied by Ty from `\\truenas.local\Business\Samples`, SMB user `SMBSVC`). Each is a DJI MIMO top-down JPEG, ~4000×2256, slab on a green/black sheet. + +`tests/fixtures/`: +- **sample_01 … sample_0N.JPG** — a representative handful of real slab photos (pulled on demand from the TrueNAS Samples share; do not bulk-copy the whole folder). +- Each fixture gets a **calibration record** in `tests/fixtures/.json`: the slab's true length and thickness (in inches), the sheet color, and the approved mask/axis reference — used to compute the expected `scale`, `sqft`, `bdft`, and sampled widths for assertions. + +The real photos drive both **bg removal** (green/black key) and **rectangle/axis calibration**. Expected values come from the calibration records, so assertions stay deterministic even on real imagery. + +**M1 fixture set (agreed, complete for M1):** three black-sheet slabs — one irregular (stress-tests the min-area rectangle/axis), two clean elongated. Green-sheet keying is deferred (not an M1 fixture). A 1 HEIC sample (optional) for the decode-fallback test. + +## Open decisions + +- (none outstanding for M1 as of this plan; new decisions append here) diff --git a/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.JPG b/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.JPG new file mode 100644 index 0000000..15c3f2b Binary files /dev/null and b/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.JPG differ diff --git a/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.json b/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.json new file mode 100644 index 0000000..9134538 --- /dev/null +++ b/tests/fixtures/dji_mimo_20260109_152422_20260109152421_1771310617476_photo.json @@ -0,0 +1,11 @@ +{ + "fixture": "dji_mimo_20260109_152422_20260109152421_1771310617476_photo.JPG", + "label": "cand_a", + "source": "\\\\truenas.local\\Business\\Samples", + "orientation": "irregular", + "sheet": "black", + "image_px": [4000, 2256], + "length_in": 28.0, + "thickness_in": 2.25, + "notes": "Organic/irregular slab shape; stress-test for min-area bounding rectangle + length-axis. Length is the longest axis of the slab." +} diff --git a/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.JPG b/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.JPG new file mode 100644 index 0000000..afc6484 Binary files /dev/null and b/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.JPG differ diff --git a/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.json b/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.json new file mode 100644 index 0000000..96b9e85 --- /dev/null +++ b/tests/fixtures/dji_mimo_20260109_155754_20260109155755_1771310617107_photo.json @@ -0,0 +1,11 @@ +{ + "fixture": "dji_mimo_20260109_155754_20260109155755_1771310617107_photo.JPG", + "label": "cand_c", + "source": "\\\\truenas.local\\Business\\Samples", + "orientation": "horizontal", + "sheet": "black", + "image_px": [4000, 2256], + "length_in": 21.0, + "thickness_in": 1.75, + "notes": "Clean elongated slab, length runs horizontally. Second clean-case fixture." +} diff --git a/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.JPG b/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.JPG new file mode 100644 index 0000000..5d4eb6c Binary files /dev/null and b/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.JPG differ diff --git a/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.json b/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.json new file mode 100644 index 0000000..f1cfd48 --- /dev/null +++ b/tests/fixtures/dji_mimo_20260109_155818_20260109155817_1771310617082_photo.json @@ -0,0 +1,11 @@ +{ + "fixture": "dji_mimo_20260109_155818_20260109155817_1771310617082_photo.JPG", + "label": "cand_b", + "source": "\\\\truenas.local\\Business\\Samples", + "orientation": "horizontal", + "sheet": "black", + "image_px": [4000, 2256], + "length_in": 21.0, + "thickness_in": 1.75, + "notes": "Clean elongated slab, length runs horizontally. Primary clean-case fixture for expected sqft/bdft/widths." +}