Skip to main content

What this feature is

A notebook is the document High IQ builds about what a person actually bought and smoked. It is assembled from their own recorded rows — an order’s line items, or a hand-picked set of stash jars — enriched with an allowlisted read of the public strain database. One is created automatically when a Pro member commits an order with the notebook toggle on, and one can be requested by hand from Order Detail or by picking jars from the stash. While a notebook builds, the person watches section-by-section progress and can cancel; if it fails they can retry the whole thing or just the failed sections, and each attempt publishes a new, immutable revision without destroying the previous one. Every notebook carries a free-tier journal — a per-strain rating, a note, and photos — and can be shared, archived, or deleted from its detail screen. Daily Stories is a notebook-adjacent surface that is deliberately switched off end to end; it is specified here as a quarantine, not as a feature.
The live generation pipeline contains no AI calls. A notebook is a deterministic, template-assembled document: Convex prepareAndStartReportV2POST /api/v1/notebooks/report-v2/source-batch (a research preflight, no model) → the Trigger.dev notebook-report-v2 task → a Convex webhook that promotes the result. Model enrichment is pinned off in three independent places. Tier C here proves a distributed deterministic pipeline, not model output.

The behavior contract

Creating a notebook from an order

Creating a notebook from stash

Who may generate, and how often

Watching a notebook build

Failure, retry, and revisions

The notebooks list and the archive

Opening, sharing, archiving, deleting

Journal and photos

Isolation, retirement, and the schema surface

Daily Stories — quarantined by design

What the live tier proves end-to-end

Tier C signs in as a real Clerk identity, drives the deployed staging Convex deployment and the production Hono API, waits on real Trigger.dev runs, and cleans up after itself.
  • A stash notebook completes the whole pipeline to ready, and the research layer really arrives — the shared run every other case reads from, guarding against a silently degraded notebook that looks fine and contains nothing (NB-200, NB-201).
  • Committing an order auto-generates a notebook that reaches ready, and one whose strains are still being researched waits and then proceeds rather than failing (NB-202, NB-203).
  • Manual order generation works end to end, and a free account is denied at the server for every generation entry point with the enforcement state asserted first so the case cannot pass vacuously (NB-204, NB-205).
  • Double-tapping generate yields exactly one notebook and one Trigger run, cancelling mid-generation confirms and leaves no hung run, and a cancel racing the terminal callback resolves to exactly one winner (NB-207, NB-208, NB-209).
  • Retrying a failed notebook produces a new revision while the previous publication stays intact, and upgrading an archive publishes V2 while keeping the archive readable (NB-210, NB-213).
  • Journal and photo round-trips work against the real backend — including a real JPEG through the .convex.site route, a PNG mislabelled as JPEG being rejected, and the photo cap enforced against the real quota tables (NB-220, NB-221, NB-222, NB-223).
  • Deleting a notebook cascades on staging down to the storage blob returning 404, and cross-user isolation, unauthenticated rejection, and validator-drift replay all hold on the deployed validators (NB-224, NB-231, NB-232, NB-234).
  • The retired surfaces stay retired on the real hosts: the archived SSE route returns 410, the /api/v1/reports/* alias behaves identically, source-batch answers a missing internal secret with 404 rather than 401, every planned artifact reports blocked, and the daily-stories cron and webhook both produce nothing (NB-214, NB-216, NB-217, NB-218, NB-228, NB-229, NB-230).

Deliberate gaps & conditions

Daily Stories is withheld on purpose, on both sides. There is no producer: the nightly cron returns daily-story-executor-unselected and no story-insert path exists outside test seeding. The server quarantine is one named constant (DAILY_STORY_CUSTOMER_VISIBILITY_ENABLED = false as const) and the client flag is hard-disabled to match, so the header strip, calendar, viewer and deep link are invisible rather than live-and-permanently-empty. NB-229 asserts the guarantee that follows: a story seeded directly into the table as owned and ready is still withheld from every customer read. The two halves must be lifted in the same change — lifting one alone reproduces the shipped defect — and a coupling test pins exactly that. NB-229 is a plain passing test, not a pin; whoever lifts the quarantine has to rewrite it. Media artifacts are structurally unreachable, and that is the specified state. The artifact registry’s only entry is a candidate, and executor selection filters on selected, so every planned artifact is immediately blocked. The artifact routes are separately quarantined in the client and their CTAs are now hidden rather than dead-ending. The ledger’s executorTaskId is deliberately kept: it is read as executor identity by the anti-forgery check, so removing it would silently disarm the guard. Two cases cannot be written as specified. One case is blocked, not deferred. NB-212 (paid work refunded when the research preflight fails) would require temporarily invalidating the internal preflight secret on shared staging, which would break every other suite running at the time. Tier A NB-034 covers the same contract deterministically. Two cases are proven in another area’s suite. Order-commit paywall behavior is owned by the Orders suite (NB-042 → ORD-074), and the notebook-view achievement projection by the Achievements Tier C suite (NB-225 → ACH-145). Build only the primary.

Where to extend

A new backend rule — a validator, an ownership check, a scheduled effect — gets a case in apps/mobile/convex/tests/notebooks.featuretests.test.ts, or notebooks.featuretests.part2.test.ts if it touches generation, paid work, or promotion. A new hook argument or screen state gets a co-located *.featuretests.test.tsx next to its source, or a jest-expo suite under apps/mobile/test/ui/features/. Anything that only fails on a real deployment — JWT verification, an outbound fetch from an action, pipeline completion — goes in apps/mobile/test/integration/suites/notebooks.integration.test.ts and must be scoped to its own test user. See Mobile Feature Test Platform for tier selection, naming, and the Tier C isolation rules.