feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(ui): re-home onboarding onto app spacing/type tokens (#289) - #497

Merged
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome
Jul 31, 2026
Merged

feat(ui): re-home onboarding onto app spacing/type tokens (#289)#497
AndresL230 merged 2 commits into
mainfrom
feat/289-onboarding-rehome

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

Onboarding was color-correct but structurally orphaned: 12 hardcoded px values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its own radial void. The flow and steps are untouched; only the frame changed.

The --fs-* scale — option (b), as you chose

Derived, not invented. Every step is a size the app already uses, counted across screens/ and components/:

--fs-md: 13pxthe app's true body size — 107 call sites
--fs-sm/xs/2xs12 / 11 / 10 — chrome and micro-labels (215 uses combined)
--fs-base/lg14 / 16 — body copy
--fs-xl/2xl/3xl18 / 22 / 26 — headings
--fs-4xl: 30pxscreen title — matches TopBar
--fs-5xl: 32pxDashboard display numeral

Deliberately not density-aware, unlike --pad-*. Retuning the whole type ramp with the density preference changes how much text fits on every screen — that's a product decision, not a refactor. Additive to add later; doing it now would hide a behaviour change inside a token introduction.

Onboarding is the scale's first and only consumer. The rest of the app keeps its inline sizes until each screen is converted deliberately — a token set with zero consumers is exactly the state #288 just finished cleaning up, and I'd rather not recreate it.

The rest of the frame

  • Every hardcoded padding → --pad-*, so onboarding finally responds to the density preference at all.
  • The bespoke radial void → the app's own --bg.
  • minHeight: 100vh100dvh — the same iOS Safari lesson ShellFrame learned in Chat/Tutor: top nav bar scrolls out of view in horizontal-nav mode #331, which onboarding never got because it renders outside (shell).

One visible change worth calling out: step headings move 26 → 30 to match TopBar. That's the alignment the issue asks for, not a side effect.

The journey that didn't exist

There was no e2e journey over /onboarding at all — notable, since it's the first screen a newly-approved student sees and it renders bare, with no ShellFrame, nav or <main> padding to inherit. Nothing in the suite would have noticed it breaking.

Added one. It signs in as USER_NEW (the only seeded user with onboarding_completed=False, so the only one the funnel renders for) and asserts the card actually occupies the screen with resolved padding — a mistyped var() computes to 0px and silently collapses the box, which no unit test would catch and which is the specific risk this PR's conversion introduces.

It's a smoke-plus-layout journey rather than a five-step walkthrough on purpose: the field validation is unit-tested, and a long scripted click-path over a funnel that's still being redesigned would break on every copy tweak.

Testids — both halves

Driving onboarding made it an E2E surface, so Onboarding.tsx joins the enforcement list in eslint.config.mjs. That immediately flagged 7 untagged controls (the rule doing its job), so all ten are now tagged and registered in docs/frontend-testids.md — surface table and inventory.

Not done

The proposal also suggested rendering onboarding in the app's left-aligned frame with shell-like chrome. I've left the centred card: moving the first-run funnel from centred-card to left-aligned-shell changes its whole character, and that's a taste call rather than debt cleanup. Happy to do it if you want it.

Gates

  • tsc --noEmit clean · npm run lint 0 errors · npx vitest run 58 files / 415 tests
  • Full local e2e cycle — in a comment below

part of #289

Onboarding was color-correct but structurally orphaned: 12 hardcoded px
values, a bespoke type ramp (32 on welcome, 26 everywhere else — matching
neither TopBar's 30 nor Dashboard's 42), and a centred card floating in its
own radial void. The flow and steps are untouched; only the frame changed.
Introduces the --fs-* type scale (option (b)). It is DERIVED, not invented:
every step is a size the app already uses, counted across screens/ and
components/ — 13px is the true body size at 107 call sites, 12/11/10 carry
chrome and micro-labels, 14-16 body copy, 18-26 headings, 30 is TopBar's
screen title, 32 the Dashboard display numeral.
Deliberately NOT density-aware, unlike --pad-*. Retuning the whole type ramp
with the density preference changes how much text fits on every screen — a
real product decision, not a refactor. Additive to do later; shipping it now
would hide a behaviour change inside a token introduction.
Onboarding is the scale's first and only consumer. The rest of the app keeps
its inline sizes until each screen is converted deliberately — a token set
with zero consumers is exactly the state #288 just finished cleaning up.
Also: every hardcoded padding becomes --pad-* (so onboarding finally responds
to the density preference), the radial void becomes the app's own --bg, and
minHeight moves 100vh -> 100dvh — the same iOS Safari lesson ShellFrame
learned in #331, which onboarding never got because it renders outside
(shell).
One visible change worth calling out: step headings move 26 -> 30 to match
TopBar. That is the alignment the issue asks for, not a side effect.
Adds the first e2e journey over /onboarding. There was none — notable, since
it is the first screen a newly-approved student sees and it renders bare,
outside (shell), with no ShellFrame, nav or <main> padding to inherit;
nothing in the suite would have noticed it breaking. It signs in as USER_NEW
(the only seeded user with onboarding_completed=False) and asserts the card
actually occupies the screen with resolved padding — a mistyped var()
computes to 0px and silently collapses the box, which no unit test would
catch.
That made onboarding a driven E2E surface, so it joins the testid
enforcement list, and its ten controls are tagged and registered in
docs/frontend-testids.md — both halves.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Jul 31, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 31, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-staging25ab839Commit Preview URL

Branch Preview URL
Jul 31 2026, 08:12 AM

@coderabbitai

coderabbitaiBot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:45 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: 830e4837-7876-49f5-a9c4-63638bdd2fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb7949 and 25ab839.

📒 Files selected for processing (6)
  • docs/frontend-testids.md
  • frontend/e2e/onboarding.spec.ts
  • frontend/e2e/support/stack.ts
  • frontend/eslint.config.mjs
  • frontend/src/app/globals.css
  • frontend/src/components/screens/Onboarding.tsx

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.

Unique testids for repeated elements. Six of the ten testids sat on elements
that render simultaneously — two TextInputs side by side in StepName, two
TagInputs (majors + minors) each with their own add button and chips, five
learning-style radios, N course results — so getByTestId could not
disambiguate them. docs/frontend-testids.md's "Repeated / list items" rule
already required a stable suffix and the codebase already had the precedent
(upload-modal-course-result-${c.id}); I missed both. TagInput now takes a
`field` prop for the same reason. eslint only checks that a data-testid is
PRESENT, not that it is unique, which is why lint stayed green.
Restored two-value paddings. Collapsing "40px 36px" to a single --pad-xl and
"14px 12px" to a single --pad-md threw away deliberate vertical/horizontal
asymmetry on the card every step renders inside. Both are two-value again.
The remaining value shifts are inherent to adopting a quantised scale and are
listed in the PR body rather than left to be discovered.
Corrected a comment that asserted something I had not verified. The USER_NEW
docstring claimed "approved so the middleware lets them past the pending
gate" — but /onboarding is not in middleware.ts's PROTECTED list or its
matcher, and Onboarding.tsx only redirects when unauthenticated, so the route
renders for any signed-in user regardless of approval or onboarding state.
USER_NEW is the right choice because it is the realistic first-run state, not
because the route gates on it. Both the stack.ts comment and the spec
docstring now say that.
part of #289
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review found four things — all fixed in 25ab839

Six of the ten testids were on elements that render simultaneously. Two TextInputs side by side in StepName, two TagInputs (majors + minors) each with their own add button and chips, five learning-style radios, N course results — all sharing one testid, so getByTestId couldn't disambiguate them. docs/frontend-testids.md already has a "Repeated / list items" rule requiring a stable suffix, and the codebase already had the precedent (upload-modal-course-result-${c.id}). I missed both. Now suffixed by id/field/value, and TagInput takes a field prop for the same reason.

Worth noting the eslint rule only checks a data-testid is present, not that it's unique — so lint was green on ambiguous ids.

Two paddings collapsed a two-value shorthand into one."40px 36px" → a single --pad-xl and "14px 12px" → a single --pad-md threw away deliberate vertical/horizontal asymmetry, on the card every step renders inside. Both are two-value again.

A comment asserted something I hadn't verified. The USER_NEW docstring claimed "approved so the middleware lets them past the pending gate" — but /onboarding isn't in middleware.ts's PROTECTED list or its matcher, and Onboarding.tsx only redirects when unauthenticated. The route renders for any signed-in user. USER_NEW is right because it's the realistic first-run state, not because the route gates on it. Corrected in both the constant and the spec docstring.

Value shifts from adopting a quantised scale

Listing these rather than leaving them to be found. The --pad-* scale has no 36/40 step, so:

wasnowdelta
card 40px 36px--pad-xl --pad-lg = 32px 22px−8 / −14
page 40px 20px--pad-xl --pad-lg = 32px 22px−8 / +2
inputs 10px 12px--pad-sm --pad-md = 10px 16px0 / +4
loading rows 14px 12px--pad-md --pad-sm = 16px 10px+2 / −2

Type is exact except the intentional 26 → 30 on step headings.

E2E

36 passed (1.3m) # 35 existing + the new onboarding journey
0 finding(s)

Gates: tsc clean · lint 0 errors · vitest 58 files / 415 tests.

@AndresL230
AndresL230 merged commit 77b59b9 into mainJul 31, 2026
7 checks passed
@AndresL230
AndresL230 deleted the feat/289-onboarding-rehome branch August 2, 2026 18:30
Sign up for freeto 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

@AndresL230