Seed-packet scan UI: camera/upload → proposal → confirm (#102) #119

Merged
steve merged 2 commits from feat/seed-packet-scan-ui into main 2026-07-22 17:07:26 +00:00
2 Commits
Author SHA1 Message Date
steveandClaude Opus 4.8 d9c2de4882 Address review: rescan staleness + escapable slow scan
Build image / build-and-push (push) Successful in 11s
From the Gadfly review of #119:

- Reset the cost field on a new scan — it was the one review field not reseeded
  in the success handler (there's no cost on a packet), so a value typed before a
  Rescan lingered into the next proposal.
- Clear the review-phase error when returning to capture via Rescan, so a failed
  confirm doesn't leave a stale error showing on the capture screen.
- Make the scan escapable: thread an AbortController through the scan mutation and
  wire it to Cancel (no longer disabled while scanning), so a slow or hung vision
  call — the server allows up to 120s — isn't a trap the user can only leave by
  reloading. An aborted scan is treated as a cancel, not an error.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 13:06:23 -04:00
steveandClaude Opus 4.8 e0d78b891b Seed-packet scan UI: camera/upload → proposal → confirm (#102)
Build image / build-and-push (push) Successful in 23s
Gadfly review (reusable) / review (pull_request) Successful in 10m32s
Adversarial Review (Gadfly) / review (pull_request) Successful in 10m33s
The seed-packet capture backend has been live since #94 (POST /seed-lots/scan
and /seed-lots/from-packet, with /capabilities reporting `vision`), but there was
no UI, so the feature Steve asked for — "take a picture of a packet and it
auto-creates" — was unreachable. This adds the mobile-first UI.

- Entry point in the Plants catalog ("Scan a packet"), shown only where
  `capabilities.vision` is on — so it's never a dead button on an instance with
  no vision model. Adds `vision` to the capabilities schema (both flags now
  default false, so a partial response hides a feature rather than failing parse).
- ScanPacketModal: two phases in one dialog. Capture uses a file input with
  `accept="image/*" capture="environment"` so a phone opens the camera directly
  (also picks an existing photo; HEIC is handled server-side). Review shows the
  extracted fields read-only for context, then an editable confirm — pick a ranked
  existing candidate (with its match reason) or create a new variety prefilled from
  the packet. Nothing commits blind, which is the whole point of the backend's
  never-auto-create design.
- New data layer (lib/seedPacket.ts): zod schemas mirroring the backend
  (SeedPacket / PacketProposal / PacketResult), the scan + confirm mutations, and
  the pure prefill helpers (newPlantDefaults / lotDefaults) — unit-tested, since
  the prefill mapping (unknown category, missing spacing, seed-count→unit) is
  where the bugs hide.
- api.ts learns to send FormData as multipart (no JSON content-type) for the
  scan upload, via a new api.postForm.

Frontend only; no backend or route change. Last open child of epic #96.

Live camera→vision→proposal verification needs a vision-configured instance
(the flow is model-gated and unreachable locally, same as the assistant);
the confirm half and all prefill logic are covered by tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
2026-07-22 12:52:21 -04:00