From 996899fcad0344eef28e0d22648b13581e2e98ee Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 1 Aug 2026 00:32:16 +0200 Subject: [PATCH] frontend: create the side-panel instance eagerly, not lazily behind a per-call guard Per mara's review on the side-panel PR: create the shared instance once at module-evaluation time instead of lazily on first call via an ensurePanel() guard every wrapper method had to remember to call. ES modules execute after the document is parsed (same timing as a defer script), so document.body is already available when this code runs -- lazy init bought nothing here and left a footgun for any future method added to either wrapper. --- frontend/packages/agent/src/app.js | 38 ++++++++++------------- frontend/packages/dashboard/src/common.js | 28 +++++++---------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 3ae0125d..2af2ab26 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -39,28 +39,24 @@ window.marked = marked; // ─── side panel (singleton drawer for inbox + loose-ends flyouts) ────── // Thin wrapper around the shared `` element (see - // @hive/shared/side-panel.js) — created and appended to lazily - // on first use, same pattern `themedToast` uses for its toast-stack - // container. This UI's `open(name, title, content)` always takes an - // owner name, so it maps straight onto the shared element's - // `openNamed`; `side-panel-body` is a plain compatibility class this - // wrapper puts on its own instance so `agent.css`'s existing - // `.side-panel-body .agent-inbox …` rules keep reaching the slotted - // content by ordinary light-DOM descendant matching. - let panelEl = null; - function ensurePanel() { - if (!panelEl) { - panelEl = document.createElement('hive-side-panel'); - panelEl.classList.add('side-panel-body'); - document.body.append(panelEl); - } - return panelEl; - } + // @hive/shared/side-panel.js) — created once, eagerly, when this + // module's IIFE runs (ES modules execute after the document is + // parsed, so `document.body` already exists; no per-call lazy-init + // guard to remember for any method, present or future). This UI's + // `open(name, title, content)` always takes an owner name, so it maps + // straight onto the shared element's `openNamed`; `side-panel-body` + // is a plain compatibility class this wrapper puts on its own + // instance so `agent.css`'s existing `.side-panel-body .agent-inbox + // …` rules keep reaching the slotted content by ordinary light-DOM + // descendant matching. + const panelEl = document.createElement('hive-side-panel'); + panelEl.classList.add('side-panel-body'); + document.body.append(panelEl); const Panel = { - open: (name, title, content) => ensurePanel().openNamed(name, title, content), - close: () => ensurePanel().close(), - refresh: (name, title, content) => ensurePanel().refresh(name, title, content), - currentOwner: () => ensurePanel().currentOwner(), + open: (name, title, content) => panelEl.openNamed(name, title, content), + close: () => panelEl.close(), + refresh: (name, title, content) => panelEl.refresh(name, title, content), + currentOwner: () => panelEl.currentOwner(), }; // Wire the header pills to open the side panel. Pre-built (vs diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index 56b5e22d..5acc6518 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -287,27 +287,23 @@ export function openBuildLogStream(id, pre, { onDone, onError } = {}) { // // Thin wrapper around the shared `` element (see // @hive/shared/side-panel.js for the chrome/behavior it owns, including -// drag-to-resize) — the instance is created and appended to -// lazily on first use, same pattern `themedToast` uses for its -// toast-stack container. `side-panel-body` is a plain compatibility +// drag-to-resize). The instance is created once, eagerly, at module +// evaluation time — ES modules run after the document is parsed (same +// timing `defer` scripts get), so `document.body` already exists here; +// no per-call lazy-init guard needed, and no easy-to-forget step for +// any new method added later. `side-panel-body` is a plain compatibility // class this wrapper puts on its own instance so `common.css`'s // existing `.side-panel-body .md …` rules keep reaching the slotted // content by ordinary light-DOM descendant matching — the shared // element itself has no idea that class name means anything. -let panelEl = null; -function ensurePanel() { - if (!panelEl) { - panelEl = document.createElement('hive-side-panel'); - panelEl.classList.add('side-panel-body'); - document.body.append(panelEl); - } - return panelEl; -} +const panelEl = document.createElement('hive-side-panel'); +panelEl.classList.add('side-panel-body'); +document.body.append(panelEl); export const Panel = { - open: (title, content) => ensurePanel().open(title, content), - openNamed: (name, title, content) => ensurePanel().openNamed(name, title, content), - refresh: (name, title, content) => ensurePanel().refresh(name, title, content), - close: () => ensurePanel().close(), + open: (title, content) => panelEl.open(title, content), + openNamed: (name, title, content) => panelEl.openNamed(name, title, content), + refresh: (name, title, content) => panelEl.refresh(name, title, content), + close: () => panelEl.close(), }; // ─── path linkification ─────────────────────────────────────────────────