From d275b50177a4ed2387980b45a407266edbcc1a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 21:19:01 +0200 Subject: [PATCH] dashboard: don't yank the form away while operator is typing every refreshState tick does root.innerHTML = '' across the managed sections, which destroys any focused input. detect the case before re-rendering: if document.activeElement is an INPUT / TEXTAREA / SELECT inside one of the managed sections, skip this tick and try again in 2s. eventually the operator blurs and the refresh lands. managed section ids: containers / tombstones / questions / inbox / approvals. msgflow + message-flow SSE rows don't have inputs so they're not affected. --- hive-c0re/assets/app.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hive-c0re/assets/app.js b/hive-c0re/assets/app.js index 6bce65d..2409710 100644 --- a/hive-c0re/assets/app.js +++ b/hive-c0re/assets/app.js @@ -574,7 +574,35 @@ // ─── state polling ────────────────────────────────────────────────────── let pollTimer = null; + // Sections whose innerHTML gets blown away on each refresh. If the + // operator is typing in one of them, skip the refresh — the next + // tick (or a manual action) will pick it up after they blur. + const MANAGED_SECTION_IDS = [ + 'containers-section', + 'tombstones-section', + 'questions-section', + 'inbox-section', + 'approvals-section', + ]; + function operatorIsTyping() { + const el_ = document.activeElement; + if (!el_ || el_ === document.body) return false; + const tag = el_.tagName; + if (tag !== 'INPUT' && tag !== 'TEXTAREA' && tag !== 'SELECT') return false; + return MANAGED_SECTION_IDS.some((id) => { + const sect = document.getElementById(id); + return sect && sect.contains(el_); + }); + } async function refreshState() { + // Don't yank the form out from under the operator. Try again + // shortly on the next tick; eventually they'll blur and the + // refresh lands. + if (operatorIsTyping()) { + if (pollTimer) clearTimeout(pollTimer); + pollTimer = setTimeout(refreshState, 2000); + return; + } try { const resp = await fetch('/api/state'); if (!resp.ok) throw new Error('http ' + resp.status);