refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez
, '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

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1) - #286

Merged
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification
Jul 2, 2026
Merged

refactor(frontend): unify CSS token layer + add (public) route group (Phase 1)#286
AndresL230 merged 32 commits into
mainfrom
refactor/token-unification

Conversation

@AndresL230

@AndresL230AndresL230 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Frontend token unification — Phase 1 (the foundation)

Collapses the two parallel CSS design systems in globals.css into one. Previously the pre-auth surface re-declared core semantic tokens (--border, --text*, --shadow-*, --dur-*) inside .landing-page with cooler/faster values, so the same class name rendered differently on either side of sign-in — the root cause of "the get-started/beta flow feels like a different product." This was documented tech debt (globals.css:726: "to be revisited and consolidated… in a follow-up pass").

What changed

  • One canonical token layer. The warm app palette in :root is the single source of truth and is unchanged. The pre-auth surface no longer shadows any core token.
  • Additive marketing layer. A small named set (--display-hero, --surface-hero, --brand-glow, --bg-mesh) scoped via a new .public-surface route-group layout — never redefines a :root token.
  • Green collapse. Retired the five competing greens (--brand-primary #2e7d52, --brand-success, --brand-teal, …) down to --brand-forest (+ bright + derived glow). --accent sage stays as a distinct role.
  • (public) route group. Landing + about/careers/privacy/terms moved under app/(public)/URLs unchanged (route groups are URL-transparent); mirrors the (shell) group.
  • Zero retired-token references remain tree-wide.

Verification

  • ✅ Clean production npm run build; route manifest confirms all public URLs unchanged.
  • /dashboard and the app shell are provably unchanged — no :root core token value or (shell) file was touched. The only app-reachable delta is --rarity-uncommon--brand-forest (intended green collapse).
  • ✅ Visual sweep of landing, beta modal, /about, /careers — warm + intact.
  • ✅ Independent whole-branch review: APPROVE WITH NITS (0 critical / 0 important); both nits fixed.

Note: the repo's Frontend CI was already red on main before this branch (unrelated lockfile/eslint-baseline drift); this change does not cause or fix that.

Scope & deferred work

This PR is the token layer only. Component shape/motion re-skins (beta pill + infinite glow, hero-card de-dup, onboarding/pending re-home, motion normalization, dead-code deletion) and the components/ directory flattening are intentionally deferred and filed as follow-up issues. See docs/superpowers/followups/2026-06-30-token-unification-followups.md.

Docs

  • Audit + visual companion: docs/frontend-rhythm-audit.md (+ .html)
  • Spec: docs/superpowers/specs/2026-06-30-token-unification-design.md
  • Plan: docs/superpowers/plans/2026-06-30-token-unification.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced shared UI components for actions and selection (buttons, segmented toggles, chips, badges, and filter pills).
    • Updated multiple screens and modals to use these controls for consistent interactions.
  • Bug Fixes / Improvements

    • Standardized brand “forest” styling and theme tokens across public and in-app surfaces.
    • Improved focus/interaction styling and harmonized dialog and button/radius/transition visuals.
  • Documentation

    • Added guided-tour and design-system audit pages, plus token unification and consistency/spec updates.

AndresL230and others added 9 commits June 30, 2026 15:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate states correct)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…plicate namespace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rarity + glass-input
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-layer host
Moves landing + about/careers/privacy/terms under app/(public)/ (URLs unchanged).
The layout applies a token-only .public-surface scope so content pages keep the
warm paper canvas; the landing retains .landing-page for its mesh visual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(public)/page.tsx, HowItWorks.tsx, Dialog.tsx: --brand-text1/2 -> --text/--text-dim,
--radius-* -> --r-*, --ease-out/in-out -> --ease. Mechanical identifier renames only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngle namespace
Migrates the 3 remaining globals.css --ease-out consumers to --ease, then deletes
the temporary :root brand aliases and the .public-surface/.landing-page namespace
aliases. Zero retired-token references remain tree-wide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + dir hygiene)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds shared UI primitives, updates token and public-surface styling, migrates public and in-app screens to the new primitives, and adds documentation for audits, plans, specs, and handoffs.

Changes

UI primitives and token migration

Layer / File(s)Summary
Shared UI primitives
frontend/src/components/ui/Button.tsx, Chip.tsx, Toggle.tsx, Badge.tsx, FilterPills.tsx, index.ts, frontend/src/components/Pill.tsx
Adds client-side Button, Chip, Toggle, Badge, and FilterPills components plus the UI barrel export, and updates Pill accessibility attributes.
Token and public-surface styling
frontend/src/app/globals.css, frontend/src/app/(public)/layout.tsx, frontend/eslint-suppressions.json, frontend/src/app/(public)/page.tsx
Updates core tokens, sizing, rarity, focus, and easing styles, introduces the public-surface wrapper, updates the suppression path, and migrates the landing page to shared Button and theme tokens.
Screen and modal migrations
frontend/src/components/Gradebook/*, frontend/src/components/screens/*, frontend/src/components/TitleFlair.tsx, frontend/src/lib/localData.ts
Replaces local button, pill, toggle, and badge rendering with shared primitives across gradebook and app screens, and updates local mock data for the affected flows.
Brand color and text token updates
frontend/src/app/(public)/*, frontend/src/components/*, frontend/src/components/Dialog.tsx, frontend/src/components/HowItWorks.tsx
Replaces hard-coded brand greens and older text, radius, and easing tokens with shared theme variables in public pages, navigation, dialogs, and HowItWorks.

Design audits and implementation notes

Layer / File(s)Summary
Audit and guided-tour docs
docs/button-shape-comparison.html, docs/changes-tour.html, docs/frontend-component-consistency-audit.md, docs/frontend-rhythm-audit.*
Adds static audit and tour documentation describing button shape, component consistency, and rhythm findings.
Plans, specs, and handoff notes
docs/superpowers/plans/*, docs/superpowers/specs/*, docs/superpowers/followups/*, docs/superpowers/handoffs/*
Adds implementation plans, design specs, deferred follow-ups, and the selector-consolidation handoff document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 29.73% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main Phase 1 token-unification and public route-group change.
Description check✅ PassedCovers the PR purpose, changes, verification, and deferred scope, though it omits the template’s Related Issues, Testing, Screenshots, and Notes sections.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/token-unification

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jun 30, 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-staging9313888Commit Preview URL

Branch Preview URL
Jul 02 2026, 04:42 AM

AndresL230and others added 7 commits June 30, 2026 16:31
…/page.tsx
The route move shifted the landing page out from under its path-keyed
suppression entry, un-suppressing 4 pre-existing grandfathered errors
(no-html-link-for-pages, prefer-const x2, react-hooks/immutability).
Re-homes the entry to the new path; no code behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rison
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire sage --accent -> brighter forest (--brand-forest-bright) for highlight/focus;
merge --state-mastery + --grade-a into one --positive status green; add .btn--lg
hero size for de-pilled CTAs. Three forest-family greens by role: forest(action) /
accent(highlight) / positive(status).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Thin wrappers over the canonical .btn/.chip classes. Button enforces one shape
(6px) with variant + size (incl. lg hero size); Toggle is the one segmented
control; Chip/Badge collapse the pill/badge zoo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ives
- Landing CTAs (Get Started x2, Sign up for Beta) -> <Button size=lg>, de-pilled
to sharp 6px, infinite glow removed.
- 5 Gradebook modal save/submit buttons -> <Button> (kills hard-coded borderRadius:6).
- Study flashcard ratings 10px -> 6px (kept color-coding).
- Tokenize orphan #1a5c2a wordmark -> var(--brand-forest) across 8 files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add optional per-option title to <Toggle>; replace the Learn setup mode pills with
it. ModelToggle (Fast/Smart) intentionally left as-is — it has a sliding animation,
per-option color semantics, and a tooltip that the generic Toggle would degrade.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Flair
Badge carries the hue on border + soft bg, keeps text neutral (colored text fails
4.5:1 on several rarity tiers). TitleFlair now wraps Badge instead of inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 4 commits June 30, 2026 18:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arted CTA
De-pilled CTAs hug the text now (lg 13/26 -> 9/18). The closing 'Get Started'
under the 'Ready to Start Growing?' hero uses a new xl size so it stays a
prominent central focal point; navbar + hero beta stay tight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bright accent
The header button hard-coded background:var(--accent); after accent shifted to the
brighter forest, it rendered brighter than every other primary button. Now a proper
<Button variant=primary> (--brand-forest), matching the term pills and app buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AndresL230and others added 9 commits June 30, 2026 20:23
Family (b) control: a wrapping row of selectable pills for 'All' + N
dynamic filters, wrapping the existing <Pill> (with per-option color/icon).
Complements <Toggle> (family (a): fixed connected segmented control).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the custom accent-filled pill buttons with the shared <Toggle>
(forest-filled active). Keeps SemesterChips' public API; also fixes the
prior --accent button fill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overview/Chat/Study match/Activity tab row now uses the shared <Toggle>.
setTab wiring unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
month/week/day/table switch now uses the shared <Toggle>; drops a
pre-existing unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid/list view switch uses the shared <Toggle>; the category filter row
uses the shared <FilterPills>. Drops the now-unused Pill import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all/activity/social/milestone/special filter row uses shared <FilterPills>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Topic filter (All + dynamic topics) uses shared <FilterPills>. The mode
toggle (Study Guide/Flashcards) is left specialized for its Framer-Motion
spring, matching the ModelToggle precedent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier filter uses shared <FilterPills> with per-option color. The course
filter (colored dots) stays specialized per the design decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): shared UI primitives + green consolidation (Phase 2)

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/frontend-component-consistency-audit.md (1)

86-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify --accent decision status.

Since globals.css already resolved --accent to var(--brand-forest-bright), the "Sage --accent" decision appears decided in favor of retiring toward forest. Update this section to reflect the current state, or note if this audit intentionally captures the pre-decision deliberation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 86 - 89, The “Sage
`--accent`” decision is no longer undecided, since `globals.css` already points
`--accent` to `var(--brand-forest-bright)`. Update the “Decision needed” section
in the audit to reflect that the accent has been resolved toward forest, or
explicitly label this as historical deliberation if the intent is to preserve
the pre-decision discussion. Keep the wording consistent with the existing
`globals.css` and `--accent` references so the status is unambiguous.
🧹 Nitpick comments (18)
docs/superpowers/specs/2026-06-30-token-unification-design.md (3)

72-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update --accent description to match final implementation.

The spec states --accent #8a9a5b (sage) stays, but the actual globals.css implementation (per upstream context) has --accent: var(--brand-forest-bright) (#2D8F5C). The green collapse re-homed --accent into the forest family. Update this section to reflect the final Phase 1 implementation where --accent was re-pointed to the brighter forest highlight role, not kept as sage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
72 - 79, Update the “Green collapse” section to match the final `globals.css`
implementation: `--accent` is no longer sage and should be described as
re-pointed to `--brand-forest-bright` for the forest highlight role. Adjust the
text around `--accent`, `--brand-forest`, and `--brand-glow` in this spec so it
reflects the Phase 1 token mapping used by the final implementation and does not
imply sage was retained.

81-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify .landing-page retirement scope.

The spec states .landing-page class is retired as a token host, but the actual implementation retains .landing-page for mesh background and utility classes (.landing-*), co-existing with .public-surface for token scoping. Update to: .landing-page is retired as a token host; it persists as a visual/utility scope for landing-specific mesh and font utilities, while .public-surface carries the marketing-layer tokens for all public routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
81 - 95, Clarify the `(public)` token-scoping guidance so `layout.tsx` and
related docs distinguish `.landing-page` from the token host: `landing-page`
should no longer be described as carrying marketing tokens, but as the
visual/utility scope for mesh and `.landing-*` classes, while `.public-surface`
is the actual token host for the public routes. Update the spec text around
`(public)/layout.tsx`, `.landing-page`, and `.public-surface` to reflect this
split and keep the existing route-group migration intent clear.

58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify --brand-text2 migration target in retirement table.

The table says migrate to var(--text-dim) / --text-muted but the implementation plan standardizes on var(--text-dim) for all 34 occurrences. Either update the spec to match the plan's mechanical migration, or note that --text-muted is reserved for a future semantic refinement pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md` around lines
58 - 69, The retirement table entry for `--brand-text2` is inconsistent with the
implementation plan. Update the spec in this token-unification section to use a
single migration target, preferably `var(--text-dim)` for all `--brand-text2`
consumers, and remove the ambiguous `--text-muted` mention unless you explicitly
document it as a future follow-up. Keep the guidance aligned with the
surrounding token refs/actions in this table so the `--brand-text2` migration is
mechanically clear.
docs/superpowers/specs/2026-06-30-component-system-phase2-design.md (3)

75-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update migration list to reflect ModelToggle decision.

The spec lists ModelToggle.tsx as a Toggle migration target, but the handoff records the precedent that ModelToggle was kept specialized for its sliding animation + Fast/Smart color semantics. Update this item to reflect the actual implementation decision, or move it to a "Deferred/Decided" section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 75 - 79, The migration list still treats ModelToggle as a Toggle target
even though the implementation decision was to keep ModelToggle specialized for
its sliding animation and Fast/Smart color semantics. Update the spec entry that
mentions ModelToggle so it matches the actual decision by removing it from the
Toggle migration list and either marking it as kept specialized or moving it
into a Deferred/Decided section alongside the relevant Learn.tsx toggle items.

48-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update Toggle spec to include size and title? in options.

The handoff documents the final Toggle API as options: {value,label,title?}[] with size: sm|md. Add these to the spec for completeness, or note that the spec predates these additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 48 - 50, The Toggle spec is missing fields that are part of the final API,
so update the `<Toggle>` documentation to reflect `size: sm|md` and `options`
entries including optional `title?`. Use the existing `components/ui/Toggle.tsx`
section to add these props, or explicitly note that the spec predates these
additions so readers know the API evolved. Keep the description aligned with the
referenced Toggle replacement usage sites.

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add xl size to Button spec or clarify discrepancy with handoff.

The handoff document (2026-06-30-selector-consolidation.md) references size: sm|md|lg|xl, but this spec only defines sm|md|lg. If xl was added post-spec, note it here; if the handoff is incorrect, update it to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md` around
lines 36 - 47, The Button sizing contract is inconsistent between this spec and
the handoff reference, since ButtonProps only lists sm, md, and lg while the
handoff expects xl as well. Update the Button spec to either explicitly add xl
to the documented size options and related class mapping, or clearly note that
xl is not supported and the handoff should be corrected. Keep the description
aligned with components/ui/Button.tsx and the ButtonProps definition so the size
contract is unambiguous.
docs/superpowers/plans/2026-06-30-token-unification.md (3)

209-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor documentation inconsistency: .glass-input:focus shadow color value.

The plan specifies rgba(27, 109, 66, 0.2) but the actual implementation in globals.css:636 uses rgba(27, 108, 66, 0.2) — matching the #1B6C42 → rgb(27,108,66) value of --brand-forest. Update the plan to match the canonical token value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 209 -
218, Update the plan entry for `.glass-input:focus` so the documented box-shadow
color matches the canonical `--brand-forest` token value used in `globals.css`
and the `glass-input:focus` rule. Replace the mismatched `rgba(27, 109, 66,
0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value that the actual
implementation uses, keeping the note aligned with the `--brand-forest` token
and the `glass-input:focus` selector.

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update layout class to match final implementation.

The plan shows className="landing-page" but the PR objectives and actual globals.css implementation use .public-surface as the primary marketing scope class (with .landing-page kept for mesh/utility compatibility). Update the plan to use .public-surface and note that .landing-page is applied additionally on the landing page itself for mesh-specific styling.

-export default function PublicLayout({ children }: { children: React.ReactNode }) {- return <div className="landing-page">{children}</div>;-}+export default function PublicLayout({ children }: { children: React.ReactNode }) {+ return <div className="public-surface">{children}</div>;+}

Or if the final implementation uses both, document that explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/superpowers/plans/2026-06-30-token-unification.md` around lines 270 -
279, The plan’s public layout example uses the wrong scope class name, so update
the documented `PublicLayout` wrapper to reflect the final marketing surface
class used in `globals.css`. Reference `PublicLayout` and make the plan say the
subtree should be wrapped with `public-surface`, with `landing-page` mentioned
as an additional class only where mesh-specific styling is needed on the landing
page itself.

298-304: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit eslint verification step to route move task.

Phase 1 experienced eslint suppression path breaks after route moves (documented in docs/superpowers/handoffs/2026-06-30-selector-consolidation.md §Gotchas). Add npx eslint . to the verification commands in Task 4 Step 4:

-```bash-cd /home/andresl/Projects/sapling/frontend-npm run build 2>&1 | tail -20-```+```bash+cd /home/andresl/Projects/sapling/frontend+npm run build 2>&1 | tail -20+npx eslint . 2>&1 | tail -10+```
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/plans/2026-06-30-token-unification.md around lines 298 -
304, The Task 4 Step 4 verification block only checks the build and missed the
eslint suppression breakage seen after route moves. Update the verification
commands in the route move plan to include an explicit npx eslint . run
(alongside the existing build check) so the step validates both compilation and
linting after the marketing routes are moved.


</details>
<!-- cr-comment:v1:86e5113c4120979995d03269 -->
</blockquote></details>
<details>
<summary>docs/superpowers/handoffs/2026-06-30-selector-consolidation.md (1)</summary><blockquote>
`52-58`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Consider adding the eslint-suppressions gotcha to the implementation plan.**
This gotcha was learned in Phase 1 but the token-unification plan (`2026-06-30-token-unification.md`) doesn't mention it in Task 4. Add a cross-reference note in the plan's Task 4 or a "Lessons learned" appendix for future agentic workers.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/superpowers/handoffs/2026-06-30-selector-consolidation.md around lines
52 - 58, Add the eslint-suppressions lesson to the token-unification plan so
future work remembers that moving or renaming files with grandfathered lint
suppressions requires re-homing entries in frontend/eslint-suppressions.json.
Update Task 4 in 2026-06-30-token-unification.md, or add a short “Lessons
learned” appendix, and cross-reference the file-path-based suppression behavior
so agentic workers don’t miss it when changing routes or component locations.


</details>
<!-- cr-comment:v1:4510f7b7efa9308f3b0ad93a -->
</blockquote></details>
<details>
<summary>frontend/src/components/SideNav.tsx (1)</summary><blockquote>
`129-131`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 131 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex than `#1a5c2a`, the glow will mismatch the text color. Consider using `color-mix()` or a derived token if available.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/SideNav.tsx around lines 129 - 131, The SideNav
styling still hard-codes the old green in the text glow, so textShadow can
drift from the updated --brand-forest color. Update the SideNav style block
to derive the shadow from the same token used by color (for example via
color-mix() or an existing derived token) so the glow stays consistent with
--brand-forest.


</details>
<!-- cr-comment:v1:b626e28a95ecb8bee7de5a57 -->
</blockquote></details>
<details>
<summary>frontend/src/components/TopNav.tsx (1)</summary><blockquote>
`189-191`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider aligning `textShadow` with the new token if `--brand-forest` value changed.**
The `textShadow` on Line 191 still hard-codes `rgba(26, 92, 42, 0.2)` (the old green). If `--brand-forest` now resolves to a different hex, the glow will mismatch the text color.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @frontend/src/components/TopNav.tsx around lines 189 - 191, The TopNav text
glow is still tied to the old green value instead of the updated brand token.
Update the textShadow in TopNav to derive from the same --brand-forest
color (or a matching token/derived value) so the glow stays consistent if the
token changes, and keep the change localized to the style block containing
textShadow.


</details>
<!-- cr-comment:v1:9e0a077a82eeeb536fc10598 -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.html (1)</summary><blockquote>
`118-118`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Minor brightness value drift from canonical token.**
The `.app-btn` hover uses `filter:brightness(1.06)` while `globals.css:206` specifies `brightness(1.05)` for `.btn--primary:hover`. For a document claiming to show the "source of truth," align this to the actual token value.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.html at line 118, The .app-btn:hover brightness
value is off from the canonical token, so align the hover filter in the
document’s button styles to match the shared primary button token used by
.btn--primary:hover. Update the hover rule in this HTML so it uses the same
brightness(1.05) value referenced by the source-of-truth stylesheet, keeping
the audit output consistent with the token definition.


</details>
<!-- cr-comment:v1:7ee7c49a0a7a45e89c9938e0 -->
</blockquote></details>
<details>
<summary>docs/button-shape-comparison.html (1)</summary><blockquote>
`100-100`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Qualify the "211 buttons" count if it's an estimate.**
The lede states the app is "already 95% there (211 buttons)" — if this is a static count from a moment in time, consider adding a date or noting it may shift as the codebase evolves. If it's precise, no change needed.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/button-shape-comparison.html at line 100, The lede in the button-shape
comparison page presents the “211 buttons” figure as a fixed fact, so qualify it
if it is only a snapshot. Update the wording in the lede content to either mark
the count as an estimate or add a time reference/snapshot note, using the
surrounding copy about “already 95% there” and the primary action button
comparison as the place to adjust.


</details>
<!-- cr-comment:v1:987b7211cb17959280d05d6d -->
</blockquote></details>
<details>
<summary>docs/frontend-rhythm-audit.md (1)</summary><blockquote>
`39-53`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_
**Spot-check line-numbered references for drift.**
The audit makes numerous precise line claims (`globals.css:60–64, 135–138, 146–149, 762–789`, etc.). During a multi-commit PR that restructures tokens and moves routes, these are likely to shift. Consider either:
- Verifying all cited line numbers against the final committed state, or
- Replacing absolute line numbers with symbol/selector references (e.g., `:root` token block, `.landing-page` block) that survive edits.
<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/frontend-rhythm-audit.md around lines 39 - 53, The audit’s references
rely on fragile absolute line numbers that may change as the token blocks move,
so update the cited locations to stable selector/symbol references instead.
Recheck the :root and .landing-page token sections in globals.css, and
replace the line-range citations with those block/selector names (or other
durable identifiers) so the drift notes still point to the correct definitions
after subsequent edits.


</details>
<!-- cr-comment:v1:59c5d6f829b656ebe3384127 -->
</blockquote></details>
<details>
<summary>frontend/src/components/ui/Button.tsx (1)</summary><blockquote>
`18-21`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_
**Consider typing `ref` explicitly for React 19 ref-as-prop support.**
`React.ButtonHTMLAttributes<HTMLButtonElement>` does not include `ref`, so consumers can't pass `ref={...}` to `<Button>` without a TS error, even though React 19 supports ref-as-prop at runtime. Adding `ref?: React.Ref<HTMLButtonElement>` to the type would future-proof this primitive for callers needing DOM access (e.g., autofocus, imperative scroll/focus).
<details>
<summary>♻️ Suggested typing addition</summary>
```diff
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: Variant;
size?: Size;
+ ref?: React.Ref<HTMLButtonElement>;
}) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Button.tsx` around lines 18 - 21, The Button
component props type currently omits ref, so callers get a TS error when using
ref-as-prop. Update the Button component signature to explicitly include ref
support alongside React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure
the exported Button primitive accepts React.Ref<HTMLButtonElement> while
preserving the existing variant and size props.
frontend/src/components/ui/Toggle.tsx (1)

30-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Group semantics dropped from prior tablist implementation.

Each option is a standalone aria-pressed button; there's no role="radiogroup"/role="tablist" (or equivalent) on the wrapper conveying that these options are mutually exclusive. The diff notes this replaces an implementation that had explicit tablist/tab/aria styling, so this is a semantic downgrade for assistive tech, though the control remains operable (each button still exposes its own label and pressed state).

Worth a follow-up to add role="radiogroup" + aria-label, with role="radio"/aria-checked on options (or role="tablist"/role="tab" if that fits the calling contexts better), since this is now a broadly-adopted shared primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/ui/Toggle.tsx` around lines 30 - 55, The Toggle
shared primitive lost the group-level semantics from the previous tablist
behavior. Update the wrapper in Toggle so it exposes a mutual-exclusion
container role such as radiogroup or tablist with an accessible label, and
change each option button to the matching child role/state (radio with
aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
frontend/src/components/Gradebook/EditWeightsModal.tsx (1)

257-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancel button not migrated to shared Button.

The Cancel button stays a bare native <button> while Save now uses the shared Button, giving them different sizing/styling. Same pattern repeats in LetterScaleEditor.tsx (line 96) and Course.tsx (line 171). Consider migrating Cancel to <Button variant="secondary" size="sm"> for visual consistency in modal footers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx` at line 257, The
modal footer still uses a native Cancel <button> in EditWeightsModal, which
leaves it visually inconsistent with the shared Button used for Save. Update the
Cancel action to use the shared Button component with the secondary/sm styling
so it matches the existing footer pattern, and apply the same migration in
LetterScaleEditor and Course where the bare Cancel buttons remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes-tour.html`:
- Around line 64-66: The legend entry for the accent token is mismatched in the
changes tour markup: the swatch in the changes-tour legend is using the
forest-bright hex while still labeling it as --accent. Update the legend item in
the changes-tour HTML so the dot color and label match the canonical token
system, either by changing the swatch to the sage accent value or by renaming
the label to --forest-bright, and verify the surrounding legend entries remain
consistent with the token names used elsewhere.
In `@docs/frontend-component-consistency-audit.md`:
- Around line 35-53: The `--accent` description in this audit is stale and still
reflects the old sage value, while the current token is already defined as the
brighter forest variant in `globals.css`. Update the `--accent` entry in this
document to match the current token definition and comment, or explicitly label
the section as historical/pre-unification baseline so it does not conflict with
the live design system state. Use the `--accent` token and the `globals.css`
definition as the reference points when revising the wording.
In `@frontend/src/components/ui/FilterPills.tsx`:
- Around line 30-40: The Pill button used by FilterPills currently behaves like
a default submit button and does not expose its active state to assistive tech.
Update the Pill component to render as a non-submitting button by setting
type="button", and add a pressed-state ARIA attribute driven by its
active/selected prop so the state is announced correctly. Focus the fix in the
Pill component itself, since FilterPills only passes active and onClick through
to Pill.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 20-29: The shared Toggle container currently renders as an
inline-flex box without wrapping, which can cause long or dynamic option lists
to overflow horizontally. Update the Toggle component’s outer container in
Toggle.tsx to restore wrapping behavior by adding flex-wrap (or equivalent
wrapping styles) alongside the existing inline-flex styling, using the Toggle
primitive itself so callers like SemesterChips keep their options flowing onto
new lines when needed.
---
Outside diff comments:
In `@docs/frontend-component-consistency-audit.md`:
- Around line 86-89: The “Sage `--accent`” decision is no longer undecided,
since `globals.css` already points `--accent` to `var(--brand-forest-bright)`.
Update the “Decision needed” section in the audit to reflect that the accent has
been resolved toward forest, or explicitly label this as historical deliberation
if the intent is to preserve the pre-decision discussion. Keep the wording
consistent with the existing `globals.css` and `--accent` references so the
status is unambiguous.
---
Nitpick comments:
In `@docs/button-shape-comparison.html`:
- Line 100: The lede in the button-shape comparison page presents the “211
buttons” figure as a fixed fact, so qualify it if it is only a snapshot. Update
the wording in the lede content to either mark the count as an estimate or add a
time reference/snapshot note, using the surrounding copy about “already 95%
there” and the primary action button comparison as the place to adjust.
In `@docs/frontend-rhythm-audit.html`:
- Line 118: The `.app-btn:hover` brightness value is off from the canonical
token, so align the hover filter in the document’s button styles to match the
shared primary button token used by `.btn--primary:hover`. Update the hover rule
in this HTML so it uses the same `brightness(1.05)` value referenced by the
source-of-truth stylesheet, keeping the audit output consistent with the token
definition.
In `@docs/frontend-rhythm-audit.md`:
- Around line 39-53: The audit’s references rely on fragile absolute line
numbers that may change as the token blocks move, so update the cited locations
to stable selector/symbol references instead. Recheck the `:root` and
`.landing-page` token sections in `globals.css`, and replace the line-range
citations with those block/selector names (or other durable identifiers) so the
drift notes still point to the correct definitions after subsequent edits.
In `@docs/superpowers/handoffs/2026-06-30-selector-consolidation.md`:
- Around line 52-58: Add the eslint-suppressions lesson to the token-unification
plan so future work remembers that moving or renaming files with grandfathered
lint suppressions requires re-homing entries in
frontend/eslint-suppressions.json. Update Task 4 in
2026-06-30-token-unification.md, or add a short “Lessons learned” appendix, and
cross-reference the file-path-based suppression behavior so agentic workers
don’t miss it when changing routes or component locations.
In `@docs/superpowers/plans/2026-06-30-token-unification.md`:
- Around line 209-218: Update the plan entry for `.glass-input:focus` so the
documented box-shadow color matches the canonical `--brand-forest` token value
used in `globals.css` and the `glass-input:focus` rule. Replace the mismatched
`rgba(27, 109, 66, 0.2)` reference with the same `rgba(27, 108, 66, 0.2)` value
that the actual implementation uses, keeping the note aligned with the
`--brand-forest` token and the `glass-input:focus` selector.
- Around line 270-279: The plan’s public layout example uses the wrong scope
class name, so update the documented `PublicLayout` wrapper to reflect the final
marketing surface class used in `globals.css`. Reference `PublicLayout` and make
the plan say the subtree should be wrapped with `public-surface`, with
`landing-page` mentioned as an additional class only where mesh-specific styling
is needed on the landing page itself.
- Around line 298-304: The Task 4 Step 4 verification block only checks the
build and missed the eslint suppression breakage seen after route moves. Update
the verification commands in the route move plan to include an explicit npx
eslint . run (alongside the existing build check) so the step validates both
compilation and linting after the marketing routes are moved.
In `@docs/superpowers/specs/2026-06-30-component-system-phase2-design.md`:
- Around line 75-79: The migration list still treats ModelToggle as a Toggle
target even though the implementation decision was to keep ModelToggle
specialized for its sliding animation and Fast/Smart color semantics. Update the
spec entry that mentions ModelToggle so it matches the actual decision by
removing it from the Toggle migration list and either marking it as kept
specialized or moving it into a Deferred/Decided section alongside the relevant
Learn.tsx toggle items.
- Around line 48-50: The Toggle spec is missing fields that are part of the
final API, so update the `<Toggle>` documentation to reflect `size: sm|md` and
`options` entries including optional `title?`. Use the existing
`components/ui/Toggle.tsx` section to add these props, or explicitly note that
the spec predates these additions so readers know the API evolved. Keep the
description aligned with the referenced Toggle replacement usage sites.
- Around line 36-47: The Button sizing contract is inconsistent between this
spec and the handoff reference, since ButtonProps only lists sm, md, and lg
while the handoff expects xl as well. Update the Button spec to either
explicitly add xl to the documented size options and related class mapping, or
clearly note that xl is not supported and the handoff should be corrected. Keep
the description aligned with components/ui/Button.tsx and the ButtonProps
definition so the size contract is unambiguous.
In `@docs/superpowers/specs/2026-06-30-token-unification-design.md`:
- Around line 72-79: Update the “Green collapse” section to match the final
`globals.css` implementation: `--accent` is no longer sage and should be
described as re-pointed to `--brand-forest-bright` for the forest highlight
role. Adjust the text around `--accent`, `--brand-forest`, and `--brand-glow` in
this spec so it reflects the Phase 1 token mapping used by the final
implementation and does not imply sage was retained.
- Around line 81-95: Clarify the `(public)` token-scoping guidance so
`layout.tsx` and related docs distinguish `.landing-page` from the token host:
`landing-page` should no longer be described as carrying marketing tokens, but
as the visual/utility scope for mesh and `.landing-*` classes, while
`.public-surface` is the actual token host for the public routes. Update the
spec text around `(public)/layout.tsx`, `.landing-page`, and `.public-surface`
to reflect this split and keep the existing route-group migration intent clear.
- Around line 58-69: The retirement table entry for `--brand-text2` is
inconsistent with the implementation plan. Update the spec in this
token-unification section to use a single migration target, preferably
`var(--text-dim)` for all `--brand-text2` consumers, and remove the ambiguous
`--text-muted` mention unless you explicitly document it as a future follow-up.
Keep the guidance aligned with the surrounding token refs/actions in this table
so the `--brand-text2` migration is mechanically clear.
In `@frontend/src/components/Gradebook/EditWeightsModal.tsx`:
- Line 257: The modal footer still uses a native Cancel <button> in
EditWeightsModal, which leaves it visually inconsistent with the shared Button
used for Save. Update the Cancel action to use the shared Button component with
the secondary/sm styling so it matches the existing footer pattern, and apply
the same migration in LetterScaleEditor and Course where the bare Cancel buttons
remain.
In `@frontend/src/components/SideNav.tsx`:
- Around line 129-131: The SideNav styling still hard-codes the old green in the
text glow, so `textShadow` can drift from the updated `--brand-forest` color.
Update the `SideNav` style block to derive the shadow from the same token used
by `color` (for example via `color-mix()` or an existing derived token) so the
glow stays consistent with `--brand-forest`.
In `@frontend/src/components/TopNav.tsx`:
- Around line 189-191: The TopNav text glow is still tied to the old green value
instead of the updated brand token. Update the `textShadow` in `TopNav` to
derive from the same `--brand-forest` color (or a matching token/derived value)
so the glow stays consistent if the token changes, and keep the change localized
to the style block containing `textShadow`.
In `@frontend/src/components/ui/Button.tsx`:
- Around line 18-21: The Button component props type currently omits ref, so
callers get a TS error when using ref-as-prop. Update the Button component
signature to explicitly include ref support alongside
React.ButtonHTMLAttributes<HTMLButtonElement>, and make sure the exported Button
primitive accepts React.Ref<HTMLButtonElement> while preserving the existing
variant and size props.
In `@frontend/src/components/ui/Toggle.tsx`:
- Around line 30-55: The Toggle shared primitive lost the group-level semantics
from the previous tablist behavior. Update the wrapper in Toggle so it exposes a
mutual-exclusion container role such as radiogroup or tablist with an accessible
label, and change each option button to the matching child role/state (radio
with aria-checked, or tab with tablist semantics) while preserving the existing
onChange and visual styling logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd3a88a8-8c68-4b91-9549-fefcfe116af4

📥 Commits

Reviewing files that changed from the base of the PR and between d86edde and ab09a30.

📒 Files selected for processing (47)
  • docs/button-shape-comparison.html
  • docs/changes-tour.html
  • docs/frontend-component-consistency-audit.md
  • docs/frontend-rhythm-audit.html
  • docs/frontend-rhythm-audit.md
  • docs/superpowers/followups/2026-06-30-token-unification-followups.md
  • docs/superpowers/handoffs/2026-06-30-selector-consolidation.md
  • docs/superpowers/plans/2026-06-30-token-unification.md
  • docs/superpowers/specs/2026-06-30-component-system-phase2-design.md
  • docs/superpowers/specs/2026-06-30-token-unification-design.md
  • frontend/eslint-suppressions.json
  • frontend/src/app/(public)/about/page.tsx
  • frontend/src/app/(public)/careers/[slug]/ApplyForm.tsx
  • frontend/src/app/(public)/careers/[slug]/page.tsx
  • frontend/src/app/(public)/careers/jobs.ts
  • frontend/src/app/(public)/careers/page.tsx
  • frontend/src/app/(public)/layout.tsx
  • frontend/src/app/(public)/page.tsx
  • frontend/src/app/(public)/privacy/page.tsx
  • frontend/src/app/(public)/terms/page.tsx
  • frontend/src/app/globals.css
  • frontend/src/components/Dialog.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/SemesterChips.tsx
  • frontend/src/components/Gradebook/SyllabusUploadFlow.tsx
  • frontend/src/components/HowItWorks.tsx
  • frontend/src/components/SideNav.tsx
  • frontend/src/components/SignInModal.tsx
  • frontend/src/components/TitleFlair.tsx
  • frontend/src/components/TopNav.tsx
  • frontend/src/components/screens/Achievements.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • frontend/src/components/screens/Learn.tsx
  • frontend/src/components/screens/Library.tsx
  • frontend/src/components/screens/Social.tsx
  • frontend/src/components/screens/Study.tsx
  • frontend/src/components/screens/Tree.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Chip.tsx
  • frontend/src/components/ui/FilterPills.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/components/ui/index.ts

Comment on lines +64 to +66
<span class="sw"><span class="dot" style="background:#1B6C42"></span>--brand-forest · primary action</span>
<span class="sw"><span class="dot" style="background:#2D8F5C"></span>--accent · highlight (was sage)</span>
<span class="sw"><span class="dot" style="background:#3a7d4e"></span>--positive · status (mastery/grade-A)</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the --accent token color in the legend.

The legend labels #2D8F5C as --accent · highlight (was sage), but this hex is --forest-bright in the canonical token system. --accent is the distinct sage token (#8a9a5b per globals.css and the PR objective to keep it separate from the forest family). Either:

  • Replace the dot color with #8a9a5b and keep the --accent label, or
  • Correct the label to --forest-bright · hover/bright.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/changes-tour.html` around lines 64 - 66, The legend entry for the accent
token is mismatched in the changes tour markup: the swatch in the changes-tour
legend is using the forest-bright hex while still labeling it as --accent.
Update the legend item in the changes-tour HTML so the dot color and label match
the canonical token system, either by changing the swatch to the sage accent
value or by renaming the label to --forest-bright, and verify the surrounding
legend entries remain consistent with the token names used elsewhere.

Comment on lines +35 to +53
## 2. Greens — 5 active, two pairs indistinguishable

12 greens exist (8 tokens + 4 hard-coded); **5 are in active semantic use**:

| Green | Token | Uses | Role | Problem |
|---|---|---|---|---|
| `#1B6C42` | `--brand-forest` | 45 | primary brand / action / rarity-uncommon | — |
| `#8a9a5b` | `--accent` (sage) | **125** | UI accents, reactions, focus, "Upload syllabus" btn | **the most-used "green" is sage, not the brand forest** — this is the main "greens feel inconsistent" culprit |
| `#3e8030` | `--c-sage` = `--grade-a` | 4 | grade-A display | **9 hex points from forest** — indistinguishable; on gradebook it reads as the brand green |
| `#4a7d5c` | `--state-mastery` | 3 | dashboard mastery | barely used; a 4th near-forest green |
| `#1a5c2a` | *(hard-coded, no token)* | 12 | landing logo, TopNav | **not in the token system** — brand drift risk |

**The core issue:** "a green affordance" renders as forest in one place, sage in another, grade-green in a third — because the greens are split by *accidental history*, not by *role*. The 125-use sage `--accent` makes the app's de-facto "main green" a muted yellow-green that clashes with the forest brand.

**Proposed green-by-role collapse (3 roles):**
1. **Brand / primary action** → `--brand-forest` (one green for all primary buttons, active nav, brand marks). Tokenize the hard-coded `#1a5c2a` into it.
2. **Positive status** (mastery, grade-A, success) → **one** status-green. Merge `--grade-a`/`--c-sage` and `--state-mastery` into a single `--positive` (distinct enough from forest to read as "status", or just = forest if we want them unified).
3. **Decorative accent** → decide sage's fate: either keep `--accent` sage as a deliberately *different* hue (not green-family) so it stops competing with forest, or retire it toward forest. Recommend: **shift accent off the green family** (it's currently a near-green that muddies everything) OR rename it so its role is explicit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale --accent token description.

The audit describes --accent as sage #8a9a5b, but globals.css already defines --accent: var(--brand-forest-bright) (#2D8F5C) with the comment "sage retired → brighter forest." This document appears to capture pre-Phase 1 state while being added in the Phase 1 changeset. Either update to current values or mark explicitly as historical/pre-unification baseline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/frontend-component-consistency-audit.md` around lines 35 - 53, The
`--accent` description in this audit is stale and still reflects the old sage
value, while the current token is already defined as the brighter forest variant
in `globals.css`. Update the `--accent` entry in this document to match the
current token definition and comment, or explicitly label the section as
historical/pre-unification baseline so it does not conflict with the live design
system state. Use the `--accent` token and the `globals.css` definition as the
reference points when revising the wording.

Comment threadfrontend/src/components/ui/FilterPills.tsx
Comment threadfrontend/src/components/ui/Toggle.tsx
AndresL230and others added 2 commits July 1, 2026 01:29
- Nav 'Get Started': keep the lg 14px label but reduce the box padding
(per-button override, leaves the shared .btn--lg used elsewhere alone).
- Hero 'Sign up for Beta Testing': lg -> xl so it grows in both text (16px)
and box, making it the dominant hero CTA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overview
Fills gaps in the NEXT_PUBLIC_LOCAL_MODE fixtures (frontend/src/lib/localData.ts)
so the "Local Dev" account renders real data for UI testing:
- gradebook per-course detail (weighted categories, graded + ungraded
assignments, drop-lowest) for c1/c2/c3, consistent with the /summary cards
- room "CS Study Group" overview: 3 members with knowledge graphs + AI
summary, plus chat messages, activity feed, and a populated directory
- interactive stubs (send/react/edit/delete/leave/kick/match) + gradescope
status stub so the tabs don't warn/error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Jose-Gael-Cruz-LopezJose-Gael-Cruz-Lopez left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of the token-unification refactor. Both code fixes below are already pushed in #297 (a stacked PR into this branch).

1. (Major) Toggle overflow regression — see inline comment on Toggle.tsx.

2. (Minor) Pill bare <button>Pill.tsx (not in this PR's diff, so commenting here): it renders <button onClick> with no type (defaults to submit, so a Pill inside any <form> submits it) and no pressed state for assistive tech. #297 adds type="button" + aria-pressed={active}.

On CodeRabbit's --accent comments (not bugs):globals.css defines --accent: var(--brand-forest-bright) (#2D8F5C), so the changes-tour.html legend is correct and the audit doc is intentionally describing the pre-change state. No change needed.

Heads-up: the Canopy Engineering Style Guide still documents --accent:#8a9a5b (sage) in its token block + palette table — it'll be stale once this merges. I've proposed the update in Canopy.

return (
<div
style={{
display: "inline-flex",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major — overflow regression on dynamic lists. This container is inline-flex with no flex-wrap. On main, SemesterChips (the one caller passing an unbounded semesters list) rendered an explicit display:flex; flex-wrap:wrap tablist — so as semesters accumulate each term (and #280 ingests more BU terms), this now overflows horizontally instead of wrapping on Gradebook + course-planner.

Fixed in #297: an opt-in wrap prop (default off, so fixed-set segmented controls like 2D/3D and teaching modes are unaffected) that SemesterChips sets.

Addresses CodeRabbit review on #286:
- Toggle: flexWrap so SemesterChips' dynamic list wraps instead of
overflowing horizontally (no effect on fixed-option callers).
- Pill: type="button" (stops enclosing-form submit) and aria-pressed
to expose active state to assistive tech.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndresL230
AndresL230 merged commit 9f193b8 into mainJul 2, 2026
5 of 6 checks passed

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/lib/localData.ts (1)

206-260: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

drop_lowest configured but never exercised in mock data.

c1-hw and c3-hw both set drop_lowest: 1, but their category_grade values equal the plain average of all graded assignments in the category (e.g. c1-hw: (89+84)/2 = 86.5 matches category_grade: 0.865; c3-hw: (93+88)/2 = 90.5 matches 0.905), and dropped_assignment_ids is [] for every course. So the drop is never reflected in either the computed grade or the badge list, meaning the "dropped assignment" UI state — which this new mock data set out to make testable — is never actually exercised.

💡 Suggested fix
 assignments: [
gA('gb-c1-1', 'c1', 'c1-hw', 'Problem Set 5 — Series', 'homework', 100, 89, -18),
gA('gb-c1-2', 'c1', 'c1-hw', 'Problem Set 6 — Convergence', 'homework', 100, 84, -9),
...
],
- dropped_assignment_ids: [],+ dropped_assignment_ids: ['gb-c1-2'],

Adjust category_grade accordingly (e.g. to reflect only the non-dropped assignment) if the UI trusts the numeric field independently of the badge list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/localData.ts` around lines 206 - 260, The mock courses in
localData.ts define drop_lowest on c1-hw and c3-hw but never actually simulate a
dropped item. Update the seeded data so the affected categories’ category_grade
values reflect the dropped-lowest behavior, and populate dropped_assignment_ids
with the assignment id(s) that should be dropped. Keep the changes aligned in
the course objects for c1 and c3 so the grade display and dropped-assignment
badges exercise the same state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/localData.ts`:
- Around line 206-260: The mock courses in localData.ts define drop_lowest on
c1-hw and c3-hw but never actually simulate a dropped item. Update the seeded
data so the affected categories’ category_grade values reflect the
dropped-lowest behavior, and populate dropped_assignment_ids with the assignment
id(s) that should be dropped. Keep the changes aligned in the course objects for
c1 and c3 so the grade display and dropped-assignment badges exercise the same
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bc1902-2f52-48db-9e6f-2a6e7c74578c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d11e29 and 9313888.

📒 Files selected for processing (3)
  • frontend/src/components/Pill.tsx
  • frontend/src/components/ui/Toggle.tsx
  • frontend/src/lib/localData.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ui/Toggle.tsx

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.

2 participants

@AndresL230@Jose-Gael-Cruz-Lopez