Retry the depth re-apply, and let the shim see into the shadow root
Two things the trace showed, one of them a wrong call on my part. getTable() was the wrong thing to wait on. The re-apply threw "No table set" two milliseconds after the event, meaning getTable() had already resolved while getView() still had not -- it is the view that is missing around a rebuild, not the table. There is no predicate for "the view is ready", so attempt applyDepth and retry until it stops throwing, up to 5s. And the shim matched nothing: not one callback reported a viewer. closest() stops at a shadow boundary, so it cannot climb from an element inside Perspective's shadow root out to the host, which is where the observed elements live. Walk host to host via getRootNode().host instead. The shim now also logs every callback it sees under [pf-obs], matched or not, with a description of each target. If it still reports nothing at all, the shim is not installed and the problem is import order rather than matching. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0459fa137d
commit
35f9b7b048
@ -21,11 +21,35 @@
|
|||||||
|
|
||||||
export const PF_OBSERVER_EVENT = 'pf-viewer-observed'
|
export const PF_OBSERVER_EVENT = 'pf-viewer-observed'
|
||||||
|
|
||||||
|
function dbg(msg, extra) {
|
||||||
|
let on = false
|
||||||
|
try { on = !!localStorage.getItem('pf_debug') } catch { /* private mode */ }
|
||||||
|
if (!on) return
|
||||||
|
const t = new Date().toISOString().slice(11, 23)
|
||||||
|
if (extra !== undefined) console.log(`[pf-obs ${t}] ${msg}`, extra)
|
||||||
|
else console.log(`[pf-obs ${t}] ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perspective observes elements inside its own shadow root, and closest() stops
|
||||||
|
// at a shadow boundary -- it will not climb from a shadow child out to the host.
|
||||||
|
// So walk the tree explicitly, hopping host to host, or the match never fires.
|
||||||
function touchesViewer(target) {
|
function touchesViewer(target) {
|
||||||
if (!(target instanceof Element)) return false
|
if (!(target instanceof Element)) return false
|
||||||
return target.tagName === 'PERSPECTIVE-VIEWER'
|
let node = target
|
||||||
|| !!target.closest?.('perspective-viewer')
|
for (let hops = 0; node && hops < 20; hops++) {
|
||||||
|| !!target.querySelector?.('perspective-viewer')
|
if (node.tagName === 'PERSPECTIVE-VIEWER') return true
|
||||||
|
if (node.closest?.('perspective-viewer')) return true
|
||||||
|
const root = node.getRootNode?.()
|
||||||
|
node = root && root.host ? root.host : node.parentElement
|
||||||
|
}
|
||||||
|
return !!target.querySelector?.('perspective-viewer')
|
||||||
|
}
|
||||||
|
|
||||||
|
function describe(target) {
|
||||||
|
if (!(target instanceof Element)) return String(target)
|
||||||
|
const root = target.getRootNode?.()
|
||||||
|
return `${target.tagName.toLowerCase()}${target.className ? '.' + String(target.className).split(' ')[0] : ''}`
|
||||||
|
+ (root && root.host ? ` (in shadow of ${root.host.tagName.toLowerCase()})` : '')
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap(Native, kind) {
|
function wrap(Native, kind) {
|
||||||
@ -41,6 +65,9 @@ function wrap(Native, kind) {
|
|||||||
callback(entries, observer)
|
callback(entries, observer)
|
||||||
} finally {
|
} finally {
|
||||||
const hit = entries.some(e => touchesViewer(e.target))
|
const hit = entries.some(e => touchesViewer(e.target))
|
||||||
|
dbg(`${kind} fired on ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}`
|
||||||
|
+ ` -> ${hit ? 'MATCHED viewer' : 'no viewer match'}`,
|
||||||
|
entries.map(e => describe(e.target)))
|
||||||
if (hit) {
|
if (hit) {
|
||||||
window.dispatchEvent(new CustomEvent(PF_OBSERVER_EVENT, {
|
window.dispatchEvent(new CustomEvent(PF_OBSERVER_EVENT, {
|
||||||
detail: {
|
detail: {
|
||||||
|
|||||||
@ -246,24 +246,28 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
lastViewIdRef.current = id
|
lastViewIdRef.current = id
|
||||||
} catch (err) { dbg(`probe(${where}) threw`, err) }
|
} catch (err) { dbg(`probe(${where}) threw`, err) }
|
||||||
}
|
}
|
||||||
// The viewer is briefly detached from its table around a refocus, and anything
|
// Around a rebuild the viewer throws "No table set" from getView() -- note it
|
||||||
// touching the view in that window throws "No table set". Poll until it is back
|
// is the *view* that is missing, not the table: getTable() resolves happily
|
||||||
// rather than guessing at a delay.
|
// through the same window, which is why gating on it did not help. There is no
|
||||||
async function waitForTable(deadlineMs = 5000, stepMs = 60) {
|
// "is the view ready" predicate to poll, so just attempt the thing we want and
|
||||||
|
// retry until it stops throwing.
|
||||||
|
async function applyDepthWhenReady(d, deadlineMs = 5000, stepMs = 80) {
|
||||||
const started = Date.now()
|
const started = Date.now()
|
||||||
let attempts = 0
|
let attempts = 0
|
||||||
while (Date.now() - started < deadlineMs) {
|
for (;;) {
|
||||||
attempts++
|
attempts++
|
||||||
try {
|
try {
|
||||||
const viewer = viewerRef.current
|
await applyDepth(d)
|
||||||
if (viewer && await viewer.getTable()) {
|
if (attempts > 1) dbg(`depth ${d} applied on attempt ${attempts} after ${Date.now() - started}ms`)
|
||||||
if (attempts > 1) dbg(`table re-attached after ${attempts} polls / ${Date.now() - started}ms`)
|
return true
|
||||||
return true
|
} catch (err) {
|
||||||
|
if (Date.now() - started >= deadlineMs) {
|
||||||
|
dbg(`gave up re-applying depth ${d} after ${attempts} attempts`, err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
} catch { /* not yet */ }
|
await new Promise(r => setTimeout(r, stepMs))
|
||||||
await new Promise(r => setTimeout(r, stepMs))
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------- END DEBUG
|
// -------------------------------------------------------------- END DEBUG
|
||||||
@ -296,9 +300,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
|||||||
try {
|
try {
|
||||||
const d = expandDepthRef.current
|
const d = expandDepthRef.current
|
||||||
if (d == null) return dbg('re-apply aborted: depth became null')
|
if (d == null) return dbg('re-apply aborted: depth became null')
|
||||||
if (!(await waitForTable())) return dbg('re-apply gave up: viewer never re-attached a table')
|
if (await applyDepthWhenReady(d)) dbg(`re-apply done depth=${d}`)
|
||||||
await applyDepth(d)
|
|
||||||
dbg(`re-apply done depth=${d}`)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dbg('re-apply THREW', err)
|
dbg('re-apply THREW', err)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user