All docs

Onboarding

Onboarding

The first 60 seconds after sign-up are a guided wizard that lands the new user with a working agent token + an MCP config they can paste into Claude Code, Cursor, or Claude Desktop.

State machine

Persisted column: users.onboarding_step — Postgres enum onboarding_step = ('org' | 'repo' | 'agent' | 'client' | 'done') (migration 0015). All transitions implemented in packages/core/src/onboarding/state.ts.

   ┌─────┐    ┌──────┐    ┌───────┐    ┌────────┐    ┌──────┐
   │ org │───▶│ repo │───▶│ agent │───▶│ client │───▶│ done │
   └─────┘    └──────┘    └───────┘    └────────┘    └──────┘
                  ▲                          ▲
                  │                          │
              skippable                  skippable

Enforcement

  • apps/web/app/routes/_app.tsx loader reads users.onboarding_step on

every authenticated app request. Non-done → 302 to /onboarding/<step>.

  • apps/web/app/routes/_marketing._index.tsx applies the same redirect on /

for signed-in users (real drobek_session cookie only — not the dev bypass fallback) so a user mid-wizard cannot land on the marketing homepage. After done, authenticated users with a workspace redirect to /o/<slug>.

  • The apps/web/app/routes/onboarding.tsx layout guards against URL

jumping: visiting /onboarding/agent while still at step org redirects back to org. Visiting an earlier step is allowed (read-only review).

  • Step transitions bump the column inside the same flow as the side

effect (org insert, token mint). A crash mid-flow leaves consistent shape.

Token "show once" invariant

The agent token plaintext is rendered exactly once — at /onboarding/client, right after /onboarding/agent posts. The carrier is a one-shot HMAC-signed, HttpOnly cookie minted by the agent step's action and cleared by the client step's loader in the same response that renders the snippet.

Reloading /onboarding/client after the cookie has been read shows the token-prefix placeholder + a callout pointing the user to Settings → Agents to mint a fresh token. This matches the parity with task 36's same-shape invariant.

Welcome email

After the org step succeeds we fire a welcome email via the shared nodemailer transport (Hostinger SMTP in prod, Mailpit in dev). Body lives in apps/web/app/lib/welcome-email.server.ts. Failure is logged and swallowed — the wizard never blocks on email delivery. Task 45 ships the final MJML template; the firing point + payload contract stays stable.

Funnel telemetry

Each step renders + every advance/skip emits a structured pino event:

{ "event": "onboarding.funnel", "user_id": "…", "onboarding_step": "agent", "action": "view" }

Once the Sentry SDK is wired (see docs/runbooks/observability.md), the same emit point also adds setTag('onboarding_step', step) so the funnel shows up in the Sentry breadcrumb / event search. Until then, log search over event:onboarding.funnel is the source of truth.

Skip rules

  • org and agent are MANDATORY. Skipping them isn't surfaced in the UI.
  • repo skip → user can wire a repo later from Settings → Integrations.

No data dependency; plans can be created without a repo (verification engine falls back to needs_review).

  • client skip → wizard completes without showing the snippet. User can

rediscover the snippet by minting a fresh token from Settings → Agents (the snippet schema is identical).

/.well-known/mcp

Cheap discovery endpoint for MCP clients that want to auto-find a server:

{
  "server": "https://mcp.drobek.app/mcp",
  "version": "1",
  "auth_scheme": "bearer",
  "docs": "https://drobek.app/docs/mcp"
}

Source: [apps/web/app/routes/[.]well-known.mcp.tsx](../apps/web/app/routes/%5B.%5Dwell-known.mcp.tsx). Nothing in the runtime depends on this endpoint — it's future-friendly metadata for clients that read it (the convention is proposed-but-not-yet broadly adopted as of 2026).

See also

component + appshell-screens.css .onboarding* rules.