Skip to content

D3: Dark mode toggle (Phase 3 stretch)#3

Merged
riccardofresi merged 3 commits into
mainfrom
feat/dark-mode
Apr 27, 2026
Merged

D3: Dark mode toggle (Phase 3 stretch)#3
riccardofresi merged 3 commits into
mainfrom
feat/dark-mode

Conversation

@riccardofresi

Copy link
Copy Markdown
Owner

Summary

D3 desired item from the RFP — operator-selectable theme for low-light warehouse stations. Three commits, cherry-picked off the original PR #2 onto main after PR #1 squash-merged.

What's in this PR

Composable + component

  • `client/src/composables/useTheme.js` — singleton ref, persisted via `localStorage['app-theme']`, applies `data-theme` to ``
  • `client/src/components/ThemeToggle.vue` — sun/moon icon swap in the banner

Theming

  • `client/src/App.vue` non-scoped `<style>`: CSS-variable palette at `:root`, `:root[data-theme="dark"]` overrides
  • High-specificity `!important` overrides for view-scoped surfaces that re-declare backgrounds (KPI cards, filter bar, banner controls, profile dropdown internals, clickable rows)
  • `color-scheme` set so native form controls follow

Bonus brand fix

Tests

  • `tests/e2e/specs/09-dark-mode.spec.js` — 5 specs: toggle visibility, default light, click → dark with localStorage persistence and luminance check, click again → light, full-reload persistence
  • Suite was 53/53 green on the original feat/dark-mode branch

Test plan

  • Manual probe via Playwright MCP after each follow-up commit — every offending surface resolves to dark token
  • (Reviewer) `cd tests/e2e && npm test` to confirm 53/53 green on rebased branch
  • (Reviewer) Toggle on each view, verify acceptable contrast

History note

Original PR #2 (now closed) had base on `phase-1-m1-stabilize-document`. After PR #1 squash-merged, that branch was deleted and the rebase produced too many conflicts with the squashed history. Cleaner to cherry-pick the 3 dark-mode commits onto fresh main:

  • `D3: Dark mode toggle (Phase 3 stretch)`
  • `D3 follow-up: extend dark coverage to view-scoped surfaces`
  • `D3 follow-up 2: profile dropdown internals + brand residual in email`

🤖 Generated with Claude Code

riccardofresi and others added 3 commits April 27, 2026 15:07
Implements the D3 desired item from the RFP — operator-selectable theme,
targeted at low-light warehouse stations per the proposal. Built on a
separate feat/dark-mode branch via git worktree so the main PR stays focused
on R1-R4 and this can be reviewed (or shelved) independently.

Architecture
  - useTheme composable persists the choice in localStorage and applies a
    data-theme="dark" attribute to <html>.
  - App.vue's <style> (intentionally non-scoped) hosts the CSS-variable
    palette: :root for light, :root[data-theme="dark"] for dark. Per-component
    scoped styles still apply on top — overrides cover only the high-traffic
    surfaces (banner, filter bar, cards, tables, badges, page headers).
  - ThemeToggle.vue is a small banner button with a sun/moon icon swap.
  - color-scheme set to match so native form controls (selects, scrollbars)
    follow.

Coverage
  - High-traffic surfaces flip cleanly.
  - Long-tail per-view scoped styles remain on the original light palette in
    dark mode and are tracked as Phase-3 polish follow-on (see PR description).

Tests
  - tests/e2e/specs/09-dark-mode.spec.js — 5 specs covering toggle visibility,
    default light, click → dark with localStorage persistence and luminance
    check, click again → light, full-reload persistence.
  - Test note: localStorage seed must be done via page.evaluate AFTER goto
    (not addInitScript) so a later page.reload() doesn't re-clear the value.
    Body has a 0.2s background-color transition; tests poll until the colour
    settles.

Verification
  - 53/53 Playwright tests pass on this branch (was 48; +5 for Flow lindsey-anthropic#9).
  - Manual probe via Playwright MCP: toggle flips data-theme, body bg switches
    from rgb(248,250,252) to rgb(11,17,32), preference survives reload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial dark mode (commit 09bbbe5 on this branch — wait, prior commit on
this branch) covered the global surfaces but missed several view-scoped
classes whose CSS specificity ties with our globals and whose stylesheet
is loaded after App.vue. Surfaced visually as white islands on the
dashboard:

  - Dashboard `.kpi-card` (the five Key Performance Indicator cards)
  - FilterBar `.filter-select` and `.filters-bar`
  - LanguageSwitcher `.language-button`
  - ProfileMenu `.profile-button`
  - FilterBar `.reset-filters-btn`
  - Dashboard `.h-bar-container` and `.task-item:hover`
  - `.clickable-row:hover` (Dashboard had `!important` on light hover)

Approach
  - Add `:root[data-theme="dark"]` selectors with `!important` for the
    specific class names. The trade-off (using !important) is bounded
    to the dark-mode override block; light mode is unaffected.
  - Cover dropdown menus too (LanguageSwitcher dropdown, ProfileMenu
    items) so the click states stay coherent.

This is still a prototype; full per-view refactor of every scoped style
to use the CSS variables would be the production-grade path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues spotted in the profile-menu popover:

1. Dropdown header banner (.dropdown-header), user name/email, divider, and
   logout item still rendered with their light-mode component-scoped styles.
   Added overrides under :root[data-theme="dark"] for:
     .dropdown-header, .user-name, .user-email, .profile-name,
     .dropdown-divider, .dropdown-item.logout (+ hover), .task-badge

2. composables/useAuth.js still hardcoded john.doe@catalystcomponents.com —
   the previous brand-correction pass (lindsey-anthropic#15) only caught the H1 in the locale
   files. This is the email field shown inside the dropdown header. Renamed
   to john.doe@meridiancomponents.example to match the brand.

Verified via Playwright MCP:
  - All dropdown surfaces resolve to dark tokens
  - Email text in DOM now reads "john.doe@meridiancomponents.example"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@riccardofresi riccardofresi merged commit 43d6c22 into main Apr 27, 2026
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