From b374f39b0d5a59a131ad1cf74fd72ed73c6b705e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 21:37:17 +0200 Subject: [PATCH] dashboard: preserve
across refresh via data-restore-key generalises the focus-preservation pattern to expanded details sections (journald viewer was collapsing on every 5s refresh; same issue for approval diff blocks). before re-render we snapshot which
are open; after render we re-apply. setting .open = true programmatically also fires the toggle event, so journald's lazy-fetch listener re-runs cleanly. tagged: journal:, approval-diff:. anything else that should survive a refresh just needs a stable data-restore-key attribute. --- hive-c0re/assets/app.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/hive-c0re/assets/app.js b/hive-c0re/assets/app.js index 2da6459..0a7a52b 100644 --- a/hive-c0re/assets/app.js +++ b/hive-c0re/assets/app.js @@ -300,7 +300,10 @@ // operator expands; refresh re-fetches; unit toggle switches // between the harness service and the full machine journal. function buildJournalDetails(containerName, defaultUnit) { - const details = el('details', { class: 'journal' }); + const details = el('details', { + class: 'journal', + 'data-restore-key': 'journal:' + containerName, + }); const summary = el('summary', {}, '↳ logs · ' + containerName); const body = el('div', { class: 'journal-body' }); const controls = el('div', { class: 'journal-controls' }); @@ -576,7 +579,9 @@ ); li.append(row); if (a.diff_html) { - const details = el('details'); + const details = el('details', { + 'data-restore-key': 'approval-diff:' + a.id, + }); details.append(el('summary', {}, 'diff vs applied')); // diff_html is pre-rendered server-side (per-line class spans inside // a
); inject as innerHTML.
@@ -601,6 +606,34 @@
     'inbox-section',
     'approvals-section',
   ];
+  // 
sections that should survive a refresh need a stable + // `data-restore-key` attribute. snapshotOpenDetails walks managed + // sections and records which keys are currently open; restoreOpenDetails + // re-applies after the render. The `toggle` event fires on + // programmatic open changes too, so any onload-fetch wired up via + // a toggle listener (e.g. journald) re-fires cleanly. + function snapshotOpenDetails() { + const open = new Set(); + for (const id of MANAGED_SECTION_IDS) { + const sect = document.getElementById(id); + if (!sect) continue; + for (const d of sect.querySelectorAll('details[data-restore-key]')) { + if (d.open) open.add(d.dataset.restoreKey); + } + } + return open; + } + function restoreOpenDetails(open) { + if (!open.size) return; + for (const id of MANAGED_SECTION_IDS) { + const sect = document.getElementById(id); + if (!sect) continue; + for (const d of sect.querySelectorAll('details[data-restore-key]')) { + if (open.has(d.dataset.restoreKey)) d.open = true; + } + } + } + function operatorIsTyping() { const el_ = document.activeElement; if (!el_ || el_ === document.body) return false; @@ -624,11 +657,13 @@ const resp = await fetch('/api/state'); if (!resp.ok) throw new Error('http ' + resp.status); const s = await resp.json(); + const openDetails = snapshotOpenDetails(); renderContainers(s); renderTombstones(s); renderQuestions(s); renderInbox(s); renderApprovals(s); + restoreOpenDetails(openDetails); notifyDeltas(s); // Auto-refresh: fast (2s) while a spawn or a per-container // action is in flight, otherwise heartbeat (5s) so newly-queued