feat(ui): add dark mode support for server dashboard#450
Conversation
- Add Tailwind CSS 4 dark variant (class-based via .dark toggle) - Add system preference detection + localStorage persistence - Add 🌙/☀️ toggle button in nav bar - Apply dark: variants to all 14 components/pages - Configure Recharts theme for dark mode (axis stroke, tooltip style) - Use slate palette: slate-950 bg, slate-900 cards, slate-800 borders Lint, typecheck, build, and all 11 tests pass.
- Fix invisible checkmark in All Caught Up (add text-green-600/dark:text-green-400) - Fix login page missing dark mode (add ThemeInitializer to App.tsx) - Replace unicode toggle with animated switch + icon labels
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughDark mode support is added across the frontend. A Tailwind ChangesDark mode support
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@server/frontend/src/App.tsx`:
- Around line 37-47: The ThemeInitializer component and Layout.tsx both
independently initialize dark mode by reading localStorage and system
preferences, creating a race condition where Layout mounts after
ThemeInitializer and overwrites the theme class. To fix this, keep the complete
theme initialization logic in the ThemeInitializer function (which reads both
stored preference and system preference, then sets the dark class on the
document element), and remove the duplicate initialization code from Layout.tsx
lines 13-20. Modify Layout.tsx to only handle the toggle state management and
localStorage persistence when the user explicitly changes their theme
preference, making ThemeInitializer the single source of truth for initial theme
setup.
- Around line 38-45: The useEffect hook in App.tsx applies the dark class after
the initial render, causing a brief flash of unstyled content on page load. To
fix this, add a synchronous blocking script tag in the index.html file that
executes before React hydrates. This script should read
localStorage["cq-dark-mode"] and apply the dark class to
document.documentElement immediately using the same logic as the useEffect
(checking localStorage, falling back to system preference via matchMedia). Place
this script in the head section before any stylesheets to ensure the correct
theme is applied synchronously before rendering, eliminating the flash.
In `@server/frontend/src/components/KnowledgeUnitModal.tsx`:
- Around line 14-17: The confidenceColor function that takes a confidence value
and returns tailwind color classes based on thresholds is duplicated identically
in both KnowledgeUnitModal.tsx and ReviewCard.tsx. Extract this confidenceColor
helper function to a shared utility module (such as utils.ts in the frontend/src
directory), then import and use it in both KnowledgeUnitModal.tsx and
ReviewCard.tsx instead of having the duplicate function definitions in each
file.
In `@server/frontend/src/components/Layout.tsx`:
- Line 78: The span element containing the emoji conditional render (dark ? "🌙"
: "☀️") should be replaced with inline SVG icons to ensure consistent visual
rendering across platforms and improve accessibility for screen readers. Replace
the emoji string with appropriate SVG icon components that represent moon and
sun icons, maintaining the same conditional logic based on the dark variable to
switch between the two icons.
- Around line 22-25: The dark mode state initialization causes a race condition
where the default false value overwrites the stored user preference. Remove the
entire first useEffect hook (the initialization effect that reads from
localStorage) since ThemeInitializer in App.tsx already handles dark mode
initialization. Instead, initialize the dark state variable by reading from the
DOM class or localStorage directly rather than defaulting to false. Keep the
second useEffect hook intact as it correctly handles updating the DOM and
localStorage whenever the dark state changes.
In `@server/frontend/src/pages/DashboardPage.tsx`:
- Around line 240-259: The Recharts components XAxis, YAxis, Tooltip, and Legend
in DashboardPage.tsx use hardcoded dark-mode colors that break the visual
hierarchy in light mode. Detect the current theme state (either by reading a
theme context, checking a `.dark` class on the page, or using a theme hook) and
conditionally apply color values for both dark and light modes to the tick,
stroke, backgroundColor, and color properties of these components. Ensure axis
labels, tooltip backgrounds, and legend text have sufficient contrast and
maintain visual consistency across both theme modes.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 488240aa-4f29-4ed9-97cf-b84f97ca2949
📒 Files selected for processing (15)
server/frontend/src/App.tsxserver/frontend/src/components/ConfidenceBadge.tsxserver/frontend/src/components/DomainTags.tsxserver/frontend/src/components/DragIndicators.tsxserver/frontend/src/components/FilteredListModal.tsxserver/frontend/src/components/KnowledgeUnitModal.tsxserver/frontend/src/components/Layout.tsxserver/frontend/src/components/ReviewActions.tsxserver/frontend/src/components/ReviewCard.tsxserver/frontend/src/components/StatusBadge.tsxserver/frontend/src/index.cssserver/frontend/src/pages/ApiKeysPage.tsxserver/frontend/src/pages/DashboardPage.tsxserver/frontend/src/pages/LoginPage.tsxserver/frontend/src/pages/ReviewPage.tsx
| function ThemeInitializer({ children }: { children: React.ReactNode }) { | ||
| useEffect(() => { | ||
| const stored = localStorage.getItem("cq-dark-mode") | ||
| const dark = | ||
| stored !== null | ||
| ? stored === "true" | ||
| : window.matchMedia("(prefers-color-scheme: dark)").matches | ||
| document.documentElement.classList.toggle("dark", dark) | ||
| }, []) | ||
| return <>{children}</> | ||
| } |
There was a problem hiding this comment.
Critical: Duplicate theme initialisation creates race condition.
Both ThemeInitializer (here) and Layout.tsx (lines 13-20) independently read localStorage["cq-dark-mode"] and system preferences to initialise dark mode. This duplication causes:
- Race condition:
Layoutmounts afterThemeInitializer, so it will re-read and re-apply the class, potentially overwriting any user interaction that happened between the two initialisations. - Maintenance burden: Changes to the initialisation logic must be synchronised across two locations.
- Two sources of truth: The initial state could theoretically differ if localStorage is modified between the two reads.
Consolidate initialisation in ThemeInitializer and remove lines 13-20 from Layout.tsx. Let Layout only manage the toggle state and persistence when the user explicitly changes the preference.
🤖 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 `@server/frontend/src/App.tsx` around lines 37 - 47, The ThemeInitializer
component and Layout.tsx both independently initialize dark mode by reading
localStorage and system preferences, creating a race condition where Layout
mounts after ThemeInitializer and overwrites the theme class. To fix this, keep
the complete theme initialization logic in the ThemeInitializer function (which
reads both stored preference and system preference, then sets the dark class on
the document element), and remove the duplicate initialization code from
Layout.tsx lines 13-20. Modify Layout.tsx to only handle the toggle state
management and localStorage persistence when the user explicitly changes their
theme preference, making ThemeInitializer the single source of truth for initial
theme setup.
…arts colors - Extract confidenceColor to shared utils.ts, remove duplicate from KnowledgeUnitModal and ReviewCard - Remove first useEffect in Layout; use lazy useState initializer for dark mode - Make Recharts colors (axis, tooltip, legend) respond to current theme
Summary
This PR implements comprehensive dark mode support across the cq web dashboard, including theme persistence and system preference detection.
Summary by CodeRabbit
New Features
Style