feat(capture): ship Milestone 1 browser capture app

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
This commit is contained in:
2026-09-14 07:53:39 +00:00
parent 3bae76cea3
commit d86fc93ed6
24 changed files with 2969 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import { get, set, del } from 'idb-keyval';
import type { Draft } from '../capture/capture.types';
const KEY = 'slabuploader-draft';
export async function loadDraft(): Promise<Draft | null> {
try {
const d = await get<Draft>(KEY);
return d && d.version === 1 ? d : null;
} catch {
return null;
}
}
export async function saveDraft(d: Draft): Promise<void> {
await set(KEY, d);
}
export async function clearDraft(): Promise<void> {
try {
await del(KEY);
} catch {
// idb unavailable: draft persistence is best-effort, app state is not
}
}