I first wrote this up as "BCC renders as a bright white window on a dark desktop." That's backwards, and I should have read the constants before filing. BCC is already dark — BG = "#1b1d23", PANEL = "#23262e", TEXT = "#e7e9ee", applied through a single module-level STYLESHEET in bcc.py.
The actual gap: the theme is hardcoded dark with no light option and no awareness of the OS appearance. A user on a light desktop gets a dark window that matches nothing else on their screen, with no way to change it.
What exists
A complete dark palette as module-level constants (ACCENT, BG, PANEL, PANEL_2, TEXT, MUTED, BORDER, GOOD, BAD, WARN).
One f-string STYLESHEET built from them, applied globally.
~20 per-widget setStyleSheet(f"color: {MUTED}")-style calls scattered through bcc.py, plus STATUS_COLORS / HEALTH_COLORS lookup tables.
So the colour vocabulary is already factored — it's just bound to one palette at import time.
Scope
A Palette value type plus DARK (byte-identical to today's colours — this must not be a redesign) and a new LIGHT.
Theme setting with three states: System (default), Light, Dark; persisted in QSettings next to the existing splitter geometry and update/autoCheck.
Pure resolve_theme(setting, system_is_dark) -> "light" | "dark" in bcc_core so the decision logic is testable without a Qt app.
STYLESHEET becomes build_stylesheet(palette).
Toggle in the existing menu, near the About action.
The hard part
The ~20 inline setStyleSheet calls read module-level constants at call time. Switching theme live means those either re-run or go stale, showing dark-theme colours on a light window. Options: re-apply global QSS and refresh the affected panes on switch, or accept a "restart to apply" for inline-styled bits. Prefer live if it's clean; don't ship a half-updated window.
Constraints
No -apple-system or any web font stack in the QSS. Removed deliberately; the comment above STYLESHEET explains why (forces a costly font-alias scan). Native system fonts only.
GOOD (#4ade80) and WARN (#fbbf24) were picked against a dark background and will not carry to white — the health column and status dots need light-palette variants that actually meet contrast.
Several hardcoded near-black values are baked into the QSS (#1a1205 as on-accent text, #202229 for the disabled table, #16181d for the diagnostics pane). These need palette slots, not literals.
The masked-secret rendering must stay visually distinct from real values in both palettes.
Tests
resolve_theme across all three settings × both OS appearances; palette completeness (every slot the stylesheet references exists in both palettes, so a missing key fails a test rather than rendering a broken window).
## Correction to the original filing
I first wrote this up as "BCC renders as a bright white window on a dark desktop." That's backwards, and I should have read the constants before filing. BCC is **already dark** — `BG = "#1b1d23"`, `PANEL = "#23262e"`, `TEXT = "#e7e9ee"`, applied through a single module-level `STYLESHEET` in `bcc.py`.
The actual gap: the theme is **hardcoded dark with no light option and no awareness of the OS appearance**. A user on a light desktop gets a dark window that matches nothing else on their screen, with no way to change it.
## What exists
- A complete dark palette as module-level constants (`ACCENT`, `BG`, `PANEL`, `PANEL_2`, `TEXT`, `MUTED`, `BORDER`, `GOOD`, `BAD`, `WARN`).
- One f-string `STYLESHEET` built from them, applied globally.
- ~20 per-widget `setStyleSheet(f"color: {MUTED}")`-style calls scattered through `bcc.py`, plus `STATUS_COLORS` / `HEALTH_COLORS` lookup tables.
So the colour vocabulary is already factored — it's just bound to one palette at import time.
## Scope
- A `Palette` value type plus `DARK` (byte-identical to today's colours — this must not be a redesign) and a new `LIGHT`.
- Theme setting with three states: **System** (default), **Light**, **Dark**; persisted in `QSettings` next to the existing splitter geometry and `update/autoCheck`.
- Pure `resolve_theme(setting, system_is_dark) -> "light" | "dark"` in `bcc_core` so the decision logic is testable without a Qt app.
- `STYLESHEET` becomes `build_stylesheet(palette)`.
- Toggle in the existing menu, near the About action.
## The hard part
The ~20 inline `setStyleSheet` calls read module-level constants at call time. Switching theme live means those either re-run or go stale, showing dark-theme colours on a light window. Options: re-apply global QSS and refresh the affected panes on switch, or accept a "restart to apply" for inline-styled bits. Prefer live if it's clean; don't ship a half-updated window.
## Constraints
- **No `-apple-system` or any web font stack in the QSS.** Removed deliberately; the comment above `STYLESHEET` explains why (forces a costly font-alias scan). Native system fonts only.
- `GOOD` (`#4ade80`) and `WARN` (`#fbbf24`) were picked against a dark background and will not carry to white — the health column and status dots need light-palette variants that actually meet contrast.
- Several hardcoded near-black values are baked into the QSS (`#1a1205` as on-accent text, `#202229` for the disabled table, `#16181d` for the diagnostics pane). These need palette slots, not literals.
- The masked-secret rendering must stay visually distinct from real values in both palettes.
## Tests
`resolve_theme` across all three settings × both OS appearances; palette completeness (every slot the stylesheet references exists in both palettes, so a missing key fails a test rather than rendering a broken window).
the_og
added the P2 label 2026-07-20 12:07:29 -04:00
the_og
changed title from Dark mode / theming — light/dark toggle with persistence to Theming — BCC is dark-only; add a light theme and system-following2026-07-20 12:13:03 -04:00
Implemented and merged in #80 (commit febd617, merge 9a04332). Every item in this issue's scope landed:
Palette value type in bcc_core with DARK_PALETTE (byte-identical to the shipped look — asserted by test_dark_palette_is_unchanged_from_the_shipped_look) and a new LIGHT_PALETTE.
Pure resolve_theme(setting, system_is_dark) → "light" | "dark", tested across all three settings × both OS appearances.
STYLESHEET → build_stylesheet(palette); apply_palette() rebinds the module-level colour names so the ~20 inline setStyleSheet call sites pick up the new colour on re-render (live switch, no restart).
Three-state setting Match system / Light / Dark under View ▸ Theme, persisted in QSettings under ui/theme, defaulting to following the system.
The three baked-in near-black literals (#1a1205, #202229, #16181d) now have palette slots; test_stylesheet_builder_has_no_hardcoded_colours forbids reintroducing any, and test_every_palette_slot_is_consumed + test_both_palettes_define_every_slot keep the two palettes in lockstep.
Light-palette GOOD/WARN were darkened to clear WCAG AA; test_palette_meets_contrast_on_panel enforces ≥ 4.5:1 for every text colour in both palettes. Native system fonts only (no -apple-system).
The one thing CI can't prove is the live look — light-mode rendering and the live re-render of inline-styled widgets on switch — since there's no PySide6 in CI. That's a human eyeball; if anything reads wrong when you toggle themes, reopen. Closing as done.
Implemented and merged in **#80** (commit `febd617`, merge `9a04332`). Every item in this issue's scope landed:
- `Palette` value type in `bcc_core` with `DARK_PALETTE` (byte-identical to the shipped look — asserted by `test_dark_palette_is_unchanged_from_the_shipped_look`) and a new `LIGHT_PALETTE`.
- Pure `resolve_theme(setting, system_is_dark) → "light" | "dark"`, tested across all three settings × both OS appearances.
- `STYLESHEET` → `build_stylesheet(palette)`; `apply_palette()` rebinds the module-level colour names so the ~20 inline `setStyleSheet` call sites pick up the new colour on re-render (live switch, no restart).
- Three-state setting **Match system / Light / Dark** under **View ▸ Theme**, persisted in `QSettings` under `ui/theme`, defaulting to following the system.
- The three baked-in near-black literals (`#1a1205`, `#202229`, `#16181d`) now have palette slots; `test_stylesheet_builder_has_no_hardcoded_colours` forbids reintroducing any, and `test_every_palette_slot_is_consumed` + `test_both_palettes_define_every_slot` keep the two palettes in lockstep.
- Light-palette `GOOD`/`WARN` were darkened to clear WCAG AA; `test_palette_meets_contrast_on_panel` enforces ≥ 4.5:1 for every text colour in both palettes. Native system fonts only (no `-apple-system`).
The one thing CI can't prove is the live *look* — light-mode rendering and the live re-render of inline-styled widgets on switch — since there's no PySide6 in CI. That's a human eyeball; if anything reads wrong when you toggle themes, reopen. Closing as done.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Correction to the original filing
I first wrote this up as "BCC renders as a bright white window on a dark desktop." That's backwards, and I should have read the constants before filing. BCC is already dark —
BG = "#1b1d23",PANEL = "#23262e",TEXT = "#e7e9ee", applied through a single module-levelSTYLESHEETinbcc.py.The actual gap: the theme is hardcoded dark with no light option and no awareness of the OS appearance. A user on a light desktop gets a dark window that matches nothing else on their screen, with no way to change it.
What exists
ACCENT,BG,PANEL,PANEL_2,TEXT,MUTED,BORDER,GOOD,BAD,WARN).STYLESHEETbuilt from them, applied globally.setStyleSheet(f"color: {MUTED}")-style calls scattered throughbcc.py, plusSTATUS_COLORS/HEALTH_COLORSlookup tables.So the colour vocabulary is already factored — it's just bound to one palette at import time.
Scope
Palettevalue type plusDARK(byte-identical to today's colours — this must not be a redesign) and a newLIGHT.QSettingsnext to the existing splitter geometry andupdate/autoCheck.resolve_theme(setting, system_is_dark) -> "light" | "dark"inbcc_coreso the decision logic is testable without a Qt app.STYLESHEETbecomesbuild_stylesheet(palette).The hard part
The ~20 inline
setStyleSheetcalls read module-level constants at call time. Switching theme live means those either re-run or go stale, showing dark-theme colours on a light window. Options: re-apply global QSS and refresh the affected panes on switch, or accept a "restart to apply" for inline-styled bits. Prefer live if it's clean; don't ship a half-updated window.Constraints
-apple-systemor any web font stack in the QSS. Removed deliberately; the comment aboveSTYLESHEETexplains why (forces a costly font-alias scan). Native system fonts only.GOOD(#4ade80) andWARN(#fbbf24) were picked against a dark background and will not carry to white — the health column and status dots need light-palette variants that actually meet contrast.#1a1205as on-accent text,#202229for the disabled table,#16181dfor the diagnostics pane). These need palette slots, not literals.Tests
resolve_themeacross all three settings × both OS appearances; palette completeness (every slot the stylesheet references exists in both palettes, so a missing key fails a test rather than rendering a broken window).Dark mode / theming — light/dark toggle with persistenceto Theming — BCC is dark-only; add a light theme and system-followingthe_og referenced this issue2026-08-03 22:41:03 -04:00
Implemented and merged in #80 (commit
febd617, merge9a04332). Every item in this issue's scope landed:Palettevalue type inbcc_corewithDARK_PALETTE(byte-identical to the shipped look — asserted bytest_dark_palette_is_unchanged_from_the_shipped_look) and a newLIGHT_PALETTE.resolve_theme(setting, system_is_dark) → "light" | "dark", tested across all three settings × both OS appearances.STYLESHEET→build_stylesheet(palette);apply_palette()rebinds the module-level colour names so the ~20 inlinesetStyleSheetcall sites pick up the new colour on re-render (live switch, no restart).QSettingsunderui/theme, defaulting to following the system.#1a1205,#202229,#16181d) now have palette slots;test_stylesheet_builder_has_no_hardcoded_coloursforbids reintroducing any, andtest_every_palette_slot_is_consumed+test_both_palettes_define_every_slotkeep the two palettes in lockstep.GOOD/WARNwere darkened to clear WCAG AA;test_palette_meets_contrast_on_panelenforces ≥ 4.5:1 for every text colour in both palettes. Native system fonts only (no-apple-system).The one thing CI can't prove is the live look — light-mode rendering and the live re-render of inline-styled widgets on switch — since there's no PySide6 in CI. That's a human eyeball; if anything reads wrong when you toggle themes, reopen. Closing as done.