Skip to main content

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

A gap listed here is a decision, not coverage. Nothing below is evidence that the behavior works.
  • 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 in apps/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.