Skip to main content

What this feature is

This is everything that makes the app someone’s. A person signs up or signs in with an email code, Apple, or Google; a Convex user record is created for them behind the scenes; and the app decides on launch whether to show them onboarding or drop them straight home. From there they edit their profile, set consumption preferences and a theme, and see whether they are on the free tier or Pro — which is what opens Professor High, notebook generation, receipt parsing and the Shopping Agent. They can chat with Professor High, which reads their own stash, orders and collection to answer. And they own the exit: Export All Data writes every row the app holds about them to a file, and Delete Account removes all of it — Convex rows, uploaded files, connected Gmail access, and the Clerk identity itself.

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 'SUB-029' finds the assertion. Rows whose coverage predates the programme are cited by ID and file; the test is found by its plain-English name in that file. A few behaviors landed in wave 3 under a defect id — those cite BUG-nnn, which is likewise the literal prefix of the test title.

The behavior contract

Signing up and signing in

Getting a user record

First run and onboarding

Profile

Preferences and theme

Knowing whether you are Pro

Syncing entitlement from the store

The store webhook

The server-side Pro gate

Feature flags

Professor High

Exporting your data

Deleting your account

Daily Stories

Daily Stories is quarantined by design; its full contract lives on the Notebooks & Daily Stories page. These rows are the account-side half.

Everything an account owns

What the live tier proves end-to-end

Tier C signs in as a real, disposable +clerk_test identity against the deployed staging Convex deployment and the production Hono API, then destroys the identity.
  • Enforcement is measured before anything is asserted against it — the suite records whether staging is enforcing the subscription gate, so no paywall result can pass for the wrong reason (SUB-044, SUB-100).
  • A brand-new identity really is new: it receives a Convex JWT, calls an authenticated query, has no data anywhere, and its lifetime stats are fully zeroed (AUTH-012, ONB-010, PRF-007).
  • Authentication holds on the deployed backend — every hard-auth function rejects an unauthenticated client, and the soft-auth queries return their guest shape rather than erroring (AUTH-013, AUTH-014).
  • All three subscription states behave on the real deployment, and so do both transitions: free closes every gate, active Pro opens every gate, a lapsed account closes them again exactly as a free one does, and a past expiry demotes a stored Pro tier (SUB-039–SUB-041, SUB-101, SUB-106, SUB-112, SUB-113, SUB-114).
  • The deployed API answers 402 for a free identity and streams for a Pro one, the entitlement endpoint’s projection agrees with both, and one real paid receipt parse completes end to end (SUB-042, SUB-043, SUB-105, SUB-109, SUB-110).
  • Preferences, feature interests and chat sessions round-trip against the deployed validators, an oversized transcript is refused by the real backend, and a Professor High turn really streams and really persists (ONB-012, CHAT-028, CHAT-029, CHAT-031).
  • A full export of a seeded identity works: one snapshot, all its pages, a clean finish, the seeded rows present, no foreign rows, and the app’s section list matching what the deployed backend actually pages (EXP-014, EXP-015, EXP-016).
  • Account deletion completes for real, through identity death — the job reaches completed, every user-owned query afterwards returns empty, the Clerk identity is gone, and an export cannot be started while the deletion is live (DEL-022, DEL-023, DEL-024, DEL-026).
  • One disposable identity runs the whole lifecycle in order — create, onboard, set preferences, register interest, chat, export, delete (X-001).

Deliberate gaps & conditions

  • Six live cases are blocked on three webhook secrets that only John can set on the staging deployment. Until they are present these skip with their reason named, which is not a pass:
    • CLERK_WEBHOOK_SECRET, plus pointing the Clerk development instance at the staging webhook URL → USR-012, USR-013, DEL-025. Without it, no user record is created on a real staging sign-up, and the webhook deletion backstop never fires.
    • REVENUECAT_WEBHOOK_SECRET → SUB-045. The store-webhook normalizer has no live coverage, and its transfer handling changed most recently.
    • PROFESSOR_HIGH_WEBHOOK_SECRET → PREF-012, CHAT-030. The server-side assistant context path has never run against a real deployment.
  • Remote feature-flag delivery is not proven on staging. FLAG-007 would require writing a flag on a deployment every other suite shares, so override precedence is proven at Tier A and B only.
  • Two chat behaviors are deliberately unbuilt at the live tier: a tool call depends on the model choosing to make one (CHAT-032), and proving the per-user chat rate limit costs twenty-one real AI requests (CHAT-033).
  • Two internal diagnostics are not asserted as cases: the internal data-count query has no user-facing surface (USR-011), and the dead hasUsedFreeNotebook field was resolved by removing it — the surviving pin asserts the field is gone from the schema and has no consumers, in usersSchemaStatic.test.ts (X-004).
  • Two daily-story behaviors are proven in the notebooks suite rather than twice: the view-streak read (STORY-011 → NB-091) and the staging cron producing nothing (STORY-018 → NB-228).
  • Theme, onboarding completion and the mini-player preference are device-local. They live in local key-value storage and have no backend, so nothing about them is asserted against a deployment — and onboarding completion is deliberately keyed per Clerk subject so sharing a device does not share it.
  • Sign-up on staging currently creates no user record. Because the Clerk webhook secret is absent, USR-012 polls for the row rather than asserting it: that case is the detector for the configuration gap, not a product test.
  • Tier C skips cleanly without credentials, naming exactly what is missing, and is never part of pnpm test, pnpm check, or a cached Turbo run.

Where to extend

A new account, preference, subscription, flag or chat rule gets its Tier A case in apps/mobile/convex/tests/users.featuretests.test.ts — or users.featuretests.part2.test.ts for deletion, export, chat and daily-story rules, which is where that half of the area lives. A new hook-argument or screen-state rule goes in the co-located *.featuretests.test.ts(x) next to its hook (useSubscription, useConsumptionPreferences, useDataExport, use-chat-engine, featureFlags) or in apps/mobile/test/ui/features/Users*.test.tsx for rendering. Reach for apps/mobile/test/integration/suites/users.integration.test.ts — or subscriptions.integration.test.ts for anything about the paywall — only when the claim is about the deployed backend, and remember that deletion is destructive: it must be the last thing an identity does. Then add the row to the master matrix; see Mobile Feature Test Platform for naming, tags, and the tier decision.