After AI extraction, check if the roaster and roast already exist by
slug. When matched, show compact summary cards instead of full edit
forms. Each card has a "Change" link to revert to the form if the
match is wrong.
- Add match_existing_entities() for slug-based roaster/roast lookup
- Return _matched-roaster-id and _matched-roast-id signals from extraction
- Add submit_existing_roast() path to skip creation when roast exists
- Dynamic submit buttons: Save Roaster & Roast / Save Roast / Open Bag
- Hidden inputs bound to signals handle all form submission cleanly
- 12 page smoke tests: homepage (empty/with data/stats), login,
auth-gated redirects (check-in, add, account), authenticated page
loads, logout, and scan redirect
- 5 check-in API tests: existing cafe, new cafe creation, auth,
and validation
- 7 scan API tests: roaster+roast creation, slug-based reuse, bag
creation with timeline event, auth, and field validation
- 9 AI extraction tests: all 3 extract endpoints with wiremock-mocked
OpenRouter, both JSON and Datastar signal responses, auth and
empty input validation