DEBUG: instrument view rebuilds and depth re-apply on refocus
Temporary. Row grouping re-expands whenever the browser regains focus, for both the toolbar EXPAND buttons and the per-row +/-. set_depth() and per-node expansion both live on the view rather than in ViewConfig, so a rebuilt view loses them -- but nothing so far distinguishes "the view was rebuilt" from "the re-apply lost its race with the redraw". Tags each view object with an id via a WeakMap and logs it at every point that could rebuild one: focus/visibilitychange/pageshow, config-update, applyDepth, the theme effect, and initViewer. A changed id across a refocus means the view was discarded; an unchanged id means the depth re-apply itself is at fault. Each bail-out in the refocus handler now says which branch it took, which also shows whether expandDepthRef was ever set. Prefix [pf-depth]. Revert this commit once the cause is known. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1807014323
commit
db67f6eede
@ -202,19 +202,69 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
// Intersection/ResizeObserver triggers when you switch back to the tab — and the
|
// 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
|
// fresh view has no depth set, so the whole tree appears expanded. Re-apply the
|
||||||
// depth we last set once the redraw has settled.
|
// depth we last set once the redraw has settled.
|
||||||
|
// ---------------------------------------------------------------- DEBUG
|
||||||
|
// Temporary instrumentation for the "row groups re-expand on refocus" bug.
|
||||||
|
// set_depth() and per-node expand/collapse live on the *view*, not in
|
||||||
|
// ViewConfig, so a rebuilt view loses both. These lines exist to show whether
|
||||||
|
// that is what happens. Remove once the cause is known.
|
||||||
|
const viewSeqRef = useRef(0)
|
||||||
|
const viewIdsRef = useRef(new WeakMap())
|
||||||
|
const lastViewIdRef = useRef(null)
|
||||||
|
|
||||||
|
function dbg(msg, extra) {
|
||||||
|
const t = new Date().toISOString().slice(11, 23)
|
||||||
|
if (extra !== undefined) console.log(`[pf-depth ${t}] ${msg}`, extra)
|
||||||
|
else console.log(`[pf-depth ${t}] ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewId(v) {
|
||||||
|
if (!v) return 'none'
|
||||||
|
if (!viewIdsRef.current.has(v)) viewIdsRef.current.set(v, ++viewSeqRef.current)
|
||||||
|
return '#' + viewIdsRef.current.get(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the viewer's current view and report whether it is the same object we
|
||||||
|
// saw last time. A changed id means the view was discarded and rebuilt.
|
||||||
|
async function probeView(where) {
|
||||||
|
const viewer = viewerRef.current
|
||||||
|
if (!viewer) return dbg(`probe(${where}): no viewer`)
|
||||||
|
try {
|
||||||
|
const v = await viewer.getView()
|
||||||
|
const id = viewId(v)
|
||||||
|
const changed = lastViewIdRef.current !== null && lastViewIdRef.current !== id
|
||||||
|
dbg(`probe(${where}): view ${id}${changed ? ` <-- REBUILT (was ${lastViewIdRef.current})` : ''}`)
|
||||||
|
lastViewIdRef.current = id
|
||||||
|
} catch (err) { dbg(`probe(${where}) threw`, err) }
|
||||||
|
}
|
||||||
|
// -------------------------------------------------------------- END DEBUG
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let queued = false
|
let queued = false
|
||||||
const reapply = () => {
|
const reapply = async (ev) => {
|
||||||
if (document.visibilityState !== 'visible') return
|
dbg(`event ${ev?.type || '?'}`, {
|
||||||
if (expandDepthRef.current == null || !viewerRef.current) return
|
visibility: document.visibilityState,
|
||||||
if (queued) return
|
expandDepthRef: expandDepthRef.current,
|
||||||
|
hasViewer: !!viewerRef.current,
|
||||||
|
})
|
||||||
|
// Probe the view identity before touching anything: if the id has changed
|
||||||
|
// since the last log line, the viewer rebuilt its view and that is where
|
||||||
|
// the depth went, rather than the re-apply below losing a race.
|
||||||
|
await probeView('on ' + (ev?.type || '?'))
|
||||||
|
|
||||||
|
if (document.visibilityState !== 'visible') return dbg('bail: not visible')
|
||||||
|
if (!viewerRef.current) return dbg('bail: no viewer')
|
||||||
|
if (expandDepthRef.current == null) return dbg('bail: no depth recorded (toolbar EXPAND never used, or per-node +/- only)')
|
||||||
|
if (queued) return dbg('bail: already queued')
|
||||||
queued = true
|
queued = true
|
||||||
// let the viewer finish its own redraw first, or it will draw over us
|
// let the viewer finish its own redraw first, or it will draw over us
|
||||||
requestAnimationFrame(() => setTimeout(async () => {
|
requestAnimationFrame(() => setTimeout(async () => {
|
||||||
queued = false
|
queued = false
|
||||||
const d = expandDepthRef.current
|
const d = expandDepthRef.current
|
||||||
if (d == null) return
|
if (d == null) return dbg('re-apply aborted: depth became null')
|
||||||
try { await applyDepth(d) } catch {}
|
try {
|
||||||
|
await applyDepth(d)
|
||||||
|
dbg(`re-apply done depth=${d}`)
|
||||||
|
} catch (err) { dbg('re-apply THREW', err) }
|
||||||
}, 60))
|
}, 60))
|
||||||
}
|
}
|
||||||
document.addEventListener('visibilitychange', reapply)
|
document.addEventListener('visibilitychange', reapply)
|
||||||
@ -370,6 +420,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewerRef.current) {
|
if (viewerRef.current) {
|
||||||
|
dbg(`theme effect -> ${dark ? 'Pro Dark' : 'Pro Light'} (a theme change rebuilds the view)`)
|
||||||
viewerRef.current.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light')
|
viewerRef.current.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light')
|
||||||
}
|
}
|
||||||
}, [dark, versionId])
|
}, [dark, versionId])
|
||||||
@ -575,6 +626,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
async function initViewer(vid, sid) {
|
async function initViewer(vid, sid) {
|
||||||
const viewer = viewerRef.current
|
const viewer = viewerRef.current
|
||||||
if (!viewer) return
|
if (!viewer) return
|
||||||
|
dbg(`initViewer(version=${vid}, source=${sid}) -- FULL RELOAD, depth reset to null`)
|
||||||
const myId = ++initIdRef.current
|
const myId = ++initIdRef.current
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setLargeDataset(false)
|
setLargeDataset(false)
|
||||||
@ -677,6 +729,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
// auto-persist viewer state (formatting, columns, etc.) to the last-used cache
|
// auto-persist viewer state (formatting, columns, etc.) to the last-used cache
|
||||||
if (viewer._pspUpdate) viewer.removeEventListener('perspective-config-update', viewer._pspUpdate)
|
if (viewer._pspUpdate) viewer.removeEventListener('perspective-config-update', viewer._pspUpdate)
|
||||||
viewer._pspUpdate = async () => {
|
viewer._pspUpdate = async () => {
|
||||||
|
dbg(`perspective-config-update (collapsing=${collapsingRef.current})`)
|
||||||
|
await probeView('config-update')
|
||||||
try {
|
try {
|
||||||
// A split_by change that is not ours is the user rearranging the pivot, and
|
// A split_by change that is not ours is the user rearranging the pivot, and
|
||||||
// it redefines the hierarchy. Ours is a collapse, and must not overwrite it.
|
// it redefines the hierarchy. Ours is a collapse, and must not overwrite it.
|
||||||
@ -787,6 +841,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
const viewer = viewerRef.current
|
const viewer = viewerRef.current
|
||||||
if (!viewer) return
|
if (!viewer) return
|
||||||
const view = await viewer.getView()
|
const view = await viewer.getView()
|
||||||
|
dbg(`applyDepth(${d}) on view ${viewId(view)}`)
|
||||||
|
lastViewIdRef.current = viewId(view)
|
||||||
await view.set_depth(d)
|
await view.set_depth(d)
|
||||||
const plugin = await viewer.getPlugin()
|
const plugin = await viewer.getPlugin()
|
||||||
await plugin.draw(view)
|
await plugin.draw(view)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user