D3: Dark mode toggle (Phase 3 stretch)#2
Closed
riccardofresi wants to merge 3 commits into
Closed
Conversation
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>
Owner
Author
Update — coverage extended to view-scoped surfaces (commit `d1bf40b`)After visual review, several surfaces remained on the light palette in dark mode because their styles live in component-scoped CSS (Vue's `data-v-xxx` attribute) which loads after App.vue and wins on tied specificity. Added high-specificity `!important` overrides under `:root[data-theme="dark"]` for:
Verified via Playwright MCP: `getComputedStyle` of every offending surface now resolves to `rgb(17, 24, 39)` (the dark elevated token) or `transparent`. Suite still 53/53 green. The `!important` is deliberate and bounded — applies only inside the dark-mode override block. The clean production path remains a per-view refactor of scoped styles to consume the CSS variables directly. |
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
added a commit
that referenced
this pull request
Apr 27, 2026
…E2E green (#1) * Phase 1 M1: stabilize Reports module, document architecture, baseline E2E Delivers the Phase 1 milestone (M1) of the Meridian engagement (RFP MC-2026-0417): the foundation for every subsequent change — Reports module remediation, current-state architecture documentation, and a Playwright E2E framework that gates future regressions. R1 — Reports module remediation (closes 9 of 15 logged defects) - Reports.vue rewritten in Composition API (was Options API) - Filters now wired through useFilters composable + watch reload - Backend /api/reports/* endpoints accept warehouse, category, status, month - i18n integration via new reports.* namespace (English + Japanese) - api.js centralises calls (no more direct axios from views) - Stable v-for keys, console-log spam removed - W3 cleanup: brand corrected to Meridian Components, "1 day" pluralization helper, Spending alert() removed and toLocaleString -> formatCurrency R3 — Browser test framework (Playwright, standalone) - tests/e2e/ self-contained: package.json, config, 17 specs across 2 files - Flow #1 smoke (shell + 6 routes + interactive nav) - Flow lindsey-anthropic#4 Reports (existing-behaviour + R1 regression contract) - Trace/screenshot/video on failure for IT debuggability - README documents stack, conventions, sample CI R4 — Architecture documentation - proposal/architecture.html: 9 sections covering system overview, request lifecycle, API reference, frontend/backend patterns, data layer, 10-item technical-debt catalogue, and 5 ADRs Act 1 proposal package (was already produced; included in this milestone for repo coherence): executive summary, technical approach, timeline + Gantt, pricing, relevant-experience scaffold, clarifying questions, capabilities deck. Verification - 17/17 Playwright tests pass against the running app - Manual snapshot via Playwright MCP confirms brand, pluralization, no Reports console noise - Defect log (docs/r1-defect-log.md) updated with disposition for all 15 findings; remaining 6 are deferred (4 await Tanaka decisions, 2 absorbed into R2 build) — within the +50% pricing trigger, no T&M conversion Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phase 2 W4-W5: extend E2E coverage to 4 more critical flows Adds Playwright specs for the four remaining non-gated critical flows in the proposal's R3 plan, raising suite coverage from 17 to 35 green tests. R3 — Critical flow coverage (4 new flows) - Flow #2 Inventory: Category and Location filtering, intersection, Reset-button behaviour. 5 tests. - Flow #3 Orders: status stat cards, header/row-count consistency, items <details> drill-down, Status + Time Period intersection. 4 tests. - Flow lindsey-anthropic#5 Demand: three trend cards (increasing/stable/decreasing), forecasts table columns, per-row trend value validation, summary-vs-table consistency check. 4 tests. - Flow lindsey-anthropic#7 Spending: 4 KPI cards with currency values, Revenue vs Costs chart bar groups, 4 cost categories with name/amount/percentage, Recent Transactions structure, post-cleanup non-clickability. 6 tests. Tooling - Authoring convention reinforced: page.waitForRequest must be armed BEFORE the action; selectOption fires synchronously and the request is gone by the time a post-action listener attaches. Defect log - lindsey-anthropic#17 added (Low): Spending category percentages labelled "% of total" but sum to ~124%. Discovered while writing the Flow lindsey-anthropic#7 spec. Open; resolution requires Operations input on the intended denominator. Verification - 35/35 Playwright tests pass against the running app (was 17/17) - Defect-log severity counts and disposition table updated to reflect lindsey-anthropic#17 and the four newly-shipped flows in tests/e2e/README.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Phase 2 W5-W7: ship R2 Restocking feature end-to-end The R2 deliverable from RFP §3.1: a new Restocking view that recommends purchase orders given current stock, demand forecast, and an operator-supplied budget ceiling. Built without a Discovery session (Tanaka unavailable in the workshop simulation), using the assumptions from the proposal — (s, S) policy with newsvendor service-level calibration (Silver, Pyke & Thomas, 2017). Backend (server/main.py) - GET /api/restocking/recommendations?budget=&service_level=&warehouse=&category= - For each inventory item with a matching demand forecast: shortfall = max(0, forecast - on_hand) qty = shortfall (or reorder_point - on_hand if below ROP) cost = qty * unit_cost criticality = shortfall * unit_cost (urgency × value at stake) - Greedy budget allocation: sort by criticality desc, mark in_budget while cumulative cost <= ceiling. - Service level snaps to the closest tabulated z-score (0.80 .. 0.99). Today z is informational; when forecasts add σ_LT, it moves into the formula. - Documented limitation: only SKUs with a forecast in server/data/demand_forecasts.json get scored (currently 9 of 32). Frontend (client/src/views/Restocking.vue) - Operator controls: budget input, service-level slider (80–99%), "Generate" button. - Five summary cards: Candidates / In budget / Out of budget / Total selected cost / Budget remaining. - Recommendations table: 11 columns including operator-overridable Qty input. Editing Qty recomputes Est. Cost client-side; status badges distinguish in-budget vs out-of-budget rows. - Auto-loads on mount; integrates with useFilters? — no, this is a single-purpose tool that uses its own controls (budget/service-level) rather than the global filter bar. - Composition API throughout, useI18n for all strings. Routing (client/src/main.js, App.vue) - /restocking route registered. - "Restocking" added to the top nav after "Reports". Internationalisation - Full restocking.* namespace added to en.js and ja.js (37 keys × 2 locales). Browser tests (tests/e2e/specs/08-restocking.spec.js) - 6 specs covering: page render, table columns, summary↔table consistency, sort-by-criticality invariant, budget-tightening behaviour, operator override propagation. Verification - 41/41 Playwright tests pass (was 35/35; +6 for Flow lindsey-anthropic#8). - Manual probe via Playwright MCP confirms: 4 candidates ranked by Est. Cost desc, all in budget at $100k ($65,965 total, $34,035 remaining), overrides update the table in real time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * W7: route Backlog (close #DEBT-05) + i18n cleanup + Flow lindsey-anthropic#6 Closes the last gated R1 cleanup items by registering a route for the Backlog view (which had been dead code in the previous vendor's handoff) and translating it through the same useI18n + pluralize plumbing used elsewhere. Routing - /backlog route registered in main.js - "Backlog" link added to App.vue nav after "Restocking" Backlog.vue rewrite - Adds useI18n; all hardcoded English strings move to a new backlog.* namespace (en + ja, 21 keys) - Days Delayed renders via pluralize() — "1 day" vs "n days" - Priority badges use t(`priority.${item.priority}`) so the lowercase raw value never reaches the DOM (defect lindsey-anthropic#9) - Empty-state copy moved out of inline styles into scoped CSS - useFilters integration preserved (Backlog already had this part right) Browser tests - tests/e2e/specs/06-backlog.spec.js — 7 specs covering routing, priority stats, table structure, translated badges, "1 day" pluralization, EN→JA translation contract - Locale-leak fix: tests that switch the language now reset localStorage in afterEach (specs 04 + 06). Without this the JP UI leaks across worker boundaries and breaks subsequent tests that assert English text — surfaced by a flaky run during this commit's development. Defect log + README - lindsey-anthropic#8, lindsey-anthropic#9 marked closed (were gated by #DEBT-05) - #DEBT-05 closed implicitly (route now exists) - Flow lindsey-anthropic#6 status flipped to shipped in tests/e2e/README.md Verification - 48/48 Playwright tests pass (was 41) - Manual probe via Playwright MCP confirms: page reachable from nav, "1 day" vs "5 days" rendered correctly, priority badges translated, JA switch translates the title to バックログ管理 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the D3 desired item from the RFP — operator-selectable theme, targeted at low-light warehouse stations per the proposal. Stacked on top of PR #1; built on a `feat/dark-mode` branch via `git worktree` so it can be reviewed (or shelved) independently of the M1+M2 work.
What's in this PR
Composable: `client/src/composables/useTheme.js`
Component: `client/src/components/ThemeToggle.vue`
Theming: `client/src/App.vue` (non-scoped `<style>`)
E2E spec: `tests/e2e/specs/09-dark-mode.spec.js`
5 tests:
Coverage limitation (intentional)
Most view-level components have hardcoded colors in their scoped styles (`background: white;`, `color: #0f172a;`). Reaching them from App.vue's global stylesheet would require either `:deep()` boilerplate in every consumer or a per-file refactor. This PR refactors the most-visible surfaces only; full coverage of every nested card and modal is a Phase-3 polish task.
For the workshop demo, this gives a clean dark mode for the dashboard, filter bar, all data tables, badges, and stat cards — which covers the screens an operator looks at all day.
Worktree workflow note
This branch was developed on a sibling `git worktree` at `../claude-darkmode` so the main directory could keep R1-R4 work intact and the dev servers could move to the worktree without conflict on ports 3000/8001. After this PR is reviewed:
Test plan
🤖 Generated with Claude Code