Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# PRD: FU-P12-T2-1 — Fix stacking click event listeners in `updateLatencyTable`

**Status:** In Progress
**Priority:** P3
**Depends on:** P12-T2 ✅
**Date:** 2026-02-16

---

## Problem

`updateLatencyTable(toolLatency)` is called on every polling cycle (~2 s) from
`handleMetricsUpdate`. Each invocation clears `tbody.innerHTML` and rebuilds rows,
but it also calls `tbody.addEventListener("click", handler)` unconditionally. Since
`tbody` is the same DOM node across calls (only its children are replaced), each
call stacks another identical click handler on it. After N refresh cycles, a single
button click fires the toggle logic N times, triggering N `fetchParamPatterns`
requests for the same row.

## Root Cause

```js
// dashboard.js:332 — inside updateLatencyTable, called every ~2 s
tbody.addEventListener("click", function (e) { … });
```

The listener is registered inside the function that is called on every poll, with no
guard against re-registration.

## Fix

Move the click listener to `setupEventHandlers()`, which runs once at init. Attach
it to `el("latency-table")` (the stable `<table>` element) instead of `tbody`, using
the same event-delegation pattern (`e.target.closest(".param-toggle-btn")`). Remove
the `tbody.addEventListener(...)` block from `updateLatencyTable`.

## Deliverables

- `src/mcpbridge_wrapper/webui/static/dashboard.js` — listener moved to
`setupEventHandlers`, removed from `updateLatencyTable`.

## Acceptance Criteria

- `updateLatencyTable` contains no `addEventListener` call.
- `setupEventHandlers` registers exactly one delegated click handler on
`el("latency-table")`.
- Toggle expand/collapse behaviour is unchanged after the fix.
- `fetchParamPatterns` is called exactly once per button click regardless of how
many times `updateLatencyTable` has been called before.
- All existing tests pass (`pytest`, `ruff check src/`).

## Out of Scope

- Changes to `fetchParamPatterns`, row rendering, or any other part of the dashboard.

---
**Archived:** 2026-02-16
**Verdict:** PASS
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Validation Report: FU-P12-T2-1

**Task:** Fix stacking click event listeners in `updateLatencyTable`
**Date:** 2026-02-16
**Verdict:** PASS

---

## Change Summary

Moved the delegated click handler for `.param-toggle-btn` out of
`updateLatencyTable` (which runs every ~2 s) into `setupEventHandlers` (which runs
once at init). The handler is now attached to `el("latency-table")` instead of
`tbody`, keeping the same event-delegation pattern.

**File changed:** `src/mcpbridge_wrapper/webui/static/dashboard.js`
- Removed `tbody.addEventListener("click", …)` block from `updateLatencyTable` (lines 332-350 prior to fix)
- Added equivalent `el("latency-table").addEventListener("click", …)` in `setupEventHandlers` (lines 570-588)

---

## Quality Gates

| Gate | Result |
|------|--------|
| `pytest` | ✅ 465 passed, 5 skipped |
| `ruff check src/` | ✅ All checks passed |
| `pytest --cov` | ✅ 95.6% (≥ 90% required) |
| `mypy src/` | N/A — not configured |

---

## Acceptance Criteria Verification

| Criterion | Status |
|-----------|--------|
| `updateLatencyTable` contains no `addEventListener` call | ✅ |
| `setupEventHandlers` registers exactly one delegated click handler on `el("latency-table")` | ✅ |
| Toggle expand/collapse behaviour unchanged | ✅ (same handler logic) |
| `fetchParamPatterns` called exactly once per click regardless of prior refresh count | ✅ (single registered listener) |
| All existing tests pass | ✅ 465 passed |
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-16 (FU-P11-T1-1)
**Last Updated:** 2026-02-16

## Archived Tasks

Expand Down Expand Up @@ -101,6 +101,7 @@
| FU-P11-T2-1 | [FU-P11-T2-1_Push_Session_Data_via_WebSocket/](FU-P11-T2-1_Push_Session_Data_via_WebSocket/) | 2026-02-16 | PASS |
| FU-P11-T2-2 | [FU-P11-T2-2_Add_limit_query_param_to_GET_api_sessions/](FU-P11-T2-2_Add_limit_query_param_to_GET_api_sessions/) | 2026-02-16 | PASS |
| FU-P11-T1-1 | [FU-P11-T1-1_Refactor_FakeWebUIConfig_to_MagicMock/](FU-P11-T1-1_Refactor_FakeWebUIConfig_to_MagicMock/) | 2026-02-16 | PASS |
| FU-P12-T2-1 | [FU-P12-T2-1_Fix_stacking_click_listeners_in_updateLatencyTable/](FU-P12-T2-1_Fix_stacking_click_listeners_in_updateLatencyTable/) | 2026-02-16 | PASS |

## Historical Artifacts

Expand Down Expand Up @@ -165,6 +166,7 @@
| [REVIEW_P12-T2_param_frequency_analysis.md](P12-T2_Add_Tool_Parameter_Frequency_Analysis/REVIEW_P12-T2_param_frequency_analysis.md) | Review report for P12-T2 |
| [REVIEW_FU-P11-T2-2_limit_query_param.md](_Historical/REVIEW_FU-P11-T2-2_limit_query_param.md) | Review report for FU-P11-T2-2 |
| [REVIEW_FU-P11-T1-1_fake_webuiconfig_refactor.md](_Historical/REVIEW_FU-P11-T1-1_fake_webuiconfig_refactor.md) | Review report for FU-P11-T1-1 |
| [REVIEW_FU-P12-T2-1_stacking_click_listeners.md](_Historical/REVIEW_FU-P12-T2-1_stacking_click_listeners.md) | Review report for FU-P12-T2-1 |

## Archive Log

Expand Down Expand Up @@ -284,3 +286,5 @@
| 2026-02-16 | FU-P11-T2-2 | Archived REVIEW_FU-P11-T2-2_limit_query_param report |
| 2026-02-16 | FU-P11-T1-1 | Archived Refactor_FakeWebUIConfig_to_MagicMock (PASS) |
| 2026-02-16 | FU-P11-T1-1 | Archived REVIEW_FU-P11-T1-1_fake_webuiconfig_refactor report |
| 2026-02-16 | FU-P12-T2-1 | Archived Fix_stacking_click_listeners_in_updateLatencyTable (PASS) |
| 2026-02-16 | FU-P12-T2-1 | Archived REVIEW_FU-P12-T2-1_stacking_click_listeners report |
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## REVIEW REPORT — FU-P12-T2-1: Fix stacking click listeners in updateLatencyTable

**Scope:** origin/main..HEAD
**Files:** 1 (dashboard.js)
**Date:** 2026-02-16

---

### Summary Verdict

- [x] Approve

---

### Critical Issues

None.

---

### Secondary Issues

None.

---

### Architectural Notes

- The fix correctly moves the delegated click handler from `updateLatencyTable`
(called every ~2 s by the polling loop) into `setupEventHandlers` (called once at
`init()`), eliminating listener stacking entirely.
- Attaching to `el("latency-table")` (the stable `<table>` element) rather than
`tbody` (whose children are rebuilt on each poll) is the right approach: the table
element is present in the static HTML and survives all DOM rebuilds.
- Event delegation via `e.target.closest(".param-toggle-btn")` is preserved
unchanged, so the handler correctly targets dynamically created buttons regardless
of when they were inserted.
- The change is a pure refactor (no logic changes); the handler body is identical to
the original.

---

### Tests

- 465 tests pass; 5 skipped. Coverage 95.6% (requirement: ≥90%).
- No JS unit tests exist for dashboard.js — this is consistent with the existing
test suite which covers only the Python backend. No gap introduced.

---

### Next Steps

- No actionable issues found. 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-16 — FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable` (PASS)
- 2026-02-16 — FU-P11-T1-1: Refactor `_FakeWebUIConfig` test stub to use `MagicMock(spec=WebUIConfig)` (PASS)
- 2026-02-16 — FU-P11-T2-2: Add `limit` query param to `GET /api/sessions` (PASS)
- 2026-02-16 — FU-P11-T2-1: Push session data via WebSocket (PASS)
- 2026-02-16 — P12-T2: Add Tool Parameter Frequency Analysis (PASS)
- 2026-02-15 — P11-T4: Add Keyboard Shortcuts & Command Palette (PASS)

## Suggested Next Tasks

- FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable` (P3, depends on P12-T2 ✅)
- FU-P12-T1-1: Remove or document `MCPInitializeParams` in schemas (P3, depends on P12-T1 ✅)
- FU-P12-T3-2: Add `error_code` column to audit CSV export (P3, depends on P12-T3 ✅)
- FU-P12-T1-2: Add code comment clarifying stdin-only client capture in `on_request` (P3, depends on P12-T1 ✅)
2 changes: 1 addition & 1 deletion SPECS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1993,7 +1993,7 @@ Phase 9 Follow-up Backlog

---

#### FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable`
#### FU-P12-T2-1: Fix stacking click event listeners in `updateLatencyTable`
- **Description:** The `tbody.addEventListener("click", ...)` call inside `updateLatencyTable()` in `dashboard.js` is re-registered on every metrics refresh (every 1 second), causing listeners to accumulate. Move the click handler to a one-time setup function called once during dashboard initialization, or guard with a `data-listener-attached` sentinel attribute on the tbody element.
- **Priority:** P3
- **Dependencies:** P12-T2
Expand Down
40 changes: 20 additions & 20 deletions src/mcpbridge_wrapper/webui/static/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,26 +328,6 @@
+ "<em style='color:#8b949e'>Loading\u2026</em></div></td>";
tbody.appendChild(detailTr);
});

tbody.addEventListener("click", function (e) {
var btn = e.target.closest(".param-toggle-btn");
if (!btn) return;
var targetId = btn.getAttribute("data-target");
var toolName = btn.getAttribute("data-tool");
var detailRow = document.getElementById(targetId);
if (!detailRow) return;
var isOpen = detailRow.style.display !== "none";
if (isOpen) {
detailRow.style.display = "none";
btn.innerHTML = "&#x25B6;";
btn.setAttribute("aria-expanded", "false");
} else {
detailRow.style.display = "";
btn.innerHTML = "&#x25BC;";
btn.setAttribute("aria-expanded", "true");
fetchParamPatterns(toolName, "patterns-" + targetId);
}
});
}

function fetchParamPatterns(toolName, containerId) {
Expand Down Expand Up @@ -585,6 +565,26 @@
auditPage = 0;
loadAuditLogs();
});

el("latency-table").addEventListener("click", function (e) {
var btn = e.target.closest(".param-toggle-btn");
if (!btn) return;
var targetId = btn.getAttribute("data-target");
var toolName = btn.getAttribute("data-tool");
var detailRow = document.getElementById(targetId);
if (!detailRow) return;
var isOpen = detailRow.style.display !== "none";
if (isOpen) {
detailRow.style.display = "none";
btn.innerHTML = "&#x25B6;";
btn.setAttribute("aria-expanded", "false");
} else {
detailRow.style.display = "";
btn.innerHTML = "&#x25BC;";
btn.setAttribute("aria-expanded", "true");
fetchParamPatterns(toolName, "patterns-" + targetId);
}
});
}

// --- Session Timeline ---
Expand Down