From 8134b955e2c2bb360b9f59e3bf74973278d69d18 Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 13:05:04 +0200 Subject: [PATCH] tabs.js: scrub #262/#486/#163 cookies from agent links + topology move + SSE resync (#712 batch 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small clusters of cookies removed: - #262 (×2): agent-declared dashboard links via /api/agent//links. Both comment blocks describe the same architectural decision (agent backend = source of truth, DOM-built so link strings can't reach the HTML parser). The cookies just attributed the decision to a PR; the prose stands without them. - #486 (×2): M0V3 topology affordances + addBulkButton perAgentBodyFor hook. Substantive prose moved to docs/web-ui.md::Selection bar in #695 already; in-code comments now reference the docs. - #163 (×3): snapshot re-sync + SSE catchup idempotence. The cookies flagged 'this is why the guard exists'; replaced with the present- tense 'post-disconnect SSE catchup can carry duplicate rows' which reads as the actual reason without needing the issue context. tabs.js: 31 → 24 #NNN refs (-7). Net +1 line (the rewrites are sometimes slightly longer when 'issue #N' is replaced with the substantive description; net is still ahead). 67% reduction since milestone start. refs #712 --- frontend/packages/dashboard/src/tabs.js | 39 +++++++++++++------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index ab7e3f60..105181fd 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -579,8 +579,8 @@ window.marked = marked; // a same-origin proxy that forwards the agent backend's own link list // (stats / screen-if-gui / forge profile / agent-configs / extras). // The agent backend is the single source of truth; no hardcoded link - // list here (issue #262). DOM-built — link strings come from the - // agent's process and must never reach the HTML parser. + // list here. DOM-built — link strings come from the agent's process + // and must never reach the HTML parser. const navStrip = el('span', { class: 'nav-strip' }); head.append(navStrip); const forgeBase = `http://${hostname}:3000`; @@ -709,9 +709,8 @@ window.marked = marked; // The hardcoded config-repo trigger and the agent-declared // extras block both moved into the unified nav strip in the // head row above (sourced from the agent backend via - // `/api/agent/{name}/links` — issue #262). Only the journald - // trigger stays here since it opens the side panel rather - // than a link. + // `/api/agent/{name}/links`). Only the journald trigger stays + // here since it opens the side panel rather than a link. body.append(drill); li.append(icon, body); @@ -806,7 +805,8 @@ window.marked = marked; disabledTitle: why('PURG3', managerNames.map((n) => `\`${n}\` is the manager`)), }); - // #486 — move agent(s) in the topology tree. Two affordances: + // Move agent(s) in the topology tree — see + // docs/web-ui.md::Selection bar for the two affordances: // // ⇡ M0V3 → ROOT promote selected agent(s) to top-level (parent=null) // ⇢ M0V3 → [sel] reparent the single selected agent under a picked @@ -973,9 +973,8 @@ window.marked = marked; // `opts.body` is a static object applied to every POST. // - `opts.perAgentBodyFor(name)` is set: `opts.action` is the // full URL (no name appended) and the per-agent body comes - // from the callback. Used by /api/topology/set-parent (#486), - // where the agent name is a body field rather than a URL - // component. + // from the callback. Used by /api/topology/set-parent, where + // the agent name is a body field rather than a URL component. for (const name of names) { const body = opts.perAgentBodyFor ? new URLSearchParams(opts.perAgentBodyFor(name)) @@ -1161,9 +1160,10 @@ window.marked = marked; const idx = questionsState.pending.findIndex((q) => q.id === ev.id); const existing = idx >= 0 ? questionsState.pending[idx] : null; if (idx >= 0) questionsState.pending.splice(idx, 1); - // Idempotent: a snapshot re-sync (issue #163) can carry this same - // answered row in `question_history` while a live event also - // delivers it — guard the unshift so history can't double a row. + // Idempotent: a snapshot re-sync (post-disconnect SSE catchup) can + // carry this same answered row in `question_history` while a live + // event also delivers it — guard the unshift so history can't + // double a row. if (!questionsState.history.some((h) => h.id === ev.id)) { questionsState.history.unshift({ id: ev.id, @@ -1461,9 +1461,10 @@ window.marked = marked; function applyApprovalResolved(ev) { // Drop from pending; prepend to history (newest-first), cap at 30. approvalsState.pending = approvalsState.pending.filter((a) => a.id !== ev.id); - // Idempotent: a snapshot re-sync (issue #163) can carry this same - // resolved row in `approval_history` while a live event also - // delivers it — guard the unshift so history can't double a row. + // Idempotent: a snapshot re-sync (post-disconnect SSE catchup) can + // carry this same resolved row in `approval_history` while a live + // event also delivers it — guard the unshift so history can't + // double a row. if (!approvalsState.history.some((h) => h.id === ev.id)) { approvalsState.history.unshift({ id: ev.id, @@ -3216,10 +3217,10 @@ window.marked = marked; catch (err) { console.error('dashboard SSE handler', ev.kind, err); } }; es.onopen = () => { - // Re-sync to recover events that fired during a disconnect - // window (issue #163). Initial connect also fires onopen — the - // first refreshState() above and this one race, but refreshState - // is idempotent so the second call just overwrites with the + // Re-sync to recover events that fired during the SSE disconnect + // window. Initial connect also fires onopen — the first + // refreshState() above and this one race, but refreshState is + // idempotent so the second call just overwrites with the // freshest snapshot. Cheap on a quiescent server, fine to repeat. refreshState(); };