Milestone 4: Onboarding and tenant settings
Purpose: New tenants complete the onboarding wizard; admins can edit org settings; feature flags are available for gating.
Exit state: Onboarding wizard (org → first inbox → invite optional → done); Settings → General (org name, slug, timezone, danger zone); feature flags DB and config loader.
Spec reference: §2.1 Tenant onboarding wizard, §2.9 Tenant settings, §2.9 Feature flags, §5.19 FeatureFlag, §5.21b FeatureFlagOverride, §6.2 Onboarding wizard, §6.10 Onboarding, settings/general.
Prerequisites: M03 (App shell). First inbox step in wizard can stub connection (M05) or link to connect flow.
4.1 Onboarding wizard
Tasks
-
Wizard route and layout
/app/[slug]/onboarding: full-screen or prominent layout (no sidebar or minimal) so user focuses on steps.- Step indicator (e.g. 1/4, 2/4). Back / Next; persist current step (e.g. in tenant or session so refresh resumes).
-
Step 1: Welcome / org confirm
- Show tenant name and slug (editable or read-only from signup). User confirms or edits; validate slug unique and URL-safe.
- On Next: update tenant name/slug if changed; move to step 2.
-
Step 2: Connect first inbox
- Option A: Embed connect-inbox flow (M05) — choose type (IMAP, Gmail, Microsoft), then OAuth or form, test connection, save. On success → step 3.
- Option B (stub): "Connect your inbox" CTA that links to Settings → Inboxes → Add, or show placeholder "You can connect an inbox in Settings"; Skip or Next to step 3.
- Spec: "Connect your first inbox" is required in wizard; recommend implementing minimal connect (at least one type) or explicit Skip for M04 and require inbox in M05.
-
Step 3: Invite team (optional)
- Form: add one or more emails with role (admin/agent/viewer). "Send invites" or "Skip".
- If Skip: go to step 4. If Send: create invitation rows, send invite emails (Postmark template; M09 implements full invite flow); then step 4.
- For M04, invite creation can be minimal (invitations table + send one email per invite); full accept flow in M09.
-
Step 4: Done
- Summary: org name, inbox count, invites sent. CTA "Go to inbox" or "Go to tickets".
- On CTA: set
tenants.onboarding_completed_at = now(); redirect to/app/[slug]/tickets.
-
Persistence and redirect
- If user exits mid-wizard, next visit to
/app/[slug]/**(middleware M02) redirects back to/app/[slug]/onboardinguntil completed. - Store step in tenant (e.g.
onboarding_stepif added to schema) or in session so "Next" and "Back" and refresh work.
- If user exits mid-wizard, next visit to
Acceptance criteria
| Criterion | Status |
|---|---|
New tenant (from M02 signup) lands on /app/[slug]/onboarding. | ✅ |
| Steps 1–4 are visible and navigable (Back/Next); step 2 either connects inbox or skips; step 3 invites or skips. | ✅ |
Completing step 4 sets onboarding_completed_at and redirects to /app/[slug]/tickets. | ✅ |
| If user leaves and returns before completing, middleware redirects to onboarding again. | ✅ |
After completion, visiting /app/[slug]/onboarding redirects to tickets (or show "Already completed" and link to tickets). | ✅ |
4.2 Settings → General
Tasks
-
Settings layout
/app/[slug]/settings/layout.tsx: settings nav (General, Inboxes, Team, Billing, SLA, Snippets, Webhooks, API, SSO, Audit). Only General and optionally placeholders for others in M04.- Content area: render child route (General = form).
-
General form
- Fields: Organization name, Slug, Timezone (searchable select or combobox).
- Validation: slug unique, URL-safe; name non-empty. Save via tRPC; update
tenantsrow. - Success: toast or inline message. Slug change warning: "The previous URL will stop working; notify your team."
-
Danger zone
- Section: "Delete organization". Button opens confirmation (AlertDialog); require typing org name to confirm (Section 10.10). On confirm: delete tenant and related data (cascade or explicit); cancel Stripe if any; redirect to
/no-workspaceor/login. Defer full delete implementation to M10 if billing not yet integrated; document "blocked until M10" or implement soft delete only for M04.
- Section: "Delete organization". Button opens confirmation (AlertDialog); require typing org name to confirm (Section 10.10). On confirm: delete tenant and related data (cascade or explicit); cancel Stripe if any; redirect to
Acceptance criteria
| Criterion | Status |
|---|---|
| Admin can open Settings → General and edit org name, slug, timezone; save persists to DB. | ✅ |
| Slug change shows warning; after save, new slug works and old slug 404s for app routes. | ✅ |
| Danger zone visible; delete either implemented (with Stripe cancel when in M10) or documented as coming in M10. | ✅ |
4.3 Feature flags
Tasks
-
Schema
feature_flag_globals(flag_key PK, enabled, description) — global defaults.feature_flags(tenant_id, flag_key, enabled) — per-tenant overrides (unique tenant_id, flag_key).
-
Config loader
getFeatureFlag(flagKey, tenantId)inapps/web/lib/get-feature-flag.ts: override for tenant else global else false. Server-only (uses getDb). Can be moved topackages/configlater for worker/tRPC.
-
Seed
- Migration inserts
new_ticket_ui= false. Settings → Feature flags UI lists known flags + per-tenant toggles (overrides).
- Migration inserts
Acceptance criteria
| Criterion | Status |
|---|---|
| Config/loader returns correct value for a flag (global and, if overrides exist, tenant override). | ✅ |
| tRPC or layout can read flag and conditionally show/hide or enable/disable a feature. | ✅ (server component + getFeatureFlag on tickets page) |
| At least one flag exists in DB and is read in code path (e.g. hide a placeholder section when flag off). | ✅ (new_ticket_ui in feature_flag_globals; tickets page shows beta line when on) |
Milestone 4 sign-off
| Criterion | Status |
|---|---|
| All tasks in 4.1–4.3 complete. | ✅ |
| All acceptance criteria met. | ✅ |
| New tenant completes onboarding; admin can edit General settings; feature flags usable in code. | ✅ |
| E2E: Onboarding and Settings smoke tests (see INDEX — Testing strategy). | ✅ |
| Ready for M05 (Inbox management). | ✅ |