From 2a62a561c45c1be3814ab7b2c417f1849369837d Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 1 Jun 2026 22:48:21 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20frontend=20cleanup=20=E2=80=94=20repla?= =?UTF-8?q?ceChildren,=20missing=20source=20badge,=20http=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace innerHTML = '' with replaceChildren() throughout tabs.js and app.js (12 + 7 sites). paintAtomic already used replaceChildren; now the direct-clear sites are consistent with it. - Add missing .rqe-source-startup_sweep CSS rule (startup_sweep is a valid QueueSource variant but had no badge style, falling through to the base muted appearance with no explicit intent). - Lowercase the one uppercase 'HTTP ' in the fetchAndRenderToolGroups error path to match every other fetch error in the file. --- frontend/packages/agent/src/app.js | 14 +++++----- frontend/packages/dashboard/src/dashboard.css | 1 + frontend/packages/dashboard/src/tabs.js | 26 +++++++++---------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 2e11ba6d..caca3feb 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -549,7 +549,7 @@ window.marked = marked; const slot = $('term-input'); if (!slot) return; if (!termInputRendered) { - slot.innerHTML = ''; + slot.replaceChildren(); const form = el('form', { action: 'send', method: 'POST', class: 'sendform-term', 'data-async': '', @@ -790,7 +790,7 @@ window.marked = marked; if (slot._boundId) { const stillPending = pending.some((q) => q.id === slot._boundId); if (!stillPending && !slot._resolvedShown) { - slot.innerHTML = ''; + slot.replaceChildren(); slot.appendChild(el('span', { class: 'ask-resolved-tag' }, '[resolved]')); slot._resolvedShown = true; } @@ -800,7 +800,7 @@ window.marked = marked; if (!q) continue; claimed.add(q.id); slot._boundId = q.id; - slot.innerHTML = ''; + slot.replaceChildren(); slot.appendChild(buildAnswerForm(q.id)); } } @@ -1110,7 +1110,7 @@ window.marked = marked; (s.status === 'needs_login_in_progress' && outLen !== lastOutputLen); if (dirty) { const root = $('status'); - root.innerHTML = ''; + root.replaceChildren(); if (s.status === 'online') renderOnline(s.label, root); else if (s.status === 'needs_login_idle') renderNeedsLoginIdle(root); else if (s.status === 'needs_login_in_progress') renderLoginInProgress(s.session || {}, root); @@ -1139,7 +1139,7 @@ window.marked = marked; (function() { const log = $('live'); if (!log || !window.HiveTerminal) return; - log.innerHTML = ''; + log.replaceChildren(); function trim(s, n) { return s.length > n ? s.slice(0, n) + '…' : s; } // Render a message body as markdown into a new
. @@ -1543,7 +1543,7 @@ window.marked = marked; // login UI; clear it so the operator sees a clean // online state without a separate refetch. const root = $('status'); - if (root) root.innerHTML = ''; + if (root) root.replaceChildren(); lastStatus = 'online'; } }, @@ -1566,7 +1566,7 @@ window.marked = marked; // Expose the panel API for slash commands (`/help`, `/clear`). termAPI = { row: (cls, text) => term.row(cls, text), - clear: () => { log.innerHTML = ''; }, + clear: () => { log.replaceChildren(); }, }; })(); diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index 272b256b..84512f97 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -1096,6 +1096,7 @@ code { .rqe-source-manual { color: var(--cyan); border-color: var(--cyan); } .rqe-source-meta_update { color: var(--purple); border-color: var(--purple); } .rqe-source-auto_update { color: var(--muted); } +.rqe-source-startup_sweep { color: var(--muted); } .rqe-source-crash_recover { color: var(--amber); border-color: var(--amber); } .rqe-source-approval { color: var(--green); border-color: var(--green); } .rqe-when { color: var(--muted); font-size: 0.85em; } diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index e571a358..14312497 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -551,7 +551,7 @@ window.marked = marked; // no-op-when-target-absent convention the other renderers // (renderTombstones, etc.) follow. if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); // Containers come from the derived map (event-driven) rather than // `s.containers`; `s` still supplies hostname (for the web-ui @@ -883,7 +883,7 @@ window.marked = marked; // Recompute action availability + tooltips per render. Each // action declares which agents it CAN'T run on; the bar disables // the button and surfaces the offending names in the tooltip. - actions.innerHTML = ''; + actions.replaceChildren(); const allRunning = selected.every((c) => c.running); const allStopped = selected.every((c) => !c.running); const noManagers = selected.every((c) => !c.is_manager); @@ -1114,7 +1114,7 @@ window.marked = marked; // no-op on /flow.html and any other page that loads the shared // bundle without the dashboard's tab panes. if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); if (!s.tombstones || !s.tombstones.length) { root.append(el('p', { class: 'empty' }, 'no kept state — clean')); return; @@ -1181,21 +1181,21 @@ window.marked = marked; async function fetchAndRenderToolGroups() { const root = $('tool-groups-section'); if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); root.append(el('p', { class: 'meta' }, 'loading…')); try { const resp = await fetch('/api/tool-groups'); - if (!resp.ok) throw new Error('HTTP ' + resp.status); + if (!resp.ok) throw new Error('http ' + resp.status); const data = await resp.json(); renderToolGroups(root, data); } catch (err) { - root.innerHTML = ''; + root.replaceChildren(); root.append(el('p', { class: 'meta' }, 'fetch failed: ' + err)); } } function renderToolGroups(root, data) { - root.innerHTML = ''; + root.replaceChildren(); const { groups, assignments } = data; if (!groups || !groups.length) { root.append(el('p', { class: 'meta' }, '(no tool groups defined)')); @@ -1380,7 +1380,7 @@ window.marked = marked; // no-op when the section is missing. `question_added` / // `question_resolved` SSE events route through here. if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19); const allPending = questionsState.pending; const activeFilter = getQuestionsFilter(); @@ -1712,7 +1712,7 @@ window.marked = marked; function renderPeerHives(peers) { const root = $('peers-section'); if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); if (!peers || !peers.length) { root.append(el('p', { class: 'empty' }, 'no peer hives configured')); return; @@ -1743,7 +1743,7 @@ window.marked = marked; // no-op elsewhere — `approval_added` / `approval_resolved` SSE // events route through here on every page that loads the bundle. if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); // Spawn request form: submitting it queues a Spawn approval that // lands in this same list, so the form belongs here rather than on @@ -1933,7 +1933,7 @@ window.marked = marked; function renderMetaInputs(s) { const root = $('meta-inputs-section'); if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); const inputs = s.meta_inputs || []; if (!inputs.length) { root.append(el('p', { class: 'empty' }, 'meta repo not seeded yet')); @@ -2054,7 +2054,7 @@ window.marked = marked; function renderRebuildQueue(s) { const root = $('rebuild-queue-section'); if (!root) return; - root.innerHTML = ''; + root.replaceChildren(); const queue = s.rebuild_queue || []; if (!queue.length) { root.append(el('p', { class: 'empty' }, 'queue is empty — nothing pending or in flight.')); @@ -3578,7 +3578,7 @@ window.marked = marked; // Step 4: rebuild the dropdown from the two overflow sets. const overflowedTabs = [...dynamicOverflow, ...defaultOverflow]; - overflowDrop.innerHTML = ''; + overflowDrop.replaceChildren(); for (const tab of overflowedTabs) { const li = document.createElement('li'); li.setAttribute('role', 'presentation');