Onboarding — Data Model
Onboarding does not own its own table. It composes writes across existing tables. See parent feature docs for column-level detail; this file maps the effect of the setup sheet on the schema.
Rows created by a successful submit
| Order | Table | Row | Source section |
|---|---|---|---|
| 1 | users | (if not yet created by webhook) — findOrCreateFromClerk inside OrganizationsService.create. | implicit |
| 2 | Clerk-side | new organization | org section → handleSubmit |
| 3 | organizations | new row with name, type, platform_tier='lite', clerk_organization_id. | org section → handleSubmit |
| 4 | memberships | new row, role='owner', status='active'. | org section → handleSubmit (in OrganizationsService.create) |
| 5 | legal_consents | one row per type in OWNER_LEGAL_DOC_TYPES (terms_of_use today), context='owner_onboarding'. | legal section → handleSubmit |
| 6 | programs | one row per ticked train tile, delivery_mode = schedule/coaching, name = programNames[mode] (falls back to a per-locale default if blank). Idempotent — a mode with an existing program is skipped. | train section → handleSubmit (onboarding-complete) |
| 7 | class_types | one row, only when a schedule program was just created (its first class type; name defaults unless onboarding-complete was called separately with classTypeName). | train section (indirectly) → handleSubmit |
| 8 | locations | one row, only for a physical/hybrid org whose ticked modes include schedule. | train section (indirectly) → handleSubmit |
| 9 | (only via a separate onboarding-complete call the sheet doesn’t make) | inviteEmails[] → invitations + memberships(pending_invitation). | not exercised by the sheet today |
Persistence of in-progress state
- LocalStorage: key
taikan:setup-sheet-v5, value JSON-serializedOnboardingState<SetupWizardContext>(flowDataplus internal OnboardJS fields; the step id is alwayssheet). TTL 7 days. No server-side copy. Removed on a successful submit. The key’s version suffix bumps whenever the step shape changes, so a half-finished flow from an older shape starts over rather than resuming into an engine that does not know it. A persistedlegalConsentsAccepted=trueis overwritten on load (see behavior). - No DB row persists progress. The org row exists only after
handleSubmitruns. - Setup guide: the dismissed flag is the localStorage key
taikan:getting-started-dismissed. Step completion is derived live from existing tables (class types, plans, memberships, payment config, workouts, programs); nothing is stored for it.
Tour completion (unused)
- Column:
users.guided_tour_completed_at(timestamptz). PATCH /users/me { guidedTourCompletedAt: <ISO> }still writes it. The web app has no guided tour and neither reads nor sends the field.
Multi-org isolation pattern
Onboarding writes that bootstrap a new org are all scoped to the org being created. The owner row in memberships is the first credential that lets the caller read/write the new org’s data through the standard requireMembership path on subsequent requests.
Soft-delete vs hard-delete
N/A — no onboarding-owned data. Org soft-delete is not exposed; legal consents are immutable (revocation creates a new row).