diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 1d4d21ea..a48f51b8 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -2442,15 +2442,6 @@ window.marked = marked; } // ─── rebuild queue ────────────────────────────────────────────────────── - // Keyed row cache for the rebuild-queue list. Maps entry.id → { el, fingerprint }. - // Same pattern as containerRowCache: reuse
  • nodes whose state hasn't - // changed rather than replacing the entire list on every snapshot event. - // The elapsed-time ticker (data-rqe-elapsed + 1s setInterval below) already - // updates running-entry timestamps in-place, so started_at doesn't need to - // invalidate — including it in the fingerprint only matters for the initial - // render of a newly-running entry. - const rebuildQueueRowCache = new Map(); - // Glyph + verb per QueueKind. Mirrors the labels used in // hive-c0re::rebuild_queue::QueueKind::as_str. const QUEUE_KIND_GLYPH = { @@ -2470,38 +2461,15 @@ window.marked = marked; cancelled: '⊘', }; - // Fingerprint for a single rebuild-queue row. Everything visible in the - // row except the ticking elapsed seconds (handled by data-rqe-elapsed - // ticker, not by re-rendering). - function rebuildQueueEntryFingerprint(entry, isChild) { - return JSON.stringify({ - state: entry.state, - step: entry.step, - kind: entry.kind, - agent: entry.agent, - source: entry.source, - started_at: entry.started_at, - enqueued_at: entry.enqueued_at, - finished_at: entry.finished_at, - reason: entry.reason, - error: entry.error, - build_log_id: entry.build_log_id, - isChild, - }); - } - function renderRebuildQueue(s) { const root = $('rebuild-queue-section'); if (!root) return; + root.replaceChildren(); const queue = s.rebuild_queue || []; - if (!queue.length) { - // Queue drained — show placeholder and purge cache. - rebuildQueueRowCache.clear(); - root.replaceChildren(el('p', { class: 'empty' }, 'queue is empty — nothing pending or in flight.')); + root.append(el('p', { class: 'empty' }, 'queue is empty — nothing pending or in flight.')); return; } - // Index by id for parent lookup. const byId = new Map(queue.map((e) => [e.id, e])); // Top-level entries first; children render nested under their parent. @@ -2513,58 +2481,21 @@ window.marked = marked; childrenOf.get(e.parent_id).push(e); } } + const ul = el('ul', { class: 'rebuild-queue' }); + for (const top of tops) { + ul.append(renderQueueEntry(top, byId)); + for (const child of childrenOf.get(top.id) || []) { + ul.append(renderQueueEntry(child, byId, true)); + } + } // Children whose parent isn't in the snapshot (history-evicted) still render flat. const orphans = queue.filter( (e) => e.parent_id != null && !byId.has(e.parent_id), ); - - // Build ordered list of
  • , reusing cached nodes for unchanged entries. - const orderedLis = []; - function addEntry(entry, isChild) { - const fp = rebuildQueueEntryFingerprint(entry, isChild); - const cached = rebuildQueueRowCache.get(entry.id); - let li; - if (cached && cached.fingerprint === fp) { - li = cached.el; - } else { - li = renderQueueEntry(entry, byId, isChild); - rebuildQueueRowCache.set(entry.id, { el: li, fingerprint: fp }); - } - orderedLis.push(li); - } - for (const top of tops) { - addEntry(top, false); - for (const child of childrenOf.get(top.id) || []) { - addEntry(child, true); - } - } for (const o of orphans) { - addEntry(o, true); + ul.append(renderQueueEntry(o, byId, true)); } - - // Drop cache entries for IDs no longer in the snapshot. - const liveIds = new Set(queue.map((e) => e.id)); - for (const [id, entry] of rebuildQueueRowCache) { - if (!liveIds.has(id)) { - entry.el.remove(); - rebuildQueueRowCache.delete(id); - } - } - - // Get or create the