Ty UAT rejected phone photos under the 1600px bar.
Lock the mid-band case (shorter side 1000) and floor
geometry at 800×1067 / 0.8×800=640 before the code change.
Why
CAPTURE.md Crop keeps slab pixels and makes removed pixels transparent.
encodeInventoryPng only drawImage-cropped the AABB from the raw photo.
Scope
composeInventoryFrame samples the source through approvedMask.bitset.
Non-slab pixels stay alpha 0. Drop the 8% MARGIN; fill 80% of mask
extents in a 3:4 frame. Floor rules unchanged.
Verification
cd frontend && npm test
12 pass, including alpha contract (no sheet green in opaque pixels).
Why
Calculate showed accurate numbers but the inventory PNG kept the sheet
background. Measure already uses approvedMask.bitset; encode did not.
Scope
Add composeInventoryFrame and inventory.test.ts alpha/crop proofs.
Wire encodeInventoryPng({ bitmap, mask, source }) at calculate().
This commit still paints the slabBounds AABB without the bitset gate so
the alpha contract fails until the follow-up fix.
Verification
node --import tsx --test src/png/inventory.test.ts
alpha contract fails (sheet green leaks); 3:4 / 80% / floor pass.
Replace MeasurePanel steppers with number inputs. Commit on blur, change,
and Calculate snaps inches to nearest 1/8 via inToEighths. Calculate
enables when both drafts parse to > 0. Axis, mask, and scale math unchanged.
Ty UAT rejects ± steppers as the only Length L / Thickness control.
Assert MeasurePanel exposes type=number inputs and that inches snap via inToEighths.
Length axis keeps the approved mask, replaces Flip 180 with a ± angle
slider, and sets scale from wood span along the approved direction rather
than bbox.longSidePx. Widths still sample every 6 in perpendicular to that
axis.
Contain-fit waits for a real parent width, uses ResizeObserver, and shrinks
width when maxH would otherwise stretch the CSS box. Pure fitDisplay is
covered by node tests.
Why
On phone, the single file control opened the camera because it set
capture=environment. CAPTURE.md requires camera or picker.
Scope
AddPhoto.svelte now has Take photo (capture=environment) and Choose
from gallery (no capture). Both call the same onPicked path.
Verification
Playwright against overnight after redeploy asserts two inputs and
gallery has no capture attribute.
Why
Phone and PC need a single capture flow that ends at inventory PNG and
board-foot numbers with no backend, inference, or Woo create.
Scope
Add frontend/ Vite+Svelte capture UI with Resume, Add photo, Mask,
Length axis, and Measure. Mask runs in a worker with Auto/Green/Black
and a 2.0s sensitivity debounce. Measure computes scale, widths, sqft,
bdft, and a 3:4 alpha inventory PNG. Persist one device draft in
IndexedDB. Serve the static build from frontend/Dockerfile via nginx
(deploy/nginx-m1.conf). No FastAPI.
Verification
sudo docker build -f frontend/Dockerfile -t slabuploader-m1 .
sudo docker run -d --name slabuploader-m1 -p 0.0.0.0:18080:80 slabuploader-m1
curl http://127.0.0.1:18080/ returns 200
Playwright: synthetic green-sheet photo through Calculate shows sqft/bdft