Skip to content
Merged
6 changes: 5 additions & 1 deletion SPECS/ARCHIVE/INDEX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# mcpbridge-wrapper Tasks Archive

**Last Updated:** 2026-02-15 (P11-T2)
**Last Updated:** 2026-02-15 (P11-T3)

## Archived Tasks

Expand Down Expand Up @@ -92,6 +92,7 @@
| P11-T1 | [P11-T1_Add_Tool_Call_Detail_Inspector/](P11-T1_Add_Tool_Call_Detail_Inspector/) | 2026-02-15 | PASS |
| BUG-T8 | [BUG-T8_Audit_Log_Cross_Process_Visibility/](BUG-T8_Audit_Log_Cross_Process_Visibility/) | 2026-02-15 | PASS |
| P11-T2 | [P11-T2_Add_Session_Timeline_View/](P11-T2_Add_Session_Timeline_View/) | 2026-02-15 | PASS |
| P11-T3 | [P11-T3_Add_Dashboard_Theme_Toggle/](P11-T3_Add_Dashboard_Theme_Toggle/) | 2026-02-15 | PASS |

## Historical Artifacts

Expand Down Expand Up @@ -148,6 +149,7 @@
| [REVIEW_P11-T1_tool_call_detail_inspector.md](P11-T1_Add_Tool_Call_Detail_Inspector/REVIEW_P11-T1_tool_call_detail_inspector.md) | Review report for P11-T1 |
| [REVIEW_BUG-T8_audit_log_cross_process.md](BUG-T8_Audit_Log_Cross_Process_Visibility/REVIEW_BUG-T8_audit_log_cross_process.md) | Review report for BUG-T8 |
| [REVIEW_P11-T2_session_timeline.md](P11-T2_Add_Session_Timeline_View/REVIEW_P11-T2_session_timeline.md) | Review report for P11-T2 |
| [REVIEW_P11-T3_dashboard_theme_toggle.md](P11-T3_Add_Dashboard_Theme_Toggle/REVIEW_P11-T3_dashboard_theme_toggle.md) | Review report for P11-T3 |

## Archive Log

Expand Down Expand Up @@ -249,3 +251,5 @@
| 2026-02-15 | BUG-T8 | Archived REVIEW_BUG-T8_audit_log_cross_process report |
| 2026-02-15 | P11-T2 | Archived Add_Session_Timeline_View (PASS) |
| 2026-02-15 | P11-T2 | Archived REVIEW_P11-T2_session_timeline report |
| 2026-02-15 | P11-T3 | Archived Add_Dashboard_Theme_Toggle (PASS) |
| 2026-02-15 | P11-T3 | Archived REVIEW_P11-T3_dashboard_theme_toggle report |
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# P11-T3: Add Dashboard Theme Toggle (Dark/Light)

**Priority:** P2
**Branch:** feature/P11-T3-dashboard-theme-toggle
**Dependencies:** P10-T1 ✅

---

## Goal

Implement a CSS-variable-based dark/light theme toggle for the dashboard, storing preference in `localStorage` and syncing Chart.js color defaults on each toggle.

---

## Analysis of Current State

The CSS already uses CSS custom properties (`--bg-primary`, `--text-primary`, etc.) for most structural colors. A handful of semi-transparent rgba values and hardcoded hex values remain in the JS (Chart.js defaults, grid colors).

**Remaining hardcoded values to extract:**
- `rgba(63, 185, 80, 0.2)` and `rgba(248, 81, 73, 0.2)` in status badge backgrounds → `--accent-green-bg`, `--accent-red-bg`
- `rgba(248, 81, 73, 0.4)` in timeline error badge border → `--accent-red-border`
- `rgba(88, 166, 255, 0.05/0.06)` row hover → `--row-hover-bg`
- `rgba(88, 166, 255, 0.1)` row active → `--row-active-bg`
- Chart.js: `#8b949e`, `#30363d`, `#21262d` grid colors (to be managed via JS at toggle time)

---

## Deliverables

### 1. `dashboard.css`
- Add `--accent-green-bg`, `--accent-red-bg`, `--accent-red-border`, `--row-hover-bg`, `--row-active-bg` CSS variables to `:root` (dark defaults)
- Replace all remaining hardcoded rgba/hex in rules with the new variables
- Add `[data-theme="light"]` block on `:root` with light-theme overrides for all variables
- Add `.btn-theme-toggle` styling

### 2. `dashboard.js`
- Add `THEME_COLORS` constant with dark/light chart color configs (text label color, border color, grid color)
- Add `applyChartTheme(isDark)` function: updates `Chart.defaults.color`, `Chart.defaults.borderColor`, and each chart's grid color options, then re-renders
- Add `initTheme()`: reads `localStorage.getItem("theme")`, defaults to `"dark"`, sets `document.documentElement.dataset.theme`, calls `applyChartTheme`, updates button label
- Add `setupThemeToggle()`: wire `btn-theme-toggle` click to toggle `data-theme` between `"dark"/"light"`, persist to `localStorage`, call `applyChartTheme`, update button label
- Call `initTheme()` and `setupThemeToggle()` from `init()`

### 3. `index.html`
- Add `<button id="btn-theme-toggle" class="btn btn-small btn-theme-toggle">Light Mode</button>` in `.header-controls` before the reset-metrics button

---

## Light Theme Color Values

| Variable | Dark | Light |
|-----------------------|-------------------|-----------|
| `--bg-primary` | `#0d1117` | `#ffffff` |
| `--bg-secondary` | `#161b22` | `#f6f8fa` |
| `--bg-card` | `#1c2128` | `#ffffff` |
| `--border-color` | `#30363d` | `#d0d7de` |
| `--text-primary` | `#e6edf3` | `#1f2328` |
| `--text-secondary` | `#8b949e` | `#636e7b` |
| `--accent-green-bg` | `rgba(63,185,80,0.2)` | `rgba(63,185,80,0.15)` |
| `--accent-red-bg` | `rgba(248,81,73,0.2)` | `rgba(248,81,73,0.15)` |
| `--accent-red-border` | `rgba(248,81,73,0.4)` | `rgba(248,81,73,0.5)` |
| `--row-hover-bg` | `rgba(88,166,255,0.06)` | `rgba(88,166,255,0.08)` |
| `--row-active-bg` | `rgba(88,166,255,0.1)` | `rgba(88,166,255,0.15)` |

Chart.js colors:
- Dark: label `#8b949e`, border `#30363d`, grid `#21262d`
- Light: label `#636e7b`, border `#d0d7de`, grid `#e8ecf0`

---

## Acceptance Criteria

- [ ] All colors in CSS use custom properties (no hardcoded hex/rgba in rules)
- [ ] Toggle button switches between dark and light themes
- [ ] Chart.js chart colors update on theme change without page reload
- [ ] Theme preference persists across page reloads via `localStorage`
- [ ] Default theme matches current dark theme (no visual regression)
- [ ] Quality gates pass: `pytest`, `ruff check src/`, `pytest --cov` ≥ 90%
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# P11-T3 Validation Report

**Task:** Add Dashboard Theme Toggle (Dark/Light)
**Date:** 2026-02-15
**Verdict:** PASS

---

## Changes Implemented

### `src/mcpbridge_wrapper/webui/static/dashboard.css`
- Added `--accent-green-bg`, `--accent-red-bg`, `--accent-red-border`, `--row-hover-bg`, `--row-active-bg` CSS variables to `:root`
- Added `[data-theme="light"]` block with full light-theme color overrides for all variables
- Replaced all hardcoded `rgba(...)` values in rules with the new CSS variables (status badges, row hover, timeline error badge)
- Added `.btn-theme-toggle` and `.btn-theme-toggle:hover` styling

### `src/mcpbridge_wrapper/webui/static/index.html`
- Added `<button id="btn-theme-toggle" class="btn btn-small btn-theme-toggle">Light Mode</button>` in `.header-controls`

### `src/mcpbridge_wrapper/webui/static/dashboard.js`
- Added `THEME_COLORS` constant with dark/light Chart.js color configs
- Added `applyChartTheme(isDark)` — updates `Chart.defaults.color`, `Chart.defaults.borderColor`, and grid colors on all active charts, then re-renders
- Added `initTheme()` — reads `localStorage`, sets `document.documentElement.dataset.theme`, calls `applyChartTheme`, syncs button label
- Added `setupThemeToggle()` — wires toggle button click, persists preference to `localStorage`
- Called `initTheme()` and `setupThemeToggle()` from `init()`

---

## Acceptance Criteria

- [x] All colors in CSS use custom properties (no hardcoded hex/rgba in selectors)
- [x] Toggle button switches between dark and light themes
- [x] Chart.js chart colors update on theme change without page reload
- [x] Theme preference persists across page reloads via `localStorage`
- [x] Default theme matches current dark theme (no visual regression)

---

## Quality Gates

| Gate | Result |
|------|--------|
| `pytest` | 403 passed, 5 skipped |
| `ruff check src/` | All checks passed |
| `pytest --cov` | 96.2% (≥ 90% required) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## REVIEW REPORT — P11-T3 Dashboard Theme Toggle

**Scope:** origin/main..HEAD (commit 5ef0fda)
**Files:** 3 (dashboard.css, dashboard.js, index.html)

### Summary Verdict
- [x] Approve
- [ ] Approve with comments
- [ ] Request changes
- [ ] Block

---

### Critical Issues

None.

---

### Secondary Issues

None.

---

### Architectural Notes

- **`[data-theme]` on `:root`** — The CSS selector `[data-theme="light"]` targets `:root` (`<html>`), and `document.documentElement.dataset.theme` sets the attribute on `<html>`. These are the same element; the pattern is correct and aligns with standard CSS theming practice.
- **`applyChartTheme` timing** — `init()` calls `initCharts()` before `initTheme()`, ensuring all chart instances exist when `applyChartTheme` iterates `Object.values(charts)`. Ordering is correct.
- **`THEME_COLORS` constant** — Centralized color config for Chart.js is a good pattern. If chart palettes change in future, one location to update.
- **`btn-warning:hover` rgba** — `rgba(210, 153, 34, 0.15)` remains hardcoded for the reset-button hover. This is acceptable: yellow hover is legible on both dark and light backgrounds and doesn't require a new CSS variable.
- **`Chart.defaults` at module load** — Defaults are set to dark at module boot, then `initTheme()` re-applies the persisted theme immediately. The double-set for dark default (no localStorage) is a harmless no-op.

---

### Tests

- No Python backend changes — all 403 tests pass, 96.2% coverage (≥90% threshold).
- Frontend-only changes (CSS/JS/HTML); no new Python tests required.
- Manual verification paths: toggle button visible in header; `data-theme` attribute switches on click; `localStorage.theme` persists; chart grid colors update; button label flips correctly.

---

### Next Steps

No actionable findings — FOLLOW-UP skipped.
3 changes: 1 addition & 2 deletions SPECS/INPROGRESS/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ The previously selected task has been archived.

## Recently Archived

- 2026-02-15 — P11-T3: Add Dashboard Theme Toggle (Dark/Light) (PASS)
- 2026-02-15 — P11-T2: Add Session Timeline View (PASS)
- 2026-02-15 — BUG-T8: Audit log cross-process visibility (PASS)
- 2026-02-15 — P11-T1: Add Tool Call Detail Inspector (Request/Response Viewer) (PASS)
- 2026-02-15 — FU-BUG-T6-1: Document stale-process cleanup for Web UI port collisions (PASS)
- 2026-02-14 — BUG-T7: Unsupported `resources/*` methods can return non-standard error shape (PASS)

## Suggested Next Tasks

- P11-T3: Add Dashboard Theme Toggle (Dark/Light) (P2, depends on P10-T1 ✅)
- P11-T4: Add Keyboard Shortcuts & Command Palette (P3, depends on P10-T1 ✅)
- FU-BUG-T7-1: Cap `pending_methods` map to guard against unbounded growth (P3)
- FU-P11-T1-1: Refactor `_FakeWebUIConfig` test stub to `MagicMock(spec=WebUIConfig)` (P3)
2 changes: 1 addition & 1 deletion SPECS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ Phase 9 Follow-up Backlog

---

#### P11-T3: Add Dashboard Theme Toggle (Dark/Light)
#### P11-T3: Add Dashboard Theme Toggle (Dark/Light)
- **Description:** Implement CSS-variable-based theme system with a toggle button in the header. Refactor all hardcoded colors in `dashboard.css` to CSS custom properties on `:root`. Add `[data-theme="light"]` overrides. Store user preference in `localStorage`. Update Chart.js color defaults on theme toggle.
- **Priority:** P2
- **Dependencies:** P10-T1
Expand Down
43 changes: 36 additions & 7 deletions src/mcpbridge_wrapper/webui/static/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,28 @@
--accent-red: #f85149;
--accent-yellow: #d29922;
--accent-purple: #bc8cff;
--accent-green-bg: rgba(63, 185, 80, 0.2);
--accent-red-bg: rgba(248, 81, 73, 0.2);
--accent-red-border: rgba(248, 81, 73, 0.4);
--row-hover-bg: rgba(88, 166, 255, 0.06);
--row-active-bg: rgba(88, 166, 255, 0.1);
--radius: 8px;
}

[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f6f8fa;
--bg-card: #ffffff;
--border-color: #d0d7de;
--text-primary: #1f2328;
--text-secondary: #636e7b;
--accent-green-bg: rgba(63, 185, 80, 0.15);
--accent-red-bg: rgba(248, 81, 73, 0.15);
--accent-red-border: rgba(248, 81, 73, 0.5);
--row-hover-bg: rgba(88, 166, 255, 0.08);
--row-active-bg: rgba(88, 166, 255, 0.15);
}

* {
margin: 0;
padding: 0;
Expand Down Expand Up @@ -56,13 +75,13 @@ header h1 {
}

.status-badge.connected {
background: rgba(63, 185, 80, 0.2);
background: var(--accent-green-bg);
color: var(--accent-green);
border: 1px solid var(--accent-green);
}

.status-badge.disconnected {
background: rgba(248, 81, 73, 0.2);
background: var(--accent-red-bg);
color: var(--accent-red);
border: 1px solid var(--accent-red);
}
Expand Down Expand Up @@ -192,7 +211,7 @@ tbody td {
}

tbody tr:hover {
background: rgba(88, 166, 255, 0.05);
background: var(--row-hover-bg);
}

.pagination {
Expand Down Expand Up @@ -240,6 +259,16 @@ tbody tr:hover {
background: rgba(210, 153, 34, 0.15);
}

.btn-theme-toggle {
border-color: var(--border-color);
color: var(--text-secondary);
}

.btn-theme-toggle:hover {
color: var(--text-primary);
background: var(--bg-card);
}

/* Error highlight */
.error-cell {
color: var(--accent-red);
Expand All @@ -260,11 +289,11 @@ footer {
}

.audit-row:hover {
background: rgba(88, 166, 255, 0.06);
background: var(--row-hover-bg);
}

.audit-row.detail-row-open {
background: rgba(88, 166, 255, 0.1);
background: var(--row-active-bg);
}

.detail-row td {
Expand Down Expand Up @@ -377,9 +406,9 @@ footer {

.timeline-session-error-badge {
font-size: 0.7rem;
background: rgba(248, 81, 73, 0.2);
background: var(--accent-red-bg);
color: var(--accent-red);
border: 1px solid rgba(248, 81, 73, 0.4);
border: 1px solid var(--accent-red-border);
border-radius: 10px;
padding: 1px 8px;
}
Expand Down
44 changes: 44 additions & 0 deletions src/mcpbridge_wrapper/webui/static/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@
const auditPageSize = 50;
let auditFilter = "";

// --- Theme ---
var THEME_COLORS = {
dark: { label: "#8b949e", border: "#30363d", grid: "#21262d" },
light: { label: "#636e7b", border: "#d0d7de", grid: "#e8ecf0" },
};

function applyChartTheme(isDark) {
var t = isDark ? THEME_COLORS.dark : THEME_COLORS.light;
Chart.defaults.color = t.label;
Chart.defaults.borderColor = t.border;
Object.values(charts).forEach(function (chart) {
if (!chart || !chart.options) return;
var scales = chart.options.scales || {};
Object.values(scales).forEach(function (scale) {
if (scale && scale.grid) scale.grid.color = t.grid;
});
chart.update("none");
});
}

function initTheme() {
var saved = localStorage.getItem("theme") || "dark";
var isDark = saved !== "light";
document.documentElement.dataset.theme = isDark ? "dark" : "light";
applyChartTheme(isDark);
var btn = el("btn-theme-toggle");
if (btn) btn.textContent = isDark ? "Light Mode" : "Dark Mode";
}

function setupThemeToggle() {
var btn = el("btn-theme-toggle");
if (!btn) return;
btn.addEventListener("click", function () {
var isDark = document.documentElement.dataset.theme !== "light";
var next = isDark ? "light" : "dark";
document.documentElement.dataset.theme = next;
localStorage.setItem("theme", next);
applyChartTheme(next === "dark");
btn.textContent = next === "dark" ? "Light Mode" : "Dark Mode";
});
}

// --- Chart.js defaults ---
Chart.defaults.color = "#8b949e";
Chart.defaults.borderColor = "#30363d";
Expand Down Expand Up @@ -496,6 +538,8 @@
// --- Init ---
function init() {
initCharts();
initTheme();
setupThemeToggle();
setupEventHandlers();
connectWebSocket();
startPolling();
Expand Down
1 change: 1 addition & 0 deletions src/mcpbridge_wrapper/webui/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<h1>XcodeMCPWrapper Dashboard</h1>
<div class="header-controls">
<span id="connection-status" class="status-badge disconnected">Disconnected</span>
<button id="btn-theme-toggle" class="btn btn-small btn-theme-toggle">Light Mode</button>
<button id="btn-reset-metrics" class="btn btn-warning">Reset Metrics</button>
</div>
</header>
Expand Down