From ebe0288202d7a5662e1d364d0c421c710c14181d Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Thu, 17 Sep 2026 13:06:28 -0400 Subject: [PATCH] Find the datagrid by asking the DOM, not by guessing the nesting The rule was injected into perspective-viewer-datagrid's shadow root, which is not where the cells live, so it applied to nothing and the grand-total column stayed visible. Locates regular-table by walking through shadow roots and injects into whatever root contains it, via getRootNode(). That makes no assumption about how the plugin nests -- which is the assumption that was wrong, and the kind that breaks on a Perspective upgrade anyway. The grid is built asynchronously after load, so hideSplitTotal now reports whether it found anything and is retried once on a short delay, as well as from the config-update handler. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/views/Forecast.jsx | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index f73e983..fa1a2ce 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -659,7 +659,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // the named table in time. await viewer.load(tableRef.current) viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') - hideSplitTotal() + if (!hideSplitTotal()) setTimeout(hideSplitTotal, 400) // restore last-used layout or build default // Strip cfg.table — table is already loaded by reference above; a stale name @@ -841,15 +841,36 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio } ` + // Find the grid wherever it is. Guessing the nesting was wrong once already -- + // the plugin element's own shadow root is not where the cells live -- so locate + // regular-table by walking through shadow roots, then inject into whatever root + // actually contains it via getRootNode(). No structural assumption survives a + // Perspective upgrade; this one asks the DOM instead. + function deepFindRegularTable(node, depth = 0) { + if (!node || depth > 12) return null + if (node.tagName === 'REGULAR-TABLE') return node + for (const child of node.children || []) { + const hit = deepFindRegularTable(child, depth + 1) + if (hit) return hit + } + if (node.shadowRoot) return deepFindRegularTable(node.shadowRoot, depth + 1) + return null + } + function hideSplitTotal() { const viewer = viewerRef.current - const root = viewer?.shadowRoot - ?.querySelector('perspective-viewer-datagrid')?.shadowRoot - if (!root || root.getElementById?.('pf-hide-split-total')) return + if (!viewer) return + const grid = deepFindRegularTable(viewer) + // The grid is built asynchronously after load, so it may not exist yet. + // Retried from the config-update handler, and once on a short delay. + if (!grid) return false + const root = grid.getRootNode() + if (!root || root.querySelector?.('#pf-hide-split-total')) return true const style = document.createElement('style') style.id = 'pf-hide-split-total' style.textContent = HIDE_SPLIT_TOTAL_CSS - root.appendChild(style) + ;(root.appendChild ? root : document.head).appendChild(style) + return true } // Row depth is a ViewConfig field, so it is set through restore() rather than