What this feature is
A user turns a dispensary receipt into a record in the app. They paste or dictate the receipt text, or photograph it, and an AI parse returns line items. They review that parse — fix a strain name, pick a different match, add a missing strain, remove a line, correct a price or amount, set the shop and purchase date, mark it a personal purchase — and then save it one of three ways: stats only, plus stash, or plus stash and a notebook. Saved orders live in an archive they can filter, search, open, edit, add to their stash, and delete. Receipts that arrive from Gmail enter this same review screen once a draft exists. A dev-only Order Import Lab seeds realistic historical receipts so the whole path can be exercised without a mailbox.This page is the contract, not a coverage report. Every line below is true of the code
on
main today and is enforced by the cited tests. Behaviors that are deliberately
unproven live in Deliberate gaps, never in the tables.The behavior contract
Importing a receipt by text
Importing a receipt by photo
Reviewing and editing before save
Saving an order
What happens after a save — the effect chain
Browsing and searching the order library
Opening and editing a saved order
Adding an order’s items to the stash
Deleting an order
Dispensary order history and stats
Historical import and the Order Import Lab
Gmail-sourced orders in this screen
Ownership and access
What the live tier proves end-to-end
Tier C drives the deployed backend with really-logged-in throwaway identities and cleans up after itself.- A save is a save. The exact payload the review screen builds is accepted by the deployed commit, its outcome is readable, a replay is idempotent, a divergent replay is rejected, and two clients racing the same key produce one order — ORD-200, ORD-201, ORD-202, ORD-203, ORD-230.
- The effect chain actually runs on real infrastructure. A committed order lands in the stash, increments the dispensary’s visit, and moves achievement metrics — ORD-204, ORD-205, ORD-206.
- Slow strain research still finishes the job. An unresolved line goes out to the research pipeline, comes back, and re-projects into the stash; the whole chain runs in reservation order and a stuck attempt is driven to completion by resume — ORD-207, ORD-209, ORD-210.
- Real receipts parse, and non-receipts do not. Real text and a real photo parse end to end through the deployed API, a cancelled-order text yields zero items, and the Pro gate is enforced live — ORD-211, ORD-212, ORD-231, ORD-213.
- Paid work is not billed twice. The same attempt id replays instead of re-parsing, a different payload under the same id conflicts, and an oversized image is refused at the real edge — ORD-214, ORD-215, ORD-216.
- Strain matching works against real strain data for a signed-in free user, with confidence bands rather than exact floats — ORD-217, ORD-218.
- Other people’s orders stay other people’s, enforced by the deployment rather than the client — ORD-221, ORD-222.
- Life after save. A live delete leaves the notebook and the stash intact, an edit round-trips, and the Order Import Lab seeds, reviews, commits, and cleans up a realistic historical batch — ORD-223, ORD-224, ORD-225.
Deliberate gaps & conditions
- The large-archive cases are opt-in. Live pagination over a 60-plus-order archive and
its search truncation (ORD-219, ORD-220) run only with
TIER_C_PAID=1, because every Lab-committed draft queues real notebook work. They skip by default. - The Lab’s production hard-off is proven statically only. ORD-226 would need a mutation invoked against the production deployment, which the programme forbids. The boundary is covered by ORD-108 instead, which asserts the same predicate over the production configuration.
- Cron healing of a stranded effect is not run live. ORD-229 needs a five-minute-plus wait for the reconciliation cron. The reconciliation logic itself is proven at Tier A by ORD-095 and ORD-096.
- Four low-value assertions are deliberately unbuilt. A start-after-end date range (ORD-032), an empty dispensary name (ORD-043), an empty retry list (ORD-101), and a parsed-float NaN already contained downstream (ORD-161) are single validator strings with no user-visible behavior of their own.
- Unbounded stats at 17,000 orders is not simulated. ORD-121 would need a bulk seed script; the bounded behavior that matters is proven by ORD-041b and ORD-041c.
- Notebook writes are quarantined by product decision. With V2 writes off, a notebook effect finalizes shell-only and records a durable marker for a later backfill. ORD-091 asserts that quarantined behavior and flips the moment writes are enabled.
- Deleting an order does not reverse stash inventory. This is intentional, not a missing cascade — the user still physically holds what they bought. ORD-063 pins it.
- Duplicate coverage lives with the primary. The order-to-notebook journey is proven in the notebooks suite, the Gmail-sourced confirm-to-finalize journey in the Gmail suite, and draft orders not counting toward achievements in the achievements suite.
Where to extend
A new Orders behavior gets its test in the tier that can see it: backend logic and validators inapps/mobile/convex/tests/orders.featuretests.test.ts (its .part2 sibling
holds the notebook-effect cases); hook and screen contracts in the co-located
*.featuretests.test.tsx beside the hook, or in
apps/mobile/test/ui/features/OrdersScreens.test.tsx for render states; deployed-backend
journeys in apps/mobile/test/integration/suites/orders.integration.test.ts.
Receipt-parser behavior — the monetary-evidence and cancelled-purchase gates, media-type
detection, determinism — belongs to the API suite at
apps/api/src/services/ai-sdk/receipt-parser.test.ts.
Give the case an ORD- id, add its matrix row, and follow the tier and naming rules on
Mobile Feature Test Platform.