Appearance
/setup Onboarding Gate
The setup gate is a one-time, persisted onboarding wizard shown to the first owner or admin of a new organization. It walks through the org-level providers required to use Postmill.
Data model
Organization.setupCompletedAt DateTime?— null means the wizard has not been completed. Existing organizations were backfilled as complete in the migration that added the column.
Gate rule
The gate is a single client-side predicate over GET /user/self:
ts
const mustSetup = !user.setupCompleted;LayoutComponent (the authenticated app chrome) redirects incomplete users to /setup and renders nothing while redirecting to avoid a chrome flash. /setup lives in its own route segment under apps/frontend/src/app/(app)/setup/ so it is outside LayoutComponent and cannot redirect itself.
Only users who can complete the required LLM step stay in the wizard. Members without owner/admin role are redirected to /dashboard because they cannot save org-level AI provider settings.
Backend surface
GET /user/selfincludessetupCompleted: boolean.POST /settings/setup/complete(auth + org-scoped) callsOrganizationService.completeSetup(orgId).OrganizationService.completeSetuprejects the call with a 400 if the organization has no active LLM provider.OrgAiSettingsService.upsertauto-activates the first saved LLM provider when no active provider exists, so the wizard's LLM step can proceed after a single save.
Wizard steps
The wizard has seven steps. Only the first step is required; the rest can be skipped and configured later in Settings.
- LLM — required. Configure an AI provider (Settings → AI). The first saved provider is auto-activated.
- AI Media — optional. Configure media-generation providers.
- Channels — optional. Connect social channels.
- Content Packs — optional. Configure premium stock providers.
- Storage — optional. Configure S3/R2/B2/IDrive/local storage.
- Shortlinks — optional. Configure a short-link provider.
- VPN — optional. Configure VPN/proxy providers.
Frontend components
apps/frontend/src/components/setup/setup-shell.tsx— blank chrome (logo, theme toggle, avatar menu) and self-fetches/user/selffor auth.apps/frontend/src/components/setup/setup-wizard.tsx— step state, footer, completion handler, and role gate.apps/frontend/src/components/setup/setup-stepper.tsx— responsive stepper header.apps/frontend/src/components/setup/step-frame.tsx— uniform step wrapper.apps/frontend/src/components/setup/steps/step-*.tsx— thin wrappers over existing Settings panels:step-llm.tsxstep-ai-media.tsxstep-channels.tsxstep-content-packs.tsxstep-storage.tsxstep-shortlinks.tsxstep-vpn.tsx
Completion flow
setup-wizard.tsx mutates the /user/self SWR cache to { setupCompleted: true } before calling router.replace('/dashboard'). This prevents the LayoutComponent gate from seeing stale state and bouncing the user back to /setup.
After completion, the org can still change every configured provider from the corresponding Settings tab.
Verified against v1.0.0