You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Repository:MervinPraison/PraisonAIUI Type: Follow-up issue — post-merge gap from closed parent (do not reopen #195) Labels:bug, ui Priority: P0 Parent (closed):#195 Agent Command Center Merged in: PR #201 Verified:origin/main @ commit 6d194fc (2026-07-14) Primary file:src/praisonaiui/templates/frontend/plugins/views/overview.js
Executive summary
The Agent Command Center on /overview polls pending approvals every 10 seconds and partially re-renders only #ov-metrics and #ov-attention. After each poll it calls bindEvents(_container, data) on the entire overview root. Interactive elements that were not re-rendered — especially agent table rows (.db-agent-row) and the omnibar — accumulate stacked click / keydown listeners. A user who stays on Overview for one minute can have 7 handlers on the same agent row; one click may open the agent drawer multiple times or navigate twice.
This is a follow-up issue. Parent #195 is closed and the Command Center feature shipped in PR #201. This tracks a runtime bug found during post-merge verification on main — not a missing v1 feature.
Environment verified
Item
Value
Git ref
6d194fc — Merge PR #205 (latest main at audit time)
flowchart TB
subgraph paint_every_30s["paint() every 30s"]
P1[loadAll API] --> P2["_container.innerHTML = shell(data)"]
P2 --> P3["bindEvents(_container)"]
P3 --> P4["All DOM is new → one listener per element ✅"]
end
subgraph refresh_every_10s["refreshApprovals() every 10s"]
R1[GET /api/approvals/pending] --> R2["#ov-metrics innerHTML only"]
R2 --> R3["#ov-attention innerHTML only"]
R3 --> R4["bindEvents(_container)"]
R4 --> R5["Agent rows unchanged → listeners stack ❌"]
end
Loading
bindEvents(container, data) attaches listeners to
Selector
DOM location
Re-rendered by refreshApprovals?
Listener stacks?
[data-nav]
Metric cards inside #ov-metrics
Yes (metrics subtree)
Yes — if bound on _container
[data-omnibar]
Page shell header
No
Yes
.db-agent-row
Agent table in shell
No
Yes
.db-agent-chat
Agent table buttons
No
Yes
.atn-approve / .atn-deny
Inside #ov-attention
Yes (attention subtree)
Old nodes removed; new nodes get listeners via full-container bind — OK for buttons only if bind scoped to #ov-attention
Exact code on main (overview.js)
refreshApprovals() — problematic tail:
asyncfunctionrefreshApprovals(){if(!_container||document.hidden)return;letpending=[];try{pending=toArray(awaitfetchJson('/api/approvals/pending'),'approvals','pending');}catch(e){return;}constdata=_container.__ovData;if(!data)return;data.pending=pending;constmetricsEl=_container.querySelector('#ov-metrics');constattnEl=_container.querySelector('#ov-attention');if(metricsEl)metricsEl.innerHTML=renderMetrics(data);if(attnEl)attnEl.innerHTML=renderAttention(data);bindEvents(_container,data);// ← binds entire tree including stale agent rows}
bindEvents() — agent row binding (unchanged DOM between polls):
Single listener on _container using event.target.closest('.db-agent-row'). Polls never add per-element listeners. Larger refactor (~40 lines); not required for hotfix.
Acceptance criteria
ID
Given
When
Then
AC-1
Overview loaded with agents
Wait 50 s (5 approval polls)
One click on agent row opens drawer once
AC-2
Pending approvals visible
Wait 50 s
One click on metric [data-nav] navigates once
AC-3
Attention feed shows pending item
Click Approve
Item clears; no duplicate POST in network tab
AC-4
User on Overview
paint() runs at 30 s
Full page still works; no regression
AC-5
View destroyed
Navigate away
stopPolling() clears timers (existing behavior)
AC-6
CI
PR merged
pytest unit + integration green
Test plan
Manual
Step
Action
Expected
1
Open /overview with ≥1 agent and ≥1 pending approval
Page loads
2
Wait 35 s without clicking
Metrics/attention update
3
Click one agent row once
Single drawer open
4
Close drawer; wait another 35 s
—
5
Click same row again
Still single drawer open
6
Approve one pending item
Count decrements; row click still single
Automated (recommended)
// Pseudocode: mount overview, mock refreshApprovals 5×, spy openDrawerfor(leti=0;i<5;i++)awaitrefreshApprovals();row.click();expect(openDrawer).toHaveBeenCalledTimes(1);
Related items (separate work)
Item
Severity
Notes
Approval POST without shared auth headers
P3
Only if gateway enforces CSRF on POST
aria-live on pending approval count
P3
Screen reader polish
WebSocket live approvals
Out of scope
STITCH-001 v1 uses polling
Terminal evidence (2026-07-14)
Commands run from c:\Users\DELL\Downloads\praisonui against origin/main.
Git HEAD
git fetch origin main
git log origin/main -1--format="%H %ci %s"
6d194fce1be82c621aea24dfc48a3f34f73789bf 2026-07-14 09:32:47 +0000 Merge pull request #205 from MervinPraison/claude/issue-200-20260714-code-studio
{"closedAt":"2026-07-14T08:42:59Z","number":195,"state":"CLOSED","title":"Enhancement: Agent Command Center — mission-control Overview with live agents, approvals, and usage (STITCH-001)"}
{"mergedAt":"2026-07-14T08:42:58Z","number":201,"state":"MERGED","title":"feat: Agent Command Center overview (STITCH-001)"}
Line-number grep — polling + bindEvents
git show origin/main:src/praisonaiui/templates/frontend/plugins/views/overview.js |Select-String-Pattern "APPROVALS_POLL|METRICS_POLL|bindEvents\(|addEventListener"
Start gateway: cd examples/python/06-dashboard && aiui run (or existing process on 8103)
HTTP 200 on /overview
E2
Open DevTools → Console
No JS errors on load
E3
Open DevTools → Elements; find first .db-agent-row
Note data-agent-id
E4
Do not click. Wait 35 seconds (≥3 approval polls at 10s)
#ov-attention may update if approvals exist
E5
Click the same agent row once
Count drawer opens
E6
Close drawer; wait another 35 seconds
—
E7
Click same agent row once again
Count drawer opens
E2E expected vs actual (main @ 6d194fc)
Step
Expected (correct)
Actual on main
Pass?
E5 first click after 35s
Drawer opens 1×
Often 2–4× (stacked handlers)
FAIL
E7 after 70s total
Drawer opens 1×
3–7× depending on dwell time
FAIL
Approve click in attention feed
Single POST + refresh
Works but triggers extra refreshApprovals → faster stack
Partial
paint() at 30s
Full re-render; listeners reset
Works — masks bug if user only waits for metrics poll
OK
E2E procedure (DevTools listener proof)
Paste in browser console on /overview after 40s dwell:
constrow=document.querySelector('.db-agent-row');letopens=0;constorig=window.__ovOpenDrawer;// If openDrawer not exposed, spy click result instead:row.click();// Or use getEventListeners(row) in Chrome DevTools → Event Listeners panel// Expected after fix: 1 listener on click// Actual on main after 4 polls: 5 listeners on click
Repository: MervinPraison/PraisonAIUI
Type: Follow-up issue — post-merge gap from closed parent (do not reopen #195)
Labels:
bug,uiPriority: P0
Parent (closed): #195 Agent Command Center
Merged in: PR #201
Verified:
origin/main@ commit6d194fc(2026-07-14)Primary file:
src/praisonaiui/templates/frontend/plugins/views/overview.jsExecutive summary
The Agent Command Center on
/overviewpolls pending approvals every 10 seconds and partially re-renders only#ov-metricsand#ov-attention. After each poll it callsbindEvents(_container, data)on the entire overview root. Interactive elements that were not re-rendered — especially agent table rows (.db-agent-row) and the omnibar — accumulate stackedclick/keydownlisteners. A user who stays on Overview for one minute can have 7 handlers on the same agent row; one click may open the agent drawer multiple times or navigate twice.This is a follow-up issue. Parent #195 is closed and the Command Center feature shipped in PR #201. This tracks a runtime bug found during post-merge verification on
main— not a missing v1 feature.Environment verified
6d194fc— Merge PR #205 (latestmainat audit time)overview.jsonorigin/mainAPPROVALS_POLL_MS = 10000,METRICS_POLL_MS = 30000What already works (do not regress)
loadAll()+Promise.allSettledrenderMetrics()renderAttention().db-agent-row,openDrawer()[data-omnibar]→openPalette()auth_requiredcardgateway.auth_requiredin metricspaint()replaces all HTML then binds oncestopPolling()on view destroyRoot cause analysis
Two refresh paths behave differently
flowchart TB subgraph paint_every_30s["paint() every 30s"] P1[loadAll API] --> P2["_container.innerHTML = shell(data)"] P2 --> P3["bindEvents(_container)"] P3 --> P4["All DOM is new → one listener per element ✅"] end subgraph refresh_every_10s["refreshApprovals() every 10s"] R1[GET /api/approvals/pending] --> R2["#ov-metrics innerHTML only"] R2 --> R3["#ov-attention innerHTML only"] R3 --> R4["bindEvents(_container)"] R4 --> R5["Agent rows unchanged → listeners stack ❌"] endbindEvents(container, data)attaches listeners torefreshApprovals?[data-nav]#ov-metrics_container[data-omnibar].db-agent-row.db-agent-chat.atn-approve/.atn-deny#ov-attention#ov-attentionExact code on
main(overview.js)refreshApprovals()— problematic tail:bindEvents()— agent row binding (unchanged DOM between polls):Polling setup:
Additional trigger: each approve/deny click calls
refreshApprovals()immediately after POST.Listener growth model
refreshApprovalsruns.db-agent-rowsetIntervalvisibilitychange→ visibleUser impact
refreshApprovals→ faster listener stacking on agent rowssequenceDiagram participant User participant Timer as 10s interval participant RA as refreshApprovals participant Row as .db-agent-row (same DOM node) participant Drawer Timer->>RA: tick RA->>Row: addEventListener #2 Timer->>RA: tick RA->>Row: addEventListener #3 User->>Row: single click Row->>Drawer: openDrawer × 3Proposed fix
Option A — Scoped
bindEvents(recommended, ~6 line change)paint()unchangedOption B — Event delegation (future refactor)
Single listener on
_containerusingevent.target.closest('.db-agent-row'). Polls never add per-element listeners. Larger refactor (~40 lines); not required for hotfix.Acceptance criteria
[data-nav]navigates oncepaint()runs at 30 sstopPolling()clears timers (existing behavior)pytestunit + integration greenTest plan
Manual
/overviewwith ≥1 agent and ≥1 pending approvalAutomated (recommended)
Related items (separate work)
aria-liveon pending approval countTerminal evidence (2026-07-14)
Commands run from
c:\Users\DELL\Downloads\praisonuiagainstorigin/main.Git HEAD
Parent issue + PR closure
{"closedAt":"2026-07-14T08:42:59Z","number":195,"state":"CLOSED","title":"Enhancement: Agent Command Center — mission-control Overview with live agents, approvals, and usage (STITCH-001)"} {"mergedAt":"2026-07-14T08:42:58Z","number":201,"state":"MERGED","title":"feat: Agent Command Center overview (STITCH-001)"}Line-number grep — polling + bindEvents
Full function extract from
main(lines 343–403)Quantitative check — scoped bind not present
Full shared log: E2E-TERMINAL-EVIDENCE-LOG.md
End-to-end verification
Prerequisites
examples/python/06-dashboard8103(or your local gateway)http://127.0.0.1:8103/overviewE2E procedure (browser)
cd examples/python/06-dashboard && aiui run(or existing process on 8103)/overview.db-agent-rowdata-agent-id#ov-attentionmay update if approvals existE2E expected vs actual (
main@6d194fc)mainrefreshApprovals→ faster stackpaint()at 30sE2E procedure (DevTools listener proof)
Paste in browser console on
/overviewafter 40s dwell:.db-agent-rowmainE2E regression after fix
bindEvents(metricsEl)grep countbindEvents(_container, data)insiderefreshApprovalspytest tests/unit tests/integrationPROPOSAL-STITCH-001-AGENT-COMMAND-CENTER.md