Skip to main content

What this feature is

Collection is the user’s personal library of strains. From a strain card, a strain page, the browse results, or the home screen, a user can favorite a strain, dislike it, or mark it owned, tasted, or want to try — and attach private notes to any of those. The Collection screen gathers all of it into six tabs with sorting, search, and six card layouts. Once three strains are marked owned or tasted, the user can rank them by head-to-head A/B comparison: pick one of two strains ten times per session, and an ELO rating produces a personal leaderboard. Collection is also an input to other features — it feeds the Safari shopping extension’s slug lists, two achievement tracks, Professor High’s answers, and the Stats tab.

How to read the contract

Every case ID below is a live test. Cases built during the feature-test programme carry their ID in the test title, so grep 'COL-063' finds the assertion. A handful of rows point at coverage that predates the programme; those are cited by ID and file, and the test is found by its plain-English name in that file.

The behavior contract

Favoriting a strain

Disliking a strain

Marking owned, tasted, or want-to-try

Notes and removing a strain

Ranking strains head to head

Browsing the collection

Identifying the right strain

Everything downstream of the collection

What the live tier proves end-to-end

Tier C runs against the deployed staging backend with a brand-new, really-signed-in identity per test, and deletes it afterwards.
  • A favorite survives the full round trip — mutation, deployed validators, and read back through the deployed queries (COL-180).
  • All twenty-three deployed collection queries accept the exact argument shapes the app’s hooks send (COL-181).
  • A complete ranking session runs start → ten comparisons → automatic end against the real deployment (COL-182).
  • The Revyl ranked-collection seed is idempotent on staging and is statically proven to reject production (COL-183).
  • A collection write reaches the achievements engine through the real debounce, not a test-forced flush (COL-184).
  • An order becomes a stash item which becomes an owned collection row, across three features on one deployment (COL-185).
  • One user’s collection is invisible to another, and a mismatched owner key is rejected by the deployed functions rather than only by local code (COL-186, COL-187).

Deliberate gaps & conditions

  • The ten-comparison session cap is advisory. The backend accepts an eleventh comparison; the cap lives only in the three client declarations that COL-091 keeps in agreement. COL-072 pins “advisory only” as the contract as a characterization — if that decision reverses, COL-072 must be rewritten, not deleted.
  • Ranking-session counters have no reader. Nothing in the app reads a session’s completed/skipped counts or its end time, so COL-182 can prove only half of the session contract on a real deployment. Either the columns get a reader or they are dead; the decision is open.
  • Sort choice is deliberately not persisted across mounts. No test asserts it (COL-133 is deferred) because a passing characterization here would only make a future product change look like a regression.
  • There are no bulk operations. Multi-select and bulk delete do not exist in this feature, so nothing in the contract covers them.
  • Curator thresholds, per-type variety counters, and the Professor High favorite path are contracted here but proved in the achievements and users suites, so the same rule is not asserted twice against two copies of the thresholds (COL-161 → ACH-013, COL-163 → ACH-009/ACH-011, COL-167 → CHAT-014).
  • Tier C skips cleanly without credentials, naming exactly what is missing. It is never part of pnpm test, pnpm check, or a cached Turbo run.

Where to extend

A new favorite/dislike/status/ranking rule gets its Tier A case in apps/mobile/convex/tests/collection.featuretests.test.ts; a pure helper rule goes next to its helper as a *.featuretests.test.ts. A new hook argument or screen state gets a Tier B case in the co-located *.featuretests.test.tsx for that hook, or in apps/mobile/test/ui/features/CollectionScreens.test.tsx / CollectionRankings.test.tsx for rendering. Only reach for apps/mobile/test/integration/suites/collection.integration.test.ts when the claim is about the deployed backend. Then add the row to the master matrix — see Mobile Feature Test Platform for naming, tags, and the tier decision.