What this feature is
The stash is the shelf: what a person currently owns, in the units they own it in. Jars arrive three ways — added by hand from a strain page, added in bulk from an order’s line items, or merged in automatically when an order is confirmed. Each jar is a line keyed by strain, product type and unit, so a second eighth of the same flower tops up the existing line while a cartridge of the same strain stays separate. From the Stash tab a person adjusts quantities, marks a jar empty when they have finished it, archives one they no longer want to track, or permanently deletes a hand-entered mistake; the same actions work across a multi-select. Every one of those writes lands in a searchable history timeline, and the timeline plus the current shelf feed the Stats cards — consumption velocity, restock prediction, cost per gram, and the stash hero forecast.The behavior contract
Adding a jar
Adding a whole order’s worth at once
Changing quantities
Consuming
Emptying, archiving, and permanent deletion
Bulk actions from the selection bar
Reading the shelf
History and the timeline
Stats cards and forecasts
The order-to-stash merge contract
Propagation to the rest of the app
Ownership, export, and deletion
Scale and concurrency
What the live tier proves end-to-end
Tier C signs in as a real Clerk identity against the deployed staging Convex deployment and the production Hono API, then cleans up after itself.- An app-shaped add round-trips through the deployed backend, and the deployed validators accept the argument shapes all five write mutations are called with from the app (STASH-300, STASH-301).
- The real scheduler runs: an add lands its
stash_addedhistory row, and syncs the strain into the collection as owned (STASH-302, STASH-303). - Strain enrichment resolves against the real Hono API for both the slug path and the name path (STASH-307).
- Every layer reports the same grams for the same shelf — stash stats, lifetime stats and the restock forecast agree on the deployed data, including millilitres (STASH-308).
- A large real archive pages to exhaustion without duplicates or gaps, and a server-side search finds a match that lies beyond the first page (STASH-309, STASH-310).
- Cross-user isolation holds on the real deployment — one account is refused another’s stash rows (STASH-313).
- The teardown contract works: the fixture reset returns a seeded test account to zero remaining rows, which is what makes the rest of the suite repeatable (STASH-312).
Deliberate gaps & conditions
Two rows are deliberately not built.
Four Tier C rows are proven in another area’s suite and must not be rebuilt
here: achievement metrics incrementing after an add (STASH-304 → the achievements
suite), the order-confirm merge end to end (STASH-305 → the orders suite), a stash
notebook accepting
sourceStashItemIds (STASH-306 → the notebooks suite), and
extension slugs matching the shelf (STASH-314 → the dispensaries suite). The
order-merge idempotency row is likewise owned by the order post-save suite
(STASH-133).
A 500-id bulk retire is a recorded ceiling, not an endorsement. STASH-052 exists
to document that no array cap is enforced on that path today, unlike permanent
deletion which caps at 50. If a cap is added, that case is where it gets asserted.
Where to extend
A new backend rule — a validator, an ownership check, an event write — gets a case inapps/mobile/convex/tests/stash.featuretests.test.ts for the write surface, or
stash.featuretests.part2.test.ts for history, analytics, propagation and scale. A
new hook argument or optimistic update gets a co-located *.featuretests.test.ts(x)
next to its source; a new screen state gets a jest-expo suite under
apps/mobile/test/ui/features/. Anything that only fails against a real deployment —
a validator drift, the scheduler actually firing, pagination over a real archive —
goes in apps/mobile/test/integration/suites/stash.integration.test.ts, scoped to its
own test user and cleaned up with the fixture reset.
See Mobile Feature Test Platform for tier
selection, naming, and the Tier C isolation rules.