ubiquitous-invention/plans/Plan-daily-driver-finish/Epic-shipping-the-shell/Task-onboarding-zero-workspace-flow.md
Randall Stillwell 7ec2ede7ca feat(web): land authed users on their oldest workspace home by slug
Path-A task 4/5. The root landing logic in apps/web/app/page.tsx was
redirecting to `/${workspaceId}` (UUID, ugly) and using no ORDER BY
(so two sessions could land on different workspaces). It also looped
zero-workspace users through `/sign-in`.

Changes:

* Inner-join workspaceMembers with workspaces to fetch the slug, not
  just the id. Order by membership createdAt ascending so users
  consistently hit their oldest workspace.
* Redirect to /{slug} (slug, not UUID).
* Removed the unused `objects` / `and` imports that were lint
  warnings.
* Zero-workspace branch redirects to /sign-in?error=no_workspace as a
  defensive fallback; documented inline that this is unreachable for
  fresh sign-ins post `ensureUserHasWorkspace` in apps/web/lib/auth.ts.

The dashboard at /{slug}/ is no longer a mockup (post commit f64d307
which wired it to objects.stats and objects.listRecent), so landing
there now shows real state.

Filed plans/Plan-daily-driver-finish/Epic-shipping-the-shell/
Task-onboarding-zero-workspace-flow.md (P2) as the follow-up that
turns the defensive fallback into a proper welcome flow with a
shared workspace-provisioning helper.

`pnpm lint && pnpm type-check` clean. Closes
plans/Plan-daily-driver-finish/Epic-shipping-the-shell/
Task-pick-workspace-landing-route.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 00:25:48 -05:00

3.2 KiB

kind slug title plan_slug epic_slug status priority tenant_id owner cursor_todo_id updated_at
task onboarding-zero-workspace-flow Build a proper onboarding flow for users with zero workspaces daily-driver-finish shipping-the-shell ready P2 global unassigned null 2026-06-02

Task summary

When a signed-in user has no workspace membership, apps/web/app/page.tsx currently bounces them through /sign-in?error=no_workspace. This is a defensive fallback, not a user experience. Build a proper onboarding flow.

Description

Why this exists

apps/web/lib/auth.ts's ensureUserHasWorkspace provisions a personal workspace on first sign-in, which means the zero-workspace state should be effectively unreachable for new sign-ins. However:

  • Existing JWTs minted before that provisioning logic was added do not trigger ensureUserHasWorkspace on refresh; only on a true sign-in event.
  • A user could conceivably leave or be removed from every workspace they belonged to (no UI for this yet, but the data model allows it).
  • A workspace could be hard-deleted out from under a user (no UI yet, but again — data-model-allowed).

The current behavior — redirect to /sign-in?error=no_workspace — is acceptable as a hidden defensive branch but is jarring for any user who hits it. We want a deliberate flow.

The flow

  1. New route: apps/web/app/(onboarding)/welcome/page.tsx. Server Component.
  2. If user is signed in AND has zero memberships, render a one-step form: "Name your workspace". Default to ${displayName}'s workspace.
  3. Form submit calls a server action that wraps ensureUserHasWorkspace (lift it out of auth.ts into apps/web/server/lib/provision-workspace.ts so both the JWT callback and the server action share the same code).
  4. On success, redirect to /{newSlug}.
  5. apps/web/app/page.tsx redirects the zero-membership case to /welcome instead of /sign-in?error=no_workspace.

Out of scope

  • Multi-workspace flow (joining an existing workspace by invite). Belongs to Plan-multitenant-saas-hardening/Task-workspace-invites-and-roles.
  • Workspace switcher UI. Separate task; not blocking onboarding.

Subtasks

  • Extract ensureUserHasWorkspace and slugifyForWorkspace from apps/web/lib/auth.ts into apps/web/server/lib/provision-workspace.ts. Update auth.ts to import from there.
  • Add apps/web/app/(onboarding)/welcome/page.tsx with the one-step form.
  • Add the server action that calls the shared provisioner.
  • Update apps/web/app/page.tsx to redirect zero-membership users to /welcome instead of /sign-in.

Owner or assignee

Unassigned

Status

ready

Estimation

S

Acceptance criteria

  • Signing in with a session that has no membership shows the welcome form, not a sign-in loop.
  • Submitting the form creates a workspace and lands the user on its home page.
  • The provisioning logic is shared between the JWT callback (first sign-in) and the welcome flow (recovery).
  • Epic: ./Epic-shipping-the-shell.md
  • Plan: ../Plan-daily-driver-finish.md
  • Related: ../../Plan-multitenant-saas-hardening/Epic-tenant-lifecycle/Task-workspace-invites-and-roles.md