Skip to content

feat: dark light transition#194

Merged
nikilok merged 15 commits into
mainfrom
feat/dark-light-transition
Jun 26, 2026
Merged

feat: dark light transition#194
nikilok merged 15 commits into
mainfrom
feat/dark-light-transition

Conversation

@nikilok

@nikilok nikilok commented Jun 26, 2026

Copy link
Copy Markdown
Owner

The expecto patronum effect inspired by Harry Potter, is what the light / dark theme tries to do.
The light from the city of London sets off the patronus charm.

Summary by CodeRabbit

  • New Features

    • Added animated light/dark theme transitions for a smoother appearance when switching themes.
    • Improved support for reduced-motion preferences so transitions can adapt to user settings.
  • Bug Fixes

    • Fixed theme changes so they stay in sync during automatic and manual mode updates.
    • Improved consistency of theme behavior across different screen sizes and page states.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
learn-tanstack-start Ready Ready Preview, Comment Jun 26, 2026 11:24pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a shared reduced-motion utility, wires animated theme switching through a transition module, adds WebGPU and 2D canvas rendering paths, exposes skyline data for bloom origin lookup, and enables a Claude plugin setting.

Changes

Theme transition flow

Layer / File(s) Summary
Shared reduced-motion helper
apps/web/src/utils.ts, apps/web/src/components/HmrcResults.tsx
prefersReducedMotion() is added in utils.ts, and HmrcResults.tsx imports it instead of the local helper.
Transition setup
apps/web/src/components/LondonSkyline.tsx, apps/web/src/theme-transition.ts
The skyline SVG adds data-london-skyline, data-sun-x, and data-sun-y, and theme-transition.ts adds timing constants, bloom-origin lookup, color maps, shared helpers, and transition state.
Transition orchestration
apps/web/src/theme-transition.ts
runThemeTransition() selects the active theme map pair and starts the WebGPU or 2D transition path, and cancelThemeTransition() tears down an active run.
WebGPU transition path
apps/web/src/theme-transition.ts, apps/web/src/theme-transition.wgsl
The WebGPU path uploads theme textures and drives the WebGPU path, and theme-transition.wgsl implements the transition shader.
2D fallback
apps/web/src/theme-transition.ts
theme-transition.ts adds the full-screen canvas fallback transition, reveal ordering, and teardown.
Animated theme toggle
apps/web/src/components/ThemeToggle.tsx
ThemeToggle.tsx resolves auto, routes animated swaps through runThemeTransition(), and calls the animated path from OS and user mode changes.

Plugin settings

Layer / File(s) Summary
Enable plugin
.claude/settings.json
enabledPlugins adds frontend-design@claude-plugins-official with a true value.

Sequence Diagram(s)

sequenceDiagram
  participant ThemeToggle
  participant runThemeTransition
  participant cancelThemeTransition
  participant startWebGPU
  participant "2D canvas fallback" as CanvasFallback
  participant swap

  ThemeToggle->>runThemeTransition: applyThemeMode(mode, true)
  runThemeTransition->>cancelThemeTransition: stop any in-flight run
  alt WebGPU available
    runThemeTransition->>startWebGPU: start animated transition
    startWebGPU->>swap: invoke at cover threshold
  else WebGPU unavailable
    runThemeTransition->>CanvasFallback: start fallback transition
    CanvasFallback->>swap: invoke at cover threshold
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • nikilok/learn-tanstack-start#175: Both PRs touch apps/web/src/components/HmrcResults.tsx around the reduced-motion logic that is now centralized through prefersReducedMotion().
  • nikilok/learn-tanstack-start#182: Both PRs touch apps/web/src/components/LondonSkyline.tsx, and this PR extends that component with sun-position data used by the transition bloom origin.

Suggested labels

transition

Poem

(_/)
(•‿•) I booped the moonlit switch tonight,
/ >🌗 and colors hopped from dark to light.
A skyline winked, the bloom began,
then dither-dots danced across the land.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a dark/light theme transition.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dark-light-transition

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/theme-transition.ts (1)

106-118: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fail fast on malformed packed colour maps.

parseHexMap() silently turns short or non-hex slices into NaN, which can become black/invalid colours in both renderers. Guard the generated asset format before parsing.

Suggested guard
 function parseHexMap(hex: string, shift: number): number[][] {
+  const expectedLength = MAP_N * 6;
+  if (hex.length !== expectedLength || /[^0-9a-f]/i.test(hex)) {
+    throw new Error(
+      `Theme transition colour map must be ${expectedLength} hex characters`,
+    );
+  }
   const map: number[][] = [];
🤖 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 `@apps/web/src/theme-transition.ts` around lines 106 - 118, parseHexMap
currently accepts short or invalid packed color data and can propagate NaN into
the theme transition renderers; add a fail-fast validation step before the loop
in parseHexMap to verify the hex string matches the expected packed MAP_N rrggbb
format and contains only valid hex digits, and throw a clear error if it does
not. Keep the fix localized to parseHexMap and use its existing channel helper
and MAP_N constant to ensure malformed generated assets are rejected before any
parsing happens.
🤖 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 `@apps/web/src/theme-transition.ts`:
- Around line 549-598: `runThemeTransition` still starts the animated
WebGPU/Canvas2D transition even when `prefersReducedMotion()` is enabled, so the
reduced-motion preference is not fully respected. Add an early short-circuit in
`runThemeTransition` before any renderer setup so it immediately calls `swap()`
and returns when reduced motion is requested. Make sure this bypass applies to
both the WebGPU path and the `startCanvas2D` fallback, and keep the existing
`teardown()`/token flow untouched for non-reduced-motion cases.

---

Nitpick comments:
In `@apps/web/src/theme-transition.ts`:
- Around line 106-118: parseHexMap currently accepts short or invalid packed
color data and can propagate NaN into the theme transition renderers; add a
fail-fast validation step before the loop in parseHexMap to verify the hex
string matches the expected packed MAP_N rrggbb format and contains only valid
hex digits, and throw a clear error if it does not. Keep the fix localized to
parseHexMap and use its existing channel helper and MAP_N constant to ensure
malformed generated assets are rejected before any parsing happens.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 49978c0b-74a9-49ed-919f-7d4755d3887d

📥 Commits

Reviewing files that changed from the base of the PR and between d5881bb and daf23c6.

📒 Files selected for processing (7)
  • .claude/settings.json
  • apps/web/src/components/HmrcResults.tsx
  • apps/web/src/components/LondonSkyline.tsx
  • apps/web/src/components/ThemeToggle.tsx
  • apps/web/src/theme-transition.ts
  • apps/web/src/theme-transition.wgsl
  • apps/web/src/utils.ts

Comment thread apps/web/src/theme-transition.ts
@nikilok nikilok merged commit 6870727 into main Jun 26, 2026
5 checks passed
@nikilok nikilok deleted the feat/dark-light-transition branch June 26, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant