Skip to content

feat(onboarding): converge home onto /setup at the derived step (chat#1889 item 1, part 2) - #1890

Merged
sweetmantech merged 1 commit into
mainfrom
feat/setup-canonical-sequence
Jul 28, 2026
Merged

feat(onboarding): converge home onto /setup at the derived step (chat#1889 item 1, part 2)#1890
sweetmantech merged 1 commit into
mainfrom
feat/setup-canonical-sequence

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Part 2 of item 1 in chat#1889 — the keystone. Part 1 is #1887 (repoint referrers).

Why

Traced on main: 4 entry paths land on 3 parallel implementations of the same onboarding steps.

Entry Lands on
Marketing valuation flow → /catalogs/{id}CatalogReportCta /onboarding/first-task
Valuation email same
Welcome email /setup/* (the interactive steps)
Direct to chat + every marketing CTA (17 bare appUrl refs) home's card-based OnboardingSequence

Home's cards only linked out to the generic app pages (/artists, /artists, /catalogs, /tasks), so a direct signup never reached the interactive steps the welcome email's /setup/* links do. getOnboardingStepContent's own docstring admitted it: "Placeholder card copy … until then the card links to the existing page where the step can be completed today" — scaffolding from #1867 that was never replaced after ConfirmRosterStep / VerifySocialsStep / FirstTaskStep shipped.

Consequence: every setup-flow fix (Spotify typeahead, delete-social, skeleton, valuation reward) currently reaches only email-entry users. This PR is the multiplier on all of them.

What changed

  • HomePage forwards an incomplete account to /setup rather than hosting a second sequence. The skipped/pinned-checklist path is unchanged.
  • /setup opens at the derived step — new SetupEntry + getSetupPathForStep. It previously hard-redirected to /setup/artists, so a signup who had already confirmed their roster was sent back to step 1. Renders a skeleton until every checkpoint source resolves, so a slow read can't flash the wrong step.
  • Deleted the placeholder surface: OnboardingSequence, OnboardingStepCard, getOnboardingStepContent (+ its test).
  • OnboardingCheckpointList reads titles from new getOnboardingStepTitle (the checklist was the only remaining consumer of the deleted card copy).
  • SetupSkipLink carries the gate's "skip for now" escape hatch onto the /setup surface — without it, forwarding home would turn a deliberately soft gate into a wall. Mounted by RosterSocialsFlow and FirstTaskStep.

Deliberately not in scope: RosterSocialsFlow's local "Step 1 of 2" counter (part 3) and the welcome email's step list (part 4).

Verification

TDD, red → green on both new units:

```

getSetupPathForStep — RED (module not found), then

✓ lib/onboarding/tests/getSetupPathForStep.test.ts (3 tests) 4ms

getOnboardingStepTitle — RED (module not found), then

✓ lib/onboarding/tests/getOnboardingStepTitle.test.ts (2 tests) 3ms
```

HomePage.onboarding.test.tsx was rewritten to assert the new contract (forwards to /setup; renders no competing step surface; skip still yields the pinned checklist and does not forward; Continue re-arms the gate).

Full onboarding + home suites:

```
pnpm exec vitest run components/Home lib/onboarding hooks/tests/useOnboardingSessionFlags.test.tsx
Test Files 20 passed (20)
Tests 80 passed (80)
```

pnpm exec tsc --noEmit clean for the touched files. Preview click-through pending.

Merge order

After #1887 (no file overlap, so either order is safe, but #1887 removes the funnel regression first). Unblocks #1869 (catalog-is-home).

Tracked in chat#1889 (matrix row 5).


Summary by cubic

Unifies onboarding into a single /setup flow that opens at the user’s derived step. Home now forwards incomplete accounts to /setup, keeping the gate soft with a skip link (chat#1889).

  • New Features

    • Added SetupEntry to resolve the derived step and route via getSetupPathForStep, showing a short skeleton while loading.
    • Home forwards to /setup instead of hosting its own sequence; skip flags the session and returns to home with the checklist pinned via SetupSkipLink (mounted in RosterSocialsFlow and FirstTaskStep).
    • OnboardingCheckpointList now uses getOnboardingStepTitle; added unit tests for the new utilities and updated Home tests.
  • Refactors

    • Removed the placeholder surface: OnboardingSequence, OnboardingStepCard, and getOnboardingStepContent (and its test).
    • /setup mounts SetupEntry instead of hard-redirecting to /setup/artists; HomePage replaces the local sequence with a redirect to /setup for incomplete accounts.

Written for commit 1a62472. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added centralized setup routing that resumes users at the appropriate onboarding step.
    • Added “Skip for now” options during setup, returning users to the home page.
    • Added consistent onboarding checkpoint titles.
  • Improvements

    • Users with incomplete onboarding are now directed into the setup flow instead of seeing it embedded on the home page.
    • Added a loading state while the correct setup step is determined.

@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 28, 2026 2:52am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 41779170-458f-4e37-9fc0-366d00d2f73d

📥 Commits

Reviewing files that changed from the base of the PR and between bbd5165 and 1a62472.

⛔ Files ignored due to path filters (4)
  • components/Home/__tests__/HomePage.onboarding.test.tsx is excluded by !**/*.test.* and included by components/**
  • lib/onboarding/__tests__/getOnboardingStepContent.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/onboarding/__tests__/getOnboardingStepTitle.test.ts is excluded by !**/*.test.* and included by lib/**
  • lib/onboarding/__tests__/getSetupPathForStep.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (12)
  • app/setup/page.tsx
  • components/Home/HomePage.tsx
  • components/Onboarding/FirstTaskStep.tsx
  • components/Onboarding/OnboardingCheckpointList.tsx
  • components/Onboarding/OnboardingSequence.tsx
  • components/Onboarding/OnboardingStepCard.tsx
  • components/Onboarding/RosterSocialsFlow.tsx
  • components/Onboarding/SetupEntry.tsx
  • components/Onboarding/SetupSkipLink.tsx
  • lib/onboarding/getOnboardingStepContent.ts
  • lib/onboarding/getOnboardingStepTitle.ts
  • lib/onboarding/getSetupPathForStep.ts
💤 Files with no reviewable changes (3)
  • components/Onboarding/OnboardingStepCard.tsx
  • components/Onboarding/OnboardingSequence.tsx
  • lib/onboarding/getOnboardingStepContent.ts

📝 Walkthrough

Walkthrough

Onboarding now enters /setup through client-side state resolution, redirects incomplete home sessions to their derived setup step, and adds skip controls to setup flows. Checkpoint titles use a dedicated mapping, while the previous sequence and step-card path is removed.

Changes

Setup routing and derived-step resolution

Layer / File(s) Summary
Canonical setup entry gate
app/setup/page.tsx, components/Onboarding/SetupEntry.tsx, lib/onboarding/getSetupPathForStep.ts
/setup mounts SetupEntry, which waits for onboarding readiness and redirects to the derived step’s canonical route.
Home onboarding redirect
components/Home/HomePage.tsx
Incomplete sequence onboarding now uses router.replace("/setup") instead of rendering the removed in-place sequence.
Setup skip controls
components/Onboarding/SetupSkipLink.tsx, components/Onboarding/FirstTaskStep.tsx, components/Onboarding/RosterSocialsFlow.tsx
Setup steps expose a skip action that records the session flag and navigates to /.
Checkpoint title mapping
lib/onboarding/getOnboardingStepTitle.ts, components/Onboarding/OnboardingCheckpointList.tsx
Checkpoint headings now come from a dedicated step-title mapping rather than shared step-card content.
Legacy sequence cleanup
components/Onboarding/OnboardingSequence.tsx, components/Onboarding/OnboardingStepCard.tsx, lib/onboarding/getOnboardingStepContent.ts
The in-place sequence, step-card component, and shared step-card content module are removed.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HomePage
  participant SetupEntry
  participant OnboardingState
  participant Router
  HomePage->>Router: replace /setup for incomplete onboarding
  SetupEntry->>OnboardingState: read ready state and derived step
  SetupEntry->>Router: replace canonical setup route
Loading

Possibly related PRs

Poem

Steps align beneath the gate,
Home now sends them through the slate.
Skip flags flutter, routes turn bright,
Titles guide the checklist right.
/setup finds the path in sight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning HomePage (38 lines), FirstTaskStep (74), and RosterSocialsFlow (25) exceed the 20-line guideline and mix routing/loading/UI concerns, so SRP/Clean Code isn't met. Split HomePage and FirstTaskStep into smaller components/helpers; extract routing effects and state branches so each function stays focused and under ~20 lines.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/setup-canonical-sequence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

<NewChatBootstrap initialMessages={initialMessages} />

P2 Badge Avoid bootstrapping chat while redirecting to setup

When onboarding sources are already cached and resolved on entry to /, shouldEnterSetup is true on the first render, yet this still mounts NewChatBootstrap before the replacement navigation runs. Its mount effect starts the non-idempotent POST /api/sessions and POST /api/sandbox provisioning sequence, and the route change does not cancel those requests, leaving an unused session and sandbox for each such home visit. Keep the chat bootstrap unmounted while forwarding to setup.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const SETUP_PATHS: Record<OnboardingStepId, string> = {
artists: "/setup/artists",
socials: "/setup/socials",
catalog: "/setup/catalog",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the skip control on the catalog destination

When the derived step is catalog, this sends an incomplete home visitor to /setup/catalog, but app/setup/catalog/page.tsx immediately redirects to /catalogs, where no SetupSkipLink is mounted. This removes the former sequence's escape hatch specifically for accounts without a catalog; /catalogs only displays “No catalogs found” for that state, and returning home starts the redirect loop again. Route this step to a surface that includes the skip control or add an equivalent escape hatch to the catalog destination.

Useful? React with 👍 / 👎.

const SetupSkipLink = () => {
const router = useRouter();
const { userData } = useUserProvider();
const { skip } = useOnboardingSessionFlags(userData?.account_id ?? "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for an account ID before enabling skip

On a direct /setup/artists, /setup/socials, or /setup/tasks visit, this button renders while UserProvider may still be resolving userData. If the user clicks it then, the flag is written under the empty account key; after navigation, the home gate switches to the real account key, reads false, and forwards the user back into setup. Hide or disable the control until account_id is available rather than recording a skip for "".

Useful? React with 👍 / 👎.

Comment on lines +21 to +22
skip();
router.push("/");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve skip when session storage is unavailable

When sessionStorage is denied or throws, skip() only updates this component's in-memory state because writeOnboardingFlag deliberately catches storage failures. The immediate navigation then unmounts that state, so the newly mounted home gate reads skipped: false and redirects straight back to setup, making the advertised soft gate unskippable in that supported failure mode. Carry the in-memory decision across navigation or provide a fallback that does not depend on the failed write.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 16 files

Confidence score: 2/5

  • In lib/onboarding/getSetupPathForStep.ts, the catalog route appears to bypass the skip-capable flow used by other setup steps, which can trap users in setup with no equivalent escape path; align catalog with a component/path that includes SetupSkipLink behavior.
  • In components/Onboarding/SetupEntry.tsx, a failed or unavailable checkpoint fetch can leave /setup stuck on a loading skeleton indefinitely, effectively blocking onboarding progress; add an error/timeout fallback with a retry or alternate navigation path.
  • In components/Onboarding/SetupSkipLink.tsx, skip persistence is fragile: clicks before account resolution write under an empty id, and storage write failures fall back to memory-only state, both of which can cause users to be redirected back into setup after navigation; gate skip writes on a resolved account id and add a durable fallback strategy when sessionStorage is unavailable.
  • In components/Home/HomePage.tsx, incomplete accounts can briefly mount chat before redirecting to setup, creating a visible flash and unnecessary chat-session provisioning; add a pre-redirect loading/empty gate so setup-bound users never mount the home chat path.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/Onboarding/SetupSkipLink.tsx">

<violation number="1" location="components/Onboarding/SetupSkipLink.tsx:21">
P2: If `sessionStorage` is unavailable (e.g. in private browsing on some older WebViews, or when storage quota is exceeded), `skip()` catches the write failure and only updates in-memory state. The immediate `router.push("/")` then unmounts this component — discarding that state — and the home gate reads `skipped: false`, redirecting right back into setup. This makes the soft gate unskippable in that failure mode.

Consider passing the skip intent as a query param (e.g. `/?skipped=1`) or verifying the write succeeded before navigating, so the skip survives even when storage is denied.</violation>

<violation number="2" location="components/Onboarding/SetupSkipLink.tsx:21">
P2: When account data has not resolved yet, clicking this link stores the skip flag under an empty account id, so the home gate will not recognize the skip for the actual account and can immediately redirect the user back into `/setup`. Guard the action until `userData?.account_id` exists (or disable the link while it is loading) before writing the session flag.</violation>
</file>

<file name="components/Onboarding/SetupEntry.tsx">

<violation number="1" location="components/Onboarding/SetupEntry.tsx:28">
P2: An errored or unavailable checkpoint request leaves `/setup` permanently showing the loading skeleton because `isReady` never becomes true and this entry has no fallback or escape hatch. The entry should expose a recoverable error/continue path (or otherwise distinguish errors from the initial loading state) instead of treating every non-ready state as indefinitely loading.</violation>

<violation number="2" location="components/Onboarding/SetupEntry.tsx:29">
P3: While setup data is loading, screen-reader users get no indication that the page is waiting because the skeleton container has no status semantics or accessible label. Adding a status region (for example `role="status"` with an accessible loading label) would expose the state conveyed visually.</violation>
</file>

<file name="components/Home/HomePage.tsx">

<violation number="1" location="components/Home/HomePage.tsx:33">
P2: Incomplete accounts still mount the home chat while this redirect is pending, causing a visible home/chat flash and an unnecessary chat-session provisioning request before the canonical setup flow loads. A loading/empty handoff should be rendered instead of `NewChatBootstrap` whenever the account is being redirected to `/setup`.</violation>
</file>

<file name="lib/onboarding/getSetupPathForStep.ts">

<violation number="1" location="lib/onboarding/getSetupPathForStep.ts:14">
P1: The `catalog` step routes to `/setup/catalog`, but unlike the artists/socials steps (which render `RosterSocialsFlow` with `SetupSkipLink`) and the task step (which renders `FirstTaskStep` with `SetupSkipLink`), the catalog destination doesn't appear to mount a `SetupSkipLink`. If `/setup/catalog` redirects to `/catalogs` — where the skip control isn't present — an account stuck at this step enters a redirect loop: home → `/setup` → `/setup/catalog` → `/catalogs` → (user navigates home) → repeat, with no escape hatch. Either wire `SetupSkipLink` into the catalog step's surface or route this to a wrapper that includes it.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

const SETUP_PATHS: Record<OnboardingStepId, string> = {
artists: "/setup/artists",
socials: "/setup/socials",
catalog: "/setup/catalog",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The catalog step routes to /setup/catalog, but unlike the artists/socials steps (which render RosterSocialsFlow with SetupSkipLink) and the task step (which renders FirstTaskStep with SetupSkipLink), the catalog destination doesn't appear to mount a SetupSkipLink. If /setup/catalog redirects to /catalogs — where the skip control isn't present — an account stuck at this step enters a redirect loop: home → /setup/setup/catalog/catalogs → (user navigates home) → repeat, with no escape hatch. Either wire SetupSkipLink into the catalog step's surface or route this to a wrapper that includes it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/onboarding/getSetupPathForStep.ts, line 14:

<comment>The `catalog` step routes to `/setup/catalog`, but unlike the artists/socials steps (which render `RosterSocialsFlow` with `SetupSkipLink`) and the task step (which renders `FirstTaskStep` with `SetupSkipLink`), the catalog destination doesn't appear to mount a `SetupSkipLink`. If `/setup/catalog` redirects to `/catalogs` — where the skip control isn't present — an account stuck at this step enters a redirect loop: home → `/setup` → `/setup/catalog` → `/catalogs` → (user navigates home) → repeat, with no escape hatch. Either wire `SetupSkipLink` into the catalog step's surface or route this to a wrapper that includes it.</comment>

<file context>
@@ -0,0 +1,24 @@
+const SETUP_PATHS: Record<OnboardingStepId, string> = {
+  artists: "/setup/artists",
+  socials: "/setup/socials",
+  catalog: "/setup/catalog",
+  task: "/setup/tasks",
+};
</file context>

const { skip } = useOnboardingSessionFlags(userData?.account_id ?? "");

const handleSkip = () => {
skip();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: If sessionStorage is unavailable (e.g. in private browsing on some older WebViews, or when storage quota is exceeded), skip() catches the write failure and only updates in-memory state. The immediate router.push("/") then unmounts this component — discarding that state — and the home gate reads skipped: false, redirecting right back into setup. This makes the soft gate unskippable in that failure mode.

Consider passing the skip intent as a query param (e.g. /?skipped=1) or verifying the write succeeded before navigating, so the skip survives even when storage is denied.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SetupSkipLink.tsx, line 21:

<comment>If `sessionStorage` is unavailable (e.g. in private browsing on some older WebViews, or when storage quota is exceeded), `skip()` catches the write failure and only updates in-memory state. The immediate `router.push("/")` then unmounts this component — discarding that state — and the home gate reads `skipped: false`, redirecting right back into setup. This makes the soft gate unskippable in that failure mode.

Consider passing the skip intent as a query param (e.g. `/?skipped=1`) or verifying the write succeeded before navigating, so the skip survives even when storage is denied.</comment>

<file context>
@@ -0,0 +1,36 @@
+  const { skip } = useOnboardingSessionFlags(userData?.account_id ?? "");
+
+  const handleSkip = () => {
+    skip();
+    router.push("/");
+  };
</file context>

router.replace(getSetupPathForStep(step));
}, [isReady, step, router]);

return (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: An errored or unavailable checkpoint request leaves /setup permanently showing the loading skeleton because isReady never becomes true and this entry has no fallback or escape hatch. The entry should expose a recoverable error/continue path (or otherwise distinguish errors from the initial loading state) instead of treating every non-ready state as indefinitely loading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SetupEntry.tsx, line 28:

<comment>An errored or unavailable checkpoint request leaves `/setup` permanently showing the loading skeleton because `isReady` never becomes true and this entry has no fallback or escape hatch. The entry should expose a recoverable error/continue path (or otherwise distinguish errors from the initial loading state) instead of treating every non-ready state as indefinitely loading.</comment>

<file context>
@@ -0,0 +1,37 @@
+    router.replace(getSetupPathForStep(step));
+  }, [isReady, step, router]);
+
+  return (
+    <div className="mx-auto flex w-full max-w-xl flex-col gap-4 px-6 py-8">
+      <Skeleton className="h-8 w-2/3 rounded-lg" />
</file context>

const { skip } = useOnboardingSessionFlags(userData?.account_id ?? "");

const handleSkip = () => {
skip();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When account data has not resolved yet, clicking this link stores the skip flag under an empty account id, so the home gate will not recognize the skip for the actual account and can immediately redirect the user back into /setup. Guard the action until userData?.account_id exists (or disable the link while it is loading) before writing the session flag.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SetupSkipLink.tsx, line 21:

<comment>When account data has not resolved yet, clicking this link stores the skip flag under an empty account id, so the home gate will not recognize the skip for the actual account and can immediately redirect the user back into `/setup`. Guard the action until `userData?.account_id` exists (or disable the link while it is loading) before writing the session flag.</comment>

<file context>
@@ -0,0 +1,36 @@
+  const { skip } = useOnboardingSessionFlags(userData?.account_id ?? "");
+
+  const handleSkip = () => {
+    skip();
+    router.push("/");
+  };
</file context>


useEffect(() => {
if (shouldEnterSetup) {
router.replace("/setup");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Incomplete accounts still mount the home chat while this redirect is pending, causing a visible home/chat flash and an unnecessary chat-session provisioning request before the canonical setup flow loads. A loading/empty handoff should be rendered instead of NewChatBootstrap whenever the account is being redirected to /setup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Home/HomePage.tsx, line 33:

<comment>Incomplete accounts still mount the home chat while this redirect is pending, causing a visible home/chat flash and an unnecessary chat-session provisioning request before the canonical setup flow loads. A loading/empty handoff should be rendered instead of `NewChatBootstrap` whenever the account is being redirected to `/setup`.</comment>

<file context>
@@ -18,21 +19,20 @@ const HomePage = ({ initialMessages }: { initialMessages?: UIMessage[] }) => {
+
+  useEffect(() => {
+    if (shouldEnterSetup) {
+      router.replace("/setup");
+    }
+  }, [shouldEnterSetup, router]);
</file context>

}, [isReady, step, router]);

return (
<div className="mx-auto flex w-full max-w-xl flex-col gap-4 px-6 py-8">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: While setup data is loading, screen-reader users get no indication that the page is waiting because the skeleton container has no status semantics or accessible label. Adding a status region (for example role="status" with an accessible loading label) would expose the state conveyed visually.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Onboarding/SetupEntry.tsx, line 29:

<comment>While setup data is loading, screen-reader users get no indication that the page is waiting because the skeleton container has no status semantics or accessible label. Adding a status region (for example `role="status"` with an accessible loading label) would expose the state conveyed visually.</comment>

<file context>
@@ -0,0 +1,37 @@
+  }, [isReady, step, router]);
+
+  return (
+    <div className="mx-auto flex w-full max-w-xl flex-col gap-4 px-6 py-8">
+      <Skeleton className="h-8 w-2/3 rounded-lg" />
+      <Skeleton className="h-[72px] w-full rounded-xl" />
</file context>

@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Notes from shipping #1887 (merged 2026-07-27, 7168ac66)

Four things surfaced while verifying #1887 that bear directly on this PR. Nothing here asks for a scope change — three are confirmations, one is a rebase.

1. Needs a rebase — one conflict, trivial resolution

This branch's merge-base is a2d8cf8b; main is now 7168ac66. A dry-run merge conflicts in one place: components/Onboarding/RosterSocialsFlow.tsx. Both PRs rewrote the same docstring — #1887 because it deleted the /onboarding/roster mount the old text referenced, this PR because it re-homes the component under /setup/* and adds the skip link.

Take this PR's version. It's a strict superset — it already says "Container for the roster + socials steps of the canonical /setup/* sequence (chat#1889), mounted by /setup/artists and /setup/socials", which is what #1887's edit was reaching for, plus the skip-link rationale. No logic is involved on either side.

2. Live evidence for the /setup → step-1 bug this PR fixes

Verified 2026-07-27 on the #1887 preview with an existing, fully onboarded account — multi-artist roster (Ana Bárbara, Charlie Zaa, Apache, Adri Torron, Collie…), socials matched per artist, scheduled tasks completing. Hitting /setup sent it to /setup/artists rendering "Step 1 of 2 — Confirm your roster" over an already-verified roster (screenshot + full pass).

The detail that makes this sharper than the cold-start framing: the welcome email's "Confirm your roster" CTA points at /setup. So it isn't only first-run users who land wrong — every returning onboarded account that clicks that link is told to redo step 1. That's live on prod today. app/setup/page.tsx hard-redirects unconditionally:

export default function SetupPage() {
  redirect("/setup/artists");
}

This PR replacing that with derived-step routing is the fix, and the real-roster repro is a good manual check for whoever verifies it: sign in as an onboarded account, hit /setup, confirm it opens at the derived step rather than step 1.

3. The getOnboardingStepContent prerequisite is already handled

#1889 flagged that OnboardingCheckpointList.tsx also imports getOnboardingStepContent, so the checklist needed its own step labels before that file could go. Confirmed done on this branch — OnboardingCheckpointList now imports getOnboardingStepTitle instead, and getOnboardingStepContent.ts + its test are deleted cleanly. No dangling importer.

4. Dead-code sweep after this PR lands: clean, nothing further to delete

Ran a zero-referrer sweep over components/Onboarding/, lib/onboarding/ and hooks/onboarding/ against this branch — no orphans. Worth recording what correctly survives, since it's easy to over-delete here:

  • OnboardingChecklist stays — HomePage.tsx still imports it after this PR.
  • OnboardingCheckpointList stays — reached via OnboardingChecklist.
  • FirstTaskStep and RosterSocialsFlow stay — mounted by /setup/tasks, /setup/artists, /setup/socials.

So OnboardingSequence + OnboardingStepCard + getOnboardingStepContent is exactly the right deletion set; there is no residual scaffolding hiding behind it.

One correction to something I said elsewhere: I'd flagged a possible FirstTaskStepOnboardingSequence dependency that would complicate deleting the container. That was a false positive from substring matching — the reference is a docstring mention, not an import. There is no circular cleanup problem.

One canonical onboarding surface (chat#1889 item 1, part 2).

Home hosted a second, card-based sequence whose cards only linked OUT to the
generic app pages (/artists, /artists, /catalogs, /tasks), while the real
interactive steps lived only under /setup/*. A direct signup — and anyone
arriving from a marketing CTA — therefore never saw the flow the welcome
email's /setup/* deep links reach. The step copy was also triplicated
(getOnboardingStepContent, api's welcomeOnboardingSteps, each step's own
heading).

- HomePage forwards an incomplete account to /setup instead of rendering its
  own sequence; the pinned-checklist (skipped) path is unchanged.
- /setup opens at the DERIVED step via new SetupEntry + getSetupPathForStep,
  instead of hard-redirecting to /setup/artists — a signup who had already
  confirmed their roster was being sent back to step 1.
- Deleted the placeholder surface: OnboardingSequence, OnboardingStepCard and
  getOnboardingStepContent (its own docstring called it placeholder copy
  pending the real steps, which have since shipped).
- OnboardingCheckpointList now reads titles from getOnboardingStepTitle.
- SetupSkipLink carries the gate's escape hatch onto the /setup surface, so
  forwarding home doesn't turn the soft gate into a wall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sweetmantech

Copy link
Copy Markdown
Collaborator Author

Rebased + preview verification — 2026-07-28

Preview: https://chat-681ule3mz-recoup.vercel.app — built from 1a624722 (rebased head, sha-filtered deployment 5633288752, state success).

Rebase

This branch was cut before chat#1887 and chat#1886 merged, and GitHub reported it DIRTY. Rebased onto main (bbd51655) — one conflict across 16 files, in components/Onboarding/RosterSocialsFlow.tsx, where #1887 and this PR both rewrote the same docstring.

Resolved in favour of this PR's version, and not just because it's newer: main's side carried the sentence "The sibling onboarding-sequence router (chat#1867) mounts ConfirmRosterStep and VerifySocialsStep directly…" — describing OnboardingSequence, which this PR deletes. Keeping it would have left a comment pointing at a file that no longer exists.

Post-rebase sanity: #1886's AvatarImage and #1887's next.config.mjs redirects both still present; this PR's four intended deletions still applied; no code references to the deleted modules remain (the two grep hits are comments). 69 files / 261 tests pass; tsc --noEmit at the known 8-error baseline with none in any touched file.

Results

Two accounts, chosen so the interesting behaviours sit at opposite ends — plus a third state created mid-test to prove the step is actually derived rather than hardcoded.

# State Action Expected Actual Result
1 signed out load / no redirect stays /, normal chat home
2 cold start (0 artists) sign in → / converge on setup //setup/artists
3 cold start opens at derived step artists step, correct for an empty roster
4 cold start "Skip for now" escape hatch works returns to /, home renders
5 cold start reload / skip persists stays /, not re-trapped
6 cold start inspect / no card sequence no OnboardingStepCard; surviving OnboardingChecklist shows 4 checkpoints + Continue
7 1 artist, 0 socials load /setup opens at derived step /setup/setup/socials ("Step 2 of 2")
8 onboarded (22 artists) load / not pulled into setup stays /, real workspace renders
9 onboarded load /setup not dumped at step 1 /setup/

Row 7 is the one that proves the feature. Rows 3 and 9 are the two extremes and would both pass a naive "empty → step 1, complete → home" implementation. Adding a single artist mid-test moved /setup from /setup/artists to /setup/socials, which is only possible if the destination is genuinely derived from account state.

Row 9 fixes a live prod bug. On main today app/setup/page.tsx hard-redirects to /setup/artists unconditionally, so a fully onboarded account hitting /setup is told to redo "Step 1 of 2 — Confirm your roster" over an already-verified roster (reproduced on a real 22-artist account). Because the welcome email's "Confirm your roster" CTA points at /setup, that misfires for every returning onboarded user who clicks it. After this PR, that same account lands on home.

Rows 4 + 5 matter as much as the convergence itself. A gate that can't be exited, or that re-traps on every reload, would be worse than the placeholder cards it replaces. It does neither — and the checklist on home (row 6) keeps the sequence reachable after skipping.

Cold start — home converges on /setup/artists:

cold start home converges

After "Skip for now" — home renders, checklist keeps it reachable:

after skip

Derived step — one artist added, /setup now opens at socials:

derived step socials

Onboarded account — home untouched:

onboarded home

Onboarded account at /setup — goes home instead of step 1:

onboarded setup goes home

Note

Row 6 shows the checklist rendering exactly 4 checkpoints, matching ONBOARDING_STEP_IDS — the count api#787 aligns the welcome email to, and the reason the 2026-07-26 correction on chat#1889 kept it at 4 rather than adding a valuation step.

@sweetmantech
sweetmantech merged commit 830b6eb into main Jul 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant