diff --git a/CLAUDE.md b/CLAUDE.md index 4761478..535f36c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -272,6 +272,21 @@ gone: `split_full` is still *read* on load, for layouts saved under the old scheme. +### Auto-pause is off + +`` auto-pauses by default: an `IntersectionObserver` on +itself plus the document's `visibilitychange` drive `AutoPauseState::apply()`, +and pausing **deletes the view** (`session.set_pause(true)` → +`view_sub.take().delete()`). Returning to the tab is therefore not a redraw but +`restore_and_render()` — a fresh view and a fresh traversal of the whole grain, +which on a large one is a multi-second chug on every tab switch, and takes any +per-node expansion with it. + +`initViewer()` calls `viewer.setAutoPause(false)` right after `viewer.load()`. +Nothing updates the table while the tab is hidden — every operation is driven +from this page — so the pause bought nothing and cost a rebuild. The view is now +held while the tab is backgrounded. + ### Still not solved: per-node expansion Expanding one specific branch is view state with no config representation, and diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index 24847a7..c7434bb 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -259,12 +259,6 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // the current selection, readable from event handlers that were registered once const slicesRef = useRef([]) - // Perspective's ROLLUP view contains every level of the hierarchy; view.set_depth() - // is the only thing hiding the deeper ones, and it lives on the view rather than in - // the saved config. The viewer rebuilds its view when it redraws — which its - // Intersection/ResizeObserver triggers when you switch back to the tab — and the - // fresh view has no depth set, so the whole tree appears expanded. Re-apply the - // depth we last set once the redraw has settled. // perspective-click fires as a CustomEvent with no modifier state of its own, // and carries no cell coordinates either, so record both from the mousedown that // precedes it. The grid lives in a shadow root, so the real target is the head of @@ -822,6 +816,22 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // that occurs when viewer.load(worker) + restore({ table: name }) can't resolve // the named table in time. await viewer.load(tableRef.current) + + // Auto-pause is on by default, and pausing *deletes the view*: + // AutoPauseState::apply() fires on visibilitychange and on the viewer's own + // IntersectionObserver, and set_pause(true) does + // `view_sub.take().delete()`. Coming back is therefore not a redraw but + // `restore_and_render()` -- a fresh view, a fresh traversal of the whole + // grain -- which is the multi-second chug on every tab switch, and why any + // per-node expansion is gone when you return. + // + // Nothing updates the table while the tab is hidden (operations are driven + // from this page), so the pause buys nothing here and costs a rebuild. + // Guarded: it is a method on the custom element, absent on older builds. + try { if (viewer.setAutoPause) await viewer.setAutoPause(false) } catch (err) { + console.error('[setAutoPause]', err) + } + viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') if (!hideSplitTotal()) setTimeout(hideSplitTotal, 400)