diff --git a/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths.md b/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths.md new file mode 100644 index 00000000..1a540445 --- /dev/null +++ b/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths.md @@ -0,0 +1,32 @@ +# BUG-T16: Tool Distribution (Pie) widget is cropped at medium widths + +## Objective +Eliminate pie widget cropping at medium viewport widths (around 1200px) while preserving correct rendering at wide desktop and mobile sizes. + +## Scope +- Frontend-only changes in Web UI static assets. +- Responsive layout and chart legend behavior for chart containers. + +## Deliverables +- Update `src/mcpbridge_wrapper/webui/static/dashboard.css` to ensure chart containers shrink without clipping and canvas sizing remains responsive. +- Update `src/mcpbridge_wrapper/webui/static/dashboard.js` to reposition doughnut legends for medium widths. +- Add/adjust frontend tests for responsive legend behavior and resize handling. +- Validation report in `SPECS/INPROGRESS/BUG-T16_Validation_Report.md`. + +## Acceptance Criteria +- Pie widget is not visually cropped at ~1200px viewport. +- Existing behavior at ~1450px and <768px remains functional. +- Chart rendering remains stable during resize events. +- Test suite passes, including required quality gates. + +## Dependencies +- Existing Web UI dashboard chart rendering (P10-T1). + +## Risks +- Resize handler overhead if updates trigger too often. +- Chart legend changes could affect visual density at desktop widths. + +## Validation Plan +- Run automated tests for web UI frontend behavior. +- Run repository quality gates (`pytest`, `ruff check src/`, `mypy src/`, `pytest --cov`). +- Manual verification checklist at representative widths: 1450px, 1200px, 1024px, 768px. diff --git a/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Validation_Report.md b/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Validation_Report.md new file mode 100644 index 00000000..ce9fce47 --- /dev/null +++ b/SPECS/ARCHIVE/BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/BUG-T16_Validation_Report.md @@ -0,0 +1,32 @@ +# BUG-T16 Validation Report + +## Task +BUG-T16 — Tool Distribution (Pie) widget is cropped at medium widths + +## Implementation Summary +- Updated chart container CSS to allow safe shrink behavior and prevent clipping at medium widths. +- Added responsive canvas sizing defaults for chart rendering stability. +- Added JS responsive legend-layout logic for doughnut charts (pie + error breakdown): + - Right-side legend at wide widths. + - Bottom legend at medium/mobile widths (<= 1280px). +- Added a unit test asserting responsive doughnut legend logic is present in dashboard JS. + +## Files Changed +- `src/mcpbridge_wrapper/webui/static/dashboard.css` +- `src/mcpbridge_wrapper/webui/static/dashboard.js` +- `tests/unit/webui/test_server.py` + +## Quality Gates +- `pytest -q` => PASS (629 passed, 5 skipped) +- `ruff check src/` => PASS +- `mypy src/` => PASS +- `pytest --cov` => PASS (91.33% total, threshold >= 90%) + +## Acceptance Criteria Check +- Pie widget cropping at medium widths addressed via responsive container + legend reflow => PASS +- Behavior at wide and mobile breakpoints preserved by breakpoint-aware legend positioning => PASS +- Resize handling added (`window.resize` listener) => PASS +- Automated tests and quality gates passing => PASS + +## Notes +- Existing deprecation warnings from `websockets`/`uvicorn` are unchanged and non-blocking for this task. diff --git a/SPECS/ARCHIVE/INDEX.md b/SPECS/ARCHIVE/INDEX.md index e1e8de6d..ee05ebb1 100644 --- a/SPECS/ARCHIVE/INDEX.md +++ b/SPECS/ARCHIVE/INDEX.md @@ -1,6 +1,6 @@ # mcpbridge-wrapper Tasks Archive -**Last Updated:** 2026-02-20 (BUG-T15_WebUI_Port_Config_Investigation) +**Last Updated:** 2026-02-20 (BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths) ## Archived Tasks @@ -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 | | BUG-T15 | [BUG-T15_WebUI_Port_Config_Investigation/](BUG-T15_WebUI_Port_Config_Investigation/) | 2026-02-20 | PASS | +| BUG-T16 | [BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/](BUG-T16_Tool_Distribution_Pie_widget_is_cropped_at_medium_widths/) | 2026-02-20 | 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 | | P11-T4 | [P11-T4_Add_Keyboard_Shortcuts_Command_Palette/](P11-T4_Add_Keyboard_Shortcuts_Command_Palette/) | 2026-02-15 | PASS | @@ -239,6 +240,7 @@ | [REVIEW_P14-T5_broker_socket_path_limit.md](_Historical/REVIEW_P14-T5_broker_socket_path_limit.md) | Review report for P14-T5 | | [REVIEW_FU-P14-T5-1_macos_ci_socket_path.md](_Historical/REVIEW_FU-P14-T5-1_macos_ci_socket_path.md) | Review report for FU-P14-T5-1 | | [REVIEW_bug_t15_webui_port_config.md](_Historical/REVIEW_bug_t15_webui_port_config.md) | Review report for BUG-T15 | +| [REVIEW_bug_t16_pie_responsive.md](_Historical/REVIEW_bug_t16_pie_responsive.md) | Review report for BUG-T16 | ## Archive Log @@ -433,3 +435,5 @@ | 2026-02-20 | FU-P14-T5-1 | Archived REVIEW_FU-P14-T5-1_macos_ci_socket_path report | | 2026-02-20 | BUG-T15 | Archived WebUI_Port_Config_Investigation (PASS) | | 2026-02-20 | BUG-T15 | Archived REVIEW_bug_t15_webui_port_config report | +| 2026-02-20 | BUG-T16 | Archived Tool_Distribution_Pie_widget_is_cropped_at_medium_widths (PASS) | +| 2026-02-20 | BUG-T16 | Archived REVIEW_bug_t16_pie_responsive report | diff --git a/SPECS/ARCHIVE/_Historical/REVIEW_bug_t16_pie_responsive.md b/SPECS/ARCHIVE/_Historical/REVIEW_bug_t16_pie_responsive.md new file mode 100644 index 00000000..ef81986d --- /dev/null +++ b/SPECS/ARCHIVE/_Historical/REVIEW_bug_t16_pie_responsive.md @@ -0,0 +1,31 @@ +## REVIEW REPORT — BUG-T16 Pie Responsive Layout + +**Scope:** origin/main..HEAD +**Files:** 8 + +### Summary Verdict +- [x] Approve +- [ ] Approve with comments +- [ ] Request changes +- [ ] Block + +### Critical Issues +- None. + +### Secondary Issues +- None. + +### Architectural Notes +- The fix is minimal and targeted: CSS shrink-safety (`min-width: 0`, `canvas width: 100%`) plus dynamic doughnut legend placement for medium widths. +- Resize handling is scoped to chart legend orientation and does not alter data flow. + +### Tests +- Added static-asset assertion test for responsive doughnut legend logic in `tests/unit/webui/test_server.py`. +- Quality gates all pass: + - `pytest -q` PASS + - `ruff check src/` PASS + - `mypy src/` PASS + - `pytest --cov` PASS (`91.33%`) + +### Next Steps +- FOLLOW-UP skipped: no actionable issues identified. diff --git a/SPECS/INPROGRESS/next.md b/SPECS/INPROGRESS/next.md index e12c31c7..50613c8b 100644 --- a/SPECS/INPROGRESS/next.md +++ b/SPECS/INPROGRESS/next.md @@ -2,8 +2,11 @@ ## Recently Archived +- **BUG-T16** — Tool Distribution (Pie) widget is cropped at medium widths (2026-02-20, PASS) - **BUG-T15** — Web UI fails to come up in MCP client runs when `--web-ui-port` and `--web-ui-config` are combined (2026-02-20, PASS) ## Suggested Next Tasks -- None currently ready. +- BUG-T17 — Rows in Audit Log table automatically fold after user unfolds them +- BUG-T14 — Rows in Per-Tool Latency Statistics fold automatically immediately after unfolding +- BUG-T10 — Tool chart colors change on update of tool type count diff --git a/SPECS/Workplan.md b/SPECS/Workplan.md index 98a53202..aa25e053 100644 --- a/SPECS/Workplan.md +++ b/SPECS/Workplan.md @@ -1436,7 +1436,7 @@ Enable parameter capture by passing `--web-ui-config` with `metrics.capture_para --- -### BUG-T14: Rows in Per-Tool Latency Statistics collapse after 1 second +### BUG-T14: Rows in Per-Tool Latency Statistics fold automatically immediately after unfolding - **Type:** Bug / Web UI / UI Stability - **Status:** 🔴 Open - **Priority:** P1 @@ -1446,12 +1446,12 @@ Enable parameter capture by passing `--web-ui-config` with `metrics.capture_para - **Affected Surface:** Per-Tool Latency Statistics table #### Description -Expanded or highlighted rows in the Per-Tool Latency Statistics table collapse/reset approximately every 1 second. This coincides with the dashboard's default WebSocket refresh interval (`dashboard.refresh_interval_ms: 1000`), suggesting the table is being fully re-rendered on each update, discarding any user interaction state (expanded rows, hover highlights, selected rows). +Expanded or highlighted rows in the Per-Tool Latency Statistics table fold/collapse automatically immediately (or within about 1 second) after the user unfolds them. This coincides with the dashboard's default WebSocket refresh interval (`dashboard.refresh_interval_ms: 1000`), suggesting the table is being fully re-rendered on each update, discarding user interaction state (expanded rows, hover highlights, selected rows). #### Symptoms ``` User expands a row in the Per-Tool Latency Statistics table to inspect details. -After ~1 second the row collapses back to its default state. +Immediately (or after ~1 second) the row collapses back to its default state. Behaviour repeats on every subsequent refresh cycle. ``` @@ -1518,6 +1518,82 @@ Use `--web-ui` with `--web-ui-config` only, and set the port in the config file. --- +### BUG-T16: Tool Distribution (Pie) widget is cropped at medium widths +- **Type:** Bug / Web UI / Responsive Layout +- **Status:** ✅ Fixed (2026-02-20) +- **Priority:** P1 +- **Discovered:** 2026-02-20 +- **Completed:** 2026-02-20 +- **Component:** Web UI Dashboard (`webui/static/`, chart layout CSS) +- **Affected Clients:** All clients using Web UI dashboard +- **Affected Surface:** Tool Distribution (Pie) widget + +#### Description +The "Tool Distribution (Pie)" widget does not flow correctly when the dashboard width is reduced to medium breakpoints. At around `1450px` layout is fine, around `1200px` the pie widget gets cropped, and below `768px` it becomes okay again. + +#### Symptoms +```text +~1450px viewport: widget displays correctly +~1200px viewport: Tool Distribution (Pie) widget is cropped +<768px viewport: widget displays correctly again +``` + +#### Root Cause Analysis +Likely a responsive breakpoint/layout gap between desktop and mobile rules where the chart container keeps a width/min-width/flex basis that overflows or clips at mid-range viewport sizes. + +#### Workaround +Resize the browser to either wide desktop (`~1450px+`) or narrow mobile (`<768px`) widths where the widget renders correctly. + +#### Resolution Path +- [x] Reproduce and capture exact breakpoint range where cropping begins/ends +- [x] Inspect chart container/grid/flex CSS rules for mid-width breakpoints (especially min-width, fixed widths, and overflow settings) +- [x] Update responsive CSS so the pie widget reflows without clipping at medium widths +- [x] Add/extend frontend responsiveness test or manual regression checklist for `1200px` range +- [x] Validate layout at `1450px`, `1200px`, `1024px`, `768px`, and mobile widths + +#### Related Items +- **P10-T1** ✅ — Web UI Dashboard implementation (chart layout subsystem) +- **BUG-T10** — Tool chart rendering instability; related chart UX surface + +--- + +### BUG-T17: Rows in Audit Log table automatically fold after user unfolds them +- **Type:** Bug / Web UI / UI Stability +- **Status:** 🔴 Open +- **Priority:** P1 +- **Discovered:** 2026-02-20 +- **Component:** Web UI Dashboard (`webui/static/`, audit log table rendering) +- **Affected Clients:** All clients using Web UI dashboard +- **Affected Surface:** Audit Log table row expand/collapse behavior + +#### Description +Rows in the Audit Log table automatically fold/collapse several seconds after a user unfolds them. This interrupts inspection workflows and suggests expanded row UI state is being reset during periodic dashboard updates. + +#### Symptoms +```text +User unfolds an Audit Log row to inspect details. +After several seconds, the row folds automatically without user action. +``` + +#### Root Cause Analysis +Likely caused by full table re-render on refresh/WebSocket updates without preserving expanded-row state, similar to other dashboard state-reset issues. + +#### Workaround +Temporarily increase dashboard refresh interval via config to reduce frequency of auto-fold behavior. + +#### Resolution Path +- [ ] Reproduce with default refresh interval and identify the exact trigger (WebSocket update vs polling refresh) +- [ ] Refactor Audit Log table updates to patch rows by stable entry ID instead of full DOM replacement +- [ ] Persist expanded/collapsed row state across refresh cycles +- [ ] Add regression test (or manual checklist) confirming unfolded rows stay unfolded across updates +- [ ] Validate behavior under active tool-call traffic + +#### Related Items +- **BUG-T12** — Audit Log update path not showing new calls; same component/surface +- **BUG-T14** — Per-Tool Latency row state resets on refresh; similar UI-state loss pattern + +--- + ### Phase 10: Web UI Control & Audit Dashboard **Intent:** Create a web-based dashboard for real-time monitoring, control, and audit logging of the XcodeMCPWrapper. Provides visibility into MCP tool usage, performance metrics, and operational control. diff --git a/src/mcpbridge_wrapper/webui/static/dashboard.css b/src/mcpbridge_wrapper/webui/static/dashboard.css index b200472d..b3901288 100644 --- a/src/mcpbridge_wrapper/webui/static/dashboard.css +++ b/src/mcpbridge_wrapper/webui/static/dashboard.css @@ -172,6 +172,7 @@ main { border: 1px solid var(--border-color); border-radius: var(--radius); padding: 16px; + min-width: 0; } .chart-container.wide { @@ -185,6 +186,8 @@ main { } canvas { + display: block; + width: 100%; max-height: 280px; } diff --git a/src/mcpbridge_wrapper/webui/static/dashboard.js b/src/mcpbridge_wrapper/webui/static/dashboard.js index dd1823f9..d235bde3 100644 --- a/src/mcpbridge_wrapper/webui/static/dashboard.js +++ b/src/mcpbridge_wrapper/webui/static/dashboard.js @@ -61,6 +61,7 @@ "#f85149", "#79c0ff", "#56d364", "#d2a8ff", "#e3b341", "#ffa198", ]; + const MEDIUM_WIDTH_BREAKPOINT = 1280; // --- Utility --- function formatUptime(seconds) { @@ -185,6 +186,26 @@ animation: { duration: 300 }, }, }); + + updateDoughnutLegendLayout(); + } + + function shouldUseBottomLegend() { + return window.innerWidth <= MEDIUM_WIDTH_BREAKPOINT; + } + + function updateDoughnutLegendLayout() { + var legendPosition = shouldUseBottomLegend() ? "bottom" : "right"; + ["toolPie", "errorBreakdown"].forEach(function (chartName) { + var chart = charts[chartName]; + if (!chart || !chart.options || !chart.options.plugins || !chart.options.plugins.legend) { + return; + } + if (chart.options.plugins.legend.position !== legendPosition) { + chart.options.plugins.legend.position = legendPosition; + chart.update("none"); + } + }); } // --- Update Functions --- @@ -792,6 +813,7 @@ initCharts(); initTheme(); setupThemeToggle(); + window.addEventListener("resize", updateDoughnutLegendLayout); setupEventHandlers(); initKeyboardShortcuts(); connectWebSocket(); diff --git a/tests/unit/webui/test_server.py b/tests/unit/webui/test_server.py index c4e30e32..3791fb81 100644 --- a/tests/unit/webui/test_server.py +++ b/tests/unit/webui/test_server.py @@ -168,6 +168,15 @@ def test_dashboard_js_uses_uniform_client_widget_escaping(self, client): assert "var escapedCount = escapeHtml(String(count));" in response.text assert "var escapedLastSeen = escapeHtml(String(lastSeen));" in response.text + def test_dashboard_js_has_responsive_doughnut_legend_logic(self, client): + """Pie/error doughnut legends switch layout at medium widths.""" + response = client.get("/static/dashboard.js") + assert response.status_code == 200 + assert "const MEDIUM_WIDTH_BREAKPOINT = 1280;" in response.text + assert "function updateDoughnutLegendLayout()" in response.text + assert '["toolPie", "errorBreakdown"]' in response.text + assert 'window.addEventListener("resize", updateDoughnutLegendLayout);' in response.text + def test_websocket_metrics_update_includes_sessions(self, client, audit): """WebSocket metrics_update message includes sessions key.""" with client.websocket_connect("/ws/metrics") as websocket: