Skip to content

Add app-wide light/dark mode toggle - #210

Closed
Trosper3 wants to merge 1 commit into
task/shared-theme-refactorfrom
task/dark-light-toggle
Closed

Trosper3 wants to merge 1 commit into
task/shared-theme-refactorfrom
task/dark-light-toggle

Conversation

@Trosper3

@Trosper3 Trosper3 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a small toggle in the top banner that switches the whole app between light and dark, persisted to localStorage.

  • components/theme.js: adds a dark palette alongside the existing light one, plus createAppTheme(mode), and turns pageBackground/sectionCardSx/innerCardSx from flat constants into mode-aware functions. Also folds pages/index.js's page-specific palette extensions (primary.lGray, white) into both shared palettes, since theme creation is no longer per-page.
  • components/ThemeModeContext.js (new): the actual state — mode, persisted to localStorage, read after mount (SSR has no localStorage, so the very first render is always light; a returning dark-mode user sees a brief flash before it switches — not worth a blocking SSR script for this). Renders MUI's own ThemeProvider + CssBaseline once, wrapping the whole app.
  • pages/_app.js: wraps <Component> in ThemeModeProvider — this is what makes the toggle affect every page from one place, instead of each page owning its own independent theme like before.
  • Every page (index, aboutus, browse, help, contribute) and the Browse-only widgets (ProblemCard, FacetFilterGroup) drop their own ThemeProvider/CssBaseline/createTheme and read mode from useThemeMode() instead. Also replaced the many hardcoded body-text hex colors (#374151, #6b7280, #9ca3af, ...) with the new textColors(mode)/surfaceColors(mode) tiers, since dark-on-dark text would otherwise be illegible against the new dark cards.
  • ResponsiveAppBar: the banner itself stays one fixed dark chrome color in both modes (matches Make the top banner color consistent across all pages #207's original "consistent everywhere" intent, just updated to the canonical #3F3F46 grey) rather than becoming mode-aware itself — it's already a deliberately fixed brand element. Adds the toggle IconButton (sun/moon) at the top-right corner.

Base branch

Based on #209 (theme centralization) since this builds directly on the shared theme.js module that PR introduces — should merge after it, or be rebased onto default once #209 lands.

Test plan

  • npm run build — succeeds, 0 errors, all 5 pages (/, /aboutus, /browse, /help, /contribute) prerender successfully.
  • npm run dev — all 5 pages serve 200, default (light) SSR render confirmed to use the shared palette (#3F3F46 AppBar chrome, #111827 heading text, rgba(139,92,246,0.08) page background) and the toggle button renders on every page.
  • Visual check of the actual toggle interaction in a real browser (does it flip to dark and back, does it persist across reload) — no browser automation tool was available this session, so this was verified structurally (context wiring, SSR output) rather than by actually clicking it. Worth a hands-on check before merging.

Adds a small toggle in the top banner that switches the whole app
between light and dark, persisted to localStorage.

- components/theme.js: adds a dark palette alongside the existing
  light one, plus createAppTheme(mode), and turns pageBackground/
  sectionCardSx/innerCardSx from flat constants into mode-aware
  functions. Also folds pages/index.js's page-specific palette
  extensions (primary.lGray, white) into both shared palettes, since
  theme creation is no longer per-page.
- components/ThemeModeContext.js (new): the actual state -- mode,
  persisted to localStorage, read after mount (SSR has no
  localStorage, so the very first render is always light; a
  returning dark-mode user sees a brief flash before it switches,
  not worth a blocking SSR script for this). Renders MUI's own
  ThemeProvider + CssBaseline once, wrapping the whole app.
- pages/_app.js: wraps <Component> in ThemeModeProvider -- this is
  what makes the toggle affect every page from one place, instead of
  each page owning its own independent theme like before.
- Every page (index, aboutus, browse, help, contribute) and the
  Browse-only widgets (ProblemCard, FacetFilterGroup) drop their own
  ThemeProvider/CssBaseline/createTheme and read mode from
  useThemeMode() instead, threading it into the now-mode-aware style
  helpers. Also replaced the many hardcoded body-text hex colors
  (#374151, #6b7280, #9ca3af, ...) with the new textColors(mode) /
  surfaceColors(mode) tiers, since dark-on-dark text would otherwise
  be illegible against the new dark cards.
- ResponsiveAppBar: the banner itself stays one fixed dark chrome
  color in both modes (matches #207's original
  "consistent everywhere" intent, just updated to the canonical
  #3F3F46 grey) rather than becoming mode-aware itself -- it's
  already a deliberately fixed brand element. Adds the toggle
  IconButton (sun/moon) at the top-right corner.

Verified: npm run build succeeds with zero errors, all pages
prerender; npm run dev serves all five pages at 200 with the shared
palette resolving correctly in the default (light) SSR render.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGUai41kxHnzFP3tiENWkK
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Redux Build System — CI Report

npm · commit b849ea8

Operation Status Summary Time
audit 1 high 280.9s
format-check 58 format · 33 import order 0.5s
lint 25 errors, 41 warnings 7.3s
typecheck ⏭️ no tsconfig.json
unit-test ⏭️ no test script in package.json
build built local/redux_gui:ci · 235MB 289.9s
integration-test 1 failed · 3 passed 20.3s
push not run — integration-test failed

Overall: ❌ 1 passed · 4 failed · 2 skipped · 1 blocked

❌ audit — 1 high
Severity Location Rule Message
high svgo@3.0.0 - 3.3.3 GHSA-2p49-hgcm-8545 SVGO removeScripts plugin leaves some executable scripts intact
❌ format-check — 58 format · 33 import order
Severity Location Rule Message
error Tools/Constants.js format needs formatting
error Tools/ProblemInstanceParser.js format needs formatting
error biome.json format needs formatting
error components/ContributorCard.js format needs formatting
error components/Quantum/QuantumCircuitVisualizer.js assist/source/organizeImports Sort these imports.
error components/Quantum/QuantumCircuitVisualizer.js format needs formatting
error components/Quantum/circuitUtils.js format needs formatting
error components/Visualization/Graphvisualization.js format needs formatting
error components/Visualization/GraphvizWrapper.js assist/source/organizeImports Sort these imports.
error components/Visualization/QuantumCircuitVis.js assist/source/organizeImports Sort these imports.
error components/Visualization/QuantumCircuitVis.js format needs formatting
error components/Visualization/ReducedVisualization.js format needs formatting
error components/Visualization/constants/VisColors.js format needs formatting
error components/Visualization/constants/VisColorsArray.js assist/source/organizeImports Sort the exported names.
error components/Visualization/constants/VisColorsArray.js format needs formatting
error components/Visualization/openqasmToQText.js format needs formatting
error components/Visualization/svgs/DynamicTableSvgReact.js format needs formatting
error components/Visualization/svgs/LaTeXGraphSvgReact.js format needs formatting
error components/Visualization/svgs/No_Viz_SVG.js assist/source/organizeImports Sort these imports.
error components/Visualization/svgs/No_Viz_SVG.js format needs formatting
… and 72 more
❌ lint — 25 errors, 41 warnings
Severity Location Rule Message
error components/ThemeModeContext.js:31 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/Visualization/QuantumCircuitVis.js:152 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Problem.js:51 no-undef 'requestInfo' is not defined.
error components/hooks/ProblemProvider/Problem.js:73 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:132 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:152 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:171 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:190 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:211 react-hooks/immutability Error: Cannot access variable before it is declared
error components/hooks/ProblemProvider/Reducer.js:215 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:51 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Reducer.js:74 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Solver.js:138 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Solver.js:24 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/hooks/ProblemProvider/Verifier.js:87 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/pageblocks/ProblemRowReact.js:154 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/pageblocks/ProblemRowReact.js:163 react-hooks/immutability Error: This value cannot be modified
error components/pageblocks/VerifyRowReact.js:46 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/pageblocks/VerifyRowReact.js:52 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
error components/pageblocks/VisualizeRowReact.js:135 react-hooks/set-state-in-effect Error: Calling setState synchronously within an effect can trigger cascading renders
… and 46 more

@Trosper3

Trosper3 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #219, which combines this theming PR with the rest of the theme-centralization stack into one PR for combined review. Closing in favor of #219.

@Trosper3 Trosper3 closed this Sep 8, 2026
@Trosper3
Trosper3 deleted the task/dark-light-toggle branch September 10, 2026 18:32
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