Skip to content

feat(ui): add dark mode support for server dashboard#450

Open
concertypin wants to merge 3 commits into
mozilla-ai:mainfrom
concertypin:feat/dark-mode
Open

feat(ui): add dark mode support for server dashboard#450
concertypin wants to merge 3 commits into
mozilla-ai:mainfrom
concertypin:feat/dark-mode

Conversation

@concertypin

@concertypin concertypin commented Jun 20, 2026

Copy link
Copy Markdown

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

    • Added dark mode support across the application, including a new toggle in the navigation bar.
    • Dark mode preference is persisted and restored on subsequent visits.
  • Style

    • Updated the look and feel of key components and pages to render correctly in both themes (including badges, modals, cards, lists, charts, and forms), with consistent dark-mode colour and contrast.

- 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
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62ad067e-b895-442e-a11b-32fef6ee54f8

📥 Commits

Reviewing files that changed from the base of the PR and between add4a51 and 6c79a34.

📒 Files selected for processing (5)
  • server/frontend/src/components/KnowledgeUnitModal.tsx
  • server/frontend/src/components/Layout.tsx
  • server/frontend/src/components/ReviewCard.tsx
  • server/frontend/src/pages/DashboardPage.tsx
  • server/frontend/src/utils.ts

Walkthrough

Dark mode support is added across the frontend. A Tailwind @custom-variant dark is defined in index.css, a ThemeInitializer component initialises the dark class from localStorage or prefers-color-scheme on mount, Layout gains a persisted toggle button, and dark: Tailwind classes are applied to every shared component and page. A confidenceColor utility is exported to map confidence scores to dark-aware colour classes.

Changes

Dark mode support

Layer / File(s) Summary
Dark mode infrastructure: CSS variant, App initialiser, Layout toggle
server/frontend/src/index.css, server/frontend/src/App.tsx, server/frontend/src/components/Layout.tsx
Defines the Tailwind @custom-variant dark scoped to .dark and its descendants. Introduces ThemeInitializer in App.tsx that reads localStorage["cq-dark-mode"] or prefers-color-scheme and applies the dark class before routes render. Adds dark state and persistence logic to Layout.tsx along with a toggle button using role="switch" and aria-checked.
Shared confidence colour utility
server/frontend/src/utils.ts
Adds confidenceColor exported helper that maps numeric confidence scores into threshold-based Tailwind CSS text colour classes for light and dark themes, imported and used by KnowledgeUnitModal and ReviewCard.
Shared component dark mode styling
server/frontend/src/components/ConfidenceBadge.tsx, server/frontend/src/components/DomainTags.tsx, server/frontend/src/components/DragIndicators.tsx, server/frontend/src/components/StatusBadge.tsx, server/frontend/src/components/FilteredListModal.tsx, server/frontend/src/components/ReviewActions.tsx
Adds dark: Tailwind class variants to style maps (TAG_STYLES, STYLES), and individual JSX elements across shared UI components, covering text, backgrounds, borders, skeleton placeholders, and interactive button states for all component families.
KnowledgeUnitModal and ReviewCard dark mode styling
server/frontend/src/components/KnowledgeUnitModal.tsx, server/frontend/src/components/ReviewCard.tsx
Imports confidenceColor from utils (replacing local definitions) and applies dark: Tailwind classes to card styles (CARD_STYLES, ACTION_BOX_STYLES), dialog container, loading skeletons, title/header, content sections (reviewer timestamp, insight detail, action callout, grid, confirmations), text elements, and footer divider.
Page-level dark mode styling
server/frontend/src/pages/LoginPage.tsx, server/frontend/src/pages/ReviewPage.tsx, server/frontend/src/pages/DashboardPage.tsx, server/frontend/src/pages/ApiKeysPage.tsx
Applies dark: Tailwind classes throughout all four pages: form controls and error messages on LoginPage; no-items summary, conflict/error messages, and navigation buttons on ReviewPage; skeleton, stat cards, domains progress bar, confidence panel, useIsDark hook with explicit Recharts tooltip and axis styling, and activity table rows on DashboardPage; and status/expiry badge helpers, create-key form, key list cards, expanded details, description lists, revoke button, and both modals on ApiKeysPage.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding comprehensive dark mode support across the dashboard UI.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2017d5d and add4a51.

📒 Files selected for processing (15)
  • server/frontend/src/App.tsx
  • server/frontend/src/components/ConfidenceBadge.tsx
  • server/frontend/src/components/DomainTags.tsx
  • server/frontend/src/components/DragIndicators.tsx
  • server/frontend/src/components/FilteredListModal.tsx
  • server/frontend/src/components/KnowledgeUnitModal.tsx
  • server/frontend/src/components/Layout.tsx
  • server/frontend/src/components/ReviewActions.tsx
  • server/frontend/src/components/ReviewCard.tsx
  • server/frontend/src/components/StatusBadge.tsx
  • server/frontend/src/index.css
  • server/frontend/src/pages/ApiKeysPage.tsx
  • server/frontend/src/pages/DashboardPage.tsx
  • server/frontend/src/pages/LoginPage.tsx
  • server/frontend/src/pages/ReviewPage.tsx

Comment on lines +37 to +47
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}</>
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

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:

  1. Race condition: Layout mounts after ThemeInitializer, so it will re-read and re-apply the class, potentially overwriting any user interaction that happened between the two initialisations.
  2. Maintenance burden: Changes to the initialisation logic must be synchronised across two locations.
  3. 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.

Comment thread server/frontend/src/App.tsx
Comment thread server/frontend/src/components/KnowledgeUnitModal.tsx Outdated
Comment thread server/frontend/src/components/Layout.tsx
Comment thread server/frontend/src/components/Layout.tsx
Comment thread server/frontend/src/pages/DashboardPage.tsx Outdated
…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
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