diff --git a/docs/web-ui.md b/docs/web-ui.md index 67b44b71..be864b4b 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -86,20 +86,6 @@ 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 @@ -472,6 +458,8 @@ needed). The joint at the row's own depth column is `├` (more siblings below) or `└` (last sibling at this depth — vertical stops at the row's icon midline). + + ### Selection bar When one or more agents are selected (via icon click), a sticky diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index b0b3ef54..f8f7c892 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -1,14 +1,18 @@ // /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 (subscribed via -// `openStream` from common.js). See docs/web-ui.md::Shape (shared by -// both) for the broader contract. +// handlers triggered by the dashboard event stream. // -// 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. +// #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). import { marked } from 'marked'; import { @@ -37,16 +41,37 @@ 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) - // 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()`). + // 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. 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.