Skip to content

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

Open
sweetmantech wants to merge 1 commit into
mainfrom
feat/setup-canonical-sequence
Open

feat(onboarding): converge home onto /setup at the derived step (chat#1889 item 1, part 2)#1890
sweetmantech wants to merge 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, so all entry points hit the same interactive steps (chat#1889).

  • New Features

    • Added SetupEntry to resolve the derived step and redirect via getSetupPathForStep, showing a short skeleton while loading.
    • /setup now mounts SetupEntry instead of hard-redirecting to /setup/artists.
    • Added SetupSkipLink to /setup/* steps to keep the gate soft; skip flags the session and returns to home with the checklist pinned.
    • Introduced getOnboardingStepTitle and updated the checklist to use it; added unit tests for new utilities and Home behavior.
  • Refactors

    • Removed the duplicate sequence: OnboardingSequence, OnboardingStepCard, and getOnboardingStepContent (and its test).
    • HomePage replaces the local sequence with a redirect to /setup for incomplete accounts.
    • Updated RosterSocialsFlow and FirstTaskStep to include the shared skip link.
    • Tests updated to assert the new contract; touched files are type-safe.

Written for commit 0b36f76. Summary will update on new commits.

Review in cubic

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>
@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 27, 2026 4:34am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e3437c59-a663-4a3d-8e75-13652cc450d4

📥 Commits

Reviewing files that changed from the base of the PR and between a2d8cf8 and 0b36f76.

⛔ 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
✨ Finishing Touches
🧪 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>

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