diff --git a/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them.md b/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them.md new file mode 100644 index 0000000..3069ccb --- /dev/null +++ b/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them.md @@ -0,0 +1,52 @@ +# PRD: BUG-T17 — Rows in Audit Log table automatically fold after user unfolds them + +## Objective +Stabilize the Audit Log table interaction model so row expansion state persists across dashboard refresh/update cycles. Users should be able to open one or more audit rows and continue inspecting detailed payloads without the UI collapsing those rows during periodic data refreshes. + +This task is scoped to the frontend rendering/update pipeline for the Audit Log widget in `src/mcpbridge_wrapper/webui/static/`. The backend API contracts (`/api/audit`, websocket update payloads) should remain unchanged unless implementation reveals an identifier stability issue that prevents reliable row-state reconciliation. + +## Success Criteria +- Expanded rows remain expanded after repeated audit refreshes. +- Collapse state changes only on explicit user click. +- Behavior remains correct during active tool-call traffic. +- Existing audit rendering behavior (row ordering, detail formatting) is preserved. +- Automated tests cover the regression scenario. + +## Acceptance Tests +1. Open dashboard, expand an audit row, wait through multiple refresh cycles, verify row remains expanded. +2. Expand multiple rows, confirm each remains expanded after updates. +3. Collapse one expanded row manually, verify only that row collapses. +4. Run full quality gates (`pytest`, `ruff check src/`, `mypy src/`, `pytest --cov`) with coverage remaining >= 90%. + +## Test-First Plan +- Identify current tests for audit dashboard rendering/update behavior. +- Add/extend a frontend unit test that reproduces auto-collapse after table update. +- Assert the fix by verifying expanded state survives multiple update invocations with stable entry identifiers. + +## Execution Plan +### Phase 1: Diagnose Update Path +- Inputs: current audit table JS update functions, refresh triggers. +- Outputs: identified state reset point and reconciliation strategy. +- Verification: local code trace confirms why expansion resets. + +### Phase 2: Implement State-Preserving Reconciliation +- Inputs: expansion state map keyed by stable audit entry ID. +- Outputs: incremental DOM update or rebuild path that reapplies expansion state. +- Verification: manual local behavior check under simulated repeated updates. + +### Phase 3: Regression Coverage and Validation +- Inputs: frontend tests and existing web UI test suite. +- Outputs: regression test(s) proving no auto-fold. +- Verification: all quality gates pass and validation report documents evidence. + +## Constraints and Decisions +- Preserve existing API schema and data polling cadence. +- Keep solution lightweight in frontend; avoid introducing new dependencies. +- Prefer deterministic keying on audit entry ID/timestamp tuple if no dedicated stable ID exists. + +## Notes +- If related row-state reset patterns are discovered in adjacent widgets, capture them as follow-up tasks instead of expanding BUG-T17 scope. + +--- +**Archived:** 2026-02-20 +**Verdict:** PASS diff --git a/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Validation_Report.md b/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Validation_Report.md new file mode 100644 index 0000000..78c2534 --- /dev/null +++ b/SPECS/ARCHIVE/BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/BUG-T17_Validation_Report.md @@ -0,0 +1,38 @@ +# Validation Report: BUG-T17 + +## Task +Rows in Audit Log table automatically fold after user unfolds them. + +## Implementation Summary +- Added persistent audit row expansion tracking in `dashboard.js` via `auditExpandedRows` keyed by stable row identity. +- Preserved expanded state across periodic `loadAuditLogs()` refreshes by collecting and reapplying expanded rows after table redraw. +- Reset expansion state on explicit pagination/filter changes to avoid stale carry-over across different result sets. +- Added regression coverage in `tests/unit/webui/test_server.py` to assert presence of expansion-state preservation logic in served frontend bundle. + +## Quality Gates + +### 1) `PYTHONPATH=src pytest` +- Result: PASS +- Evidence: `630 passed, 5 skipped` + +### 2) `ruff check src/` +- Result: PASS +- Evidence: `All checks passed!` + +### 3) `PYTHONPATH=src mypy src/` +- Result: PASS +- Evidence: `Success: no issues found in 18 source files` + +### 4) `PYTHONPATH=src pytest --cov` +- Result: PASS +- Evidence: + - `630 passed, 5 skipped` + - `Required test coverage of 90.0% reached` + - `Total coverage: 91.33%` + +## Manual Validation Notes +- Code path confirms previous behavior rebuilt the audit table every 5 seconds and dropped expanded row DOM state. +- New logic explicitly restores expanded state for rows still present after refresh. + +## Verdict +PASS diff --git a/SPECS/ARCHIVE/INDEX.md b/SPECS/ARCHIVE/INDEX.md index eb629e3..ebee80d 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-T18_Error_Breakdown_widget_must_be_full_width_streatched) +**Last Updated:** 2026-02-20 (BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them) ## Archived Tasks @@ -94,6 +94,7 @@ | 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 | | BUG-T18 | [BUG-T18_Error_Breakdown_widget_must_be_full_width_streatched/](BUG-T18_Error_Breakdown_widget_must_be_full_width_streatched/) | 2026-02-20 | PASS | +| BUG-T17 | [BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/](BUG-T17_Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them/) | 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 | @@ -243,6 +244,7 @@ | [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 | | [REVIEW_bug_t18_workplan_entry.md](_Historical/REVIEW_bug_t18_workplan_entry.md) | Review report for BUG-T18 | +| [REVIEW_bug_t17_audit_log_rows_stay_unfolded.md](_Historical/REVIEW_bug_t17_audit_log_rows_stay_unfolded.md) | Review report for BUG-T17 | ## Archive Log @@ -441,3 +443,5 @@ | 2026-02-20 | BUG-T16 | Archived REVIEW_bug_t16_pie_responsive report | | 2026-02-20 | BUG-T18 | Archived Error_Breakdown_widget_must_be_full_width_streatched (PASS) | | 2026-02-20 | BUG-T18 | Archived REVIEW_bug_t18_workplan_entry report | +| 2026-02-20 | BUG-T17 | Archived Rows_in_Audit_Log_table_automatically_fold_after_user_unfolds_them (PASS) | +| 2026-02-20 | BUG-T17 | Archived REVIEW_bug_t17_audit_log_rows_stay_unfolded report | diff --git a/SPECS/ARCHIVE/_Historical/REVIEW_bug_t17_audit_log_rows_stay_unfolded.md b/SPECS/ARCHIVE/_Historical/REVIEW_bug_t17_audit_log_rows_stay_unfolded.md new file mode 100644 index 0000000..732c312 --- /dev/null +++ b/SPECS/ARCHIVE/_Historical/REVIEW_bug_t17_audit_log_rows_stay_unfolded.md @@ -0,0 +1,34 @@ +## REVIEW REPORT — BUG-T17 audit-log-rows-stay-unfolded + +**Scope:** origin/main..HEAD +**Files:** 7 + +### Summary Verdict +- [x] Approve +- [ ] Approve with comments +- [ ] Request changes +- [ ] Block + +### Critical Issues +- None. + +### Secondary Issues +- None. + +### Architectural Notes +- The fix keeps state management local to `dashboard.js` and avoids API contract changes. +- Expansion state is reset on explicit page/filter navigation, reducing stale-row carryover risk. +- Reopening details after refresh re-fetches payload details for expanded rows; this is acceptable for current scale and keeps implementation simple. + +### Tests +- Added regression assertion in `tests/unit/webui/test_server.py` to verify audit-row state-preservation logic is present in served frontend bundle. +- Quality gates passed: + - `PYTHONPATH=src pytest` + - `ruff check src/` + - `PYTHONPATH=src mypy src/` + - `PYTHONPATH=src pytest --cov` +- Coverage remains above threshold: `91.33%`. + +### Next Steps +- No actionable review findings. +- FOLLOW-UP step is skipped for BUG-T17. diff --git a/SPECS/INPROGRESS/next.md b/SPECS/INPROGRESS/next.md index d82ec9e..c2aeb6f 100644 --- a/SPECS/INPROGRESS/next.md +++ b/SPECS/INPROGRESS/next.md @@ -2,12 +2,12 @@ ## Recently Archived +- **BUG-T17** — Rows in Audit Log table automatically fold after user unfolds them (2026-02-20, PASS) - **BUG-T18** — Error Breakdown widget must be full width streatched (2026-02-20, PASS) - **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 -- 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 +- BUG-T12 — New audit log entries are not shown in the dashboard in real time diff --git a/SPECS/Workplan.md b/SPECS/Workplan.md index 3ac33c2..eab6980 100644 --- a/SPECS/Workplan.md +++ b/SPECS/Workplan.md @@ -1559,9 +1559,10 @@ Resize the browser to either wide desktop (`~1450px+`) or narrow mobile (`<768px ### BUG-T17: Rows in Audit Log table automatically fold after user unfolds them - **Type:** Bug / Web UI / UI Stability -- **Status:** 🔴 Open +- **Status:** ✅ Fixed (2026-02-20) - **Priority:** P1 - **Discovered:** 2026-02-20 +- **Completed:** 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 @@ -1582,11 +1583,11 @@ Likely caused by full table re-render on refresh/WebSocket updates without prese 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 +- [x] Reproduce with default refresh interval and identify the exact trigger (WebSocket update vs polling refresh) +- [x] Refactor Audit Log table updates to patch rows by stable entry ID instead of full DOM replacement +- [x] Persist expanded/collapsed row state across refresh cycles +- [x] Add regression test (or manual checklist) confirming unfolded rows stay unfolded across updates +- [x] Validate behavior under active tool-call traffic #### Related Items - **BUG-T12** — Audit Log update path not showing new calls; same component/surface diff --git a/src/mcpbridge_wrapper/webui/static/dashboard.js b/src/mcpbridge_wrapper/webui/static/dashboard.js index d235bde..5d6e792 100644 --- a/src/mcpbridge_wrapper/webui/static/dashboard.js +++ b/src/mcpbridge_wrapper/webui/static/dashboard.js @@ -9,6 +9,7 @@ let auditPage = 0; const auditPageSize = 50; let auditFilter = ""; + var auditExpandedRows = Object.create(null); // --- Theme --- var THEME_COLORS = { @@ -432,15 +433,49 @@ .replace(/"/g, """); } - function toggleDetailRow(tr, requestId) { - var existing = tr.nextSibling; + function getAuditRowKey(entry) { + return [ + String(entry.request_id || "-"), + String(entry.timestamp_iso || "-"), + String(entry.tool || "-"), + String(entry.direction || "-"), + String(entry.error || "-"), + ].join("|"); + } + + function collectExpandedAuditRows(tbody) { + var expanded = Object.create(null); + if (!tbody) { + return expanded; + } + + var openRows = tbody.querySelectorAll("tr.audit-row.detail-row-open"); + for (var i = 0; i < openRows.length; i++) { + var rowKey = openRows[i].getAttribute("data-audit-row-key"); + if (rowKey) { + expanded[rowKey] = true; + } + } + + return expanded; + } + + function toggleDetailRow(tr, requestId, rowKey, persistState) { + var shouldPersist = persistState !== false; + var existing = tr.nextElementSibling; if (existing && existing.classList && existing.classList.contains("detail-row")) { existing.parentNode.removeChild(existing); tr.classList.remove("detail-row-open"); + if (shouldPersist && rowKey) { + delete auditExpandedRows[rowKey]; + } return; } tr.classList.add("detail-row-open"); + if (shouldPersist && rowKey) { + auditExpandedRows[rowKey] = true; + } var detailTr = document.createElement("tr"); detailTr.className = "detail-row"; var td = document.createElement("td"); @@ -490,14 +525,23 @@ .then(function (r) { return r.json(); }) .then(function (data) { var tbody = el("audit-table").querySelector("tbody"); + var expandedRows = collectExpandedAuditRows(tbody); + for (var key in auditExpandedRows) { + if (Object.prototype.hasOwnProperty.call(auditExpandedRows, key)) { + expandedRows[key] = true; + } + } tbody.innerHTML = ""; + var nextExpandedRows = Object.create(null); if (!data.entries.length) { tbody.innerHTML = "