From 711d42031c923f2fc6366d22e91b846ffe1ef3a0 Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 11:51:17 +0200 Subject: [PATCH] docs: scrub module-split cookies from tabs.js + migrate paintAtomic rationale (#712 batch 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two threads cleaned up in tabs.js: 1. Top-of-file '#406 step 1/2/3' historical narrative deleted — it documented past module splits (moves to common.js / flow.js, file rename app.js → tabs.js) which are git-history concerns, not current-behaviour docs. Replaced with a two-sentence contract description that references docs/web-ui.md and the present-tense module split. 2. Five 'moved to ./common.js (#406)' stubs deleted — same reasoning. The imports at the top of the file already document what's in common.js; standalone 'this function lives elsewhere' pointers are noise once you stop tracking the move event. paintAtomic's substantive rationale migrated to a new 'Atomic section repaint' subsection in docs/web-ui.md (under the existing focus-preservation note); in-code comment shrinks to a two-line reference. Same pattern as the topology-tree batch. Net: 36 fewer lines in tabs.js, 14 new in docs/web-ui.md. Functional code unchanged; build clean. refs #712 --- docs/web-ui.md | 14 ++++++++ frontend/packages/dashboard/src/tabs.js | 47 ++++++------------------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/docs/web-ui.md b/docs/web-ui.md index be864b4b..ee2ce321 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -86,6 +86,20 @@ and, if so, skips the refresh (defers 2s). The operator never has the form yanked out from under them mid-type; the update lands as soon as they blur. +**Atomic section repaint:** every managed-section renderer goes +through `paintAtomic(liveRoot, build)`: the builder appends into +a fresh `DocumentFragment` (off-DOM) and the commit is one +`replaceChildren` call. The naive `root.innerHTML = ''; root.append(...)` +shape was visibly flashing empty on every poll cycle — on async +paths the await yield gave the browser a paint opportunity between +the clear and the re-append, and on complex builds (many `el()` +allocations) layout could escape the per-task budget even on the +synchronous path. The fragment approach keeps the intermediate +empty state invisible. Builders receive the fragment as their +`root`, so existing renderer code carries over unchanged; early +returns inside the builder still commit whatever was appended +before they returned. + **`
` open-state preservation:** any collapsible element tagged with `data-restore-key=""` survives the refresh. `snapshotOpenDetails()` walks managed sections before diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index f8f7c892..b0b3ef54 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -1,18 +1,14 @@ // /index.html entry point: tab renderers + tab routing + refreshState // + notification deltas. Reads /api/state on cold load and after every // async-form submit; live updates run through `applyXxx` mutation -// handlers triggered by the dashboard event stream. +// handlers triggered by the dashboard event stream (subscribed via +// `openStream` from common.js). See docs/web-ui.md::Shape (shared by +// both) for the broader contract. // -// #406 step 1: pure helpers + side panel + OS notifications + path -// linkification moved to `./common.js`. -// #406 step 2: the flow-only IIFEs (operator inbox, inbox-pill, broker -// terminal, @-mention composer) moved to `./flow.js`. /flow.html loads -// `flow.js` as its own bundle entry; this file is loaded only by -// /index.html. -// #406 step 3: file renamed from `app.js` → `tabs.js` since it owns -// the dashboard *tabs* surface only (the FL0W page has its own bundle). -// Live SSE subscription is wired through `openStream` from common.js -// (#406 step 3 hookup; see also #408 for stream-side filtering). +// Pure helpers (DOM, side panel, OS notifications, path linkification) +// live in `./common.js`; the flow-page surface (operator inbox, broker +// terminal, @-mention composer) lives in `./flow.js` — `flow.html` and +// `index.html` each load their own bundle. import { marked } from 'marked'; import { @@ -41,37 +37,16 @@ window.marked = marked; const CTX_WARN_TOKENS = 150_000; // fallback red threshold (≈ 75% of 200k) const CTX_CAUTION_TOKENS = 100_000; // fallback yellow threshold (≈ 50% of 200k) - // Helpers ($, el, esc, form, fmtAgeSecs) moved to ./common.js (#406). - - // #464 — atomic-swap render helper. Each managed section's render - // function used to do `root.innerHTML = ''; root.append(...);` in - // sequence; even though both operations sit in the same JS turn, - // operators could still see a "blink" on every poll cycle because - // (a) on async paths the await yield gave the browser a paint - // opportunity, and (b) complex builds with many `el()` allocations - // can blow the browser's per-task budget enough for layout to flash - // empty before the new children land. - // - // The fix: build the new content off-DOM into a `DocumentFragment`, - // then move it into the live root in a single `replaceChildren` - // call. The browser never sees an intermediate empty state. Builder - // callbacks receive the fragment as their `root` argument, so each - // renderer's existing `root.append(...)` code carries over with - // zero internal changes. Early-return inside the builder is fine — - // the commit still happens with whatever the builder appended. + // Atomic-swap render helper: build into a DocumentFragment off-DOM, + // commit with one `replaceChildren`. See docs/web-ui.md::Atomic + // section repaint for why (no intermediate empty-state flash on + // poll cycles even when the builder allocates a lot of `el()`). function paintAtomic(liveRoot, build) { const buf = document.createDocumentFragment(); build(buf); liveRoot.replaceChildren(buf); } - // Side panel singleton (Panel) moved to ./common.js (#406). - - // Path linkification + file-preview side panel (openFilePanel, - // makePathLink, appendText, appendLinkified) moved to ./common.js (#406). - - // OS notification module (NOTIF) moved to ./common.js (#406). - // Track which items we've already notified about so a re-render // doesn't re-fire for the same row. Keyed by stable ids; reset only // when the page reloads.