A data refresh is not a deselection: the legend keeps a selection its new nodes still contain
rega measured the WAGF Regions tab on Simple Research: the polygon highlight lands 51-85 ms after the click, then 433-667 ms later it is gone — fill-opacity back to 0.7, line-width back to 0.6, no row marked, every region at full colour. That reads exactly like the Driver's "the legend isn't connecting with the map", and it is worse than not connecting: it connected and then undid itself. Those two values are the fingerprint. 0.7 and 0.6 are written only by this profile's CLEAR paths, every one of which is reached from a deselect — so something sent select(null) half a second after the click, and nobody clicked twice. SemanticTreeLegend watches the IDENTITY of its nodes array, and every tree feeding it is a computed that returns a NEW array each evaluation. Any late async that touches a dependency — pins still arriving, a lazily loaded layer, a refresh — rebuilds the array with IDENTICAL CONTENT, and the reset in that watcher then cancelled the reader's selection: expandedIds cleared, selectedNode nulled, select(null) emitted. The reset is right for genuinely new data; what was wrong is that it could not tell the two cases apart. It asks one question now: does the selected row still EXIST in the new nodes? If it does, re-point at the new object and leave the tab and the expansion alone, because nothing about the reader's state has become invalid. If it is gone, the reset runs exactly as before and the deselect is honest. Re-pointed rather than kept, because the old object belongs to the discarded array and holding it would compare the highlight against a node the tree no longer contains. This is shared, so it is every tree legend on every map, not only mine: any profile whose data arrives or refreshes after a click was silently losing that click. NOT changed, and deliberately: the WAGF Regions TAB click also compiles shaders for regions-fill and regions-border (rega: 2.9-4.1 s on SwiftShader), because those layers are added on that click. Warming them at boot would mean paying for two more layers on every reader of every tab to save a one-time cost on one tab, and the 6.4 MB regions GeoJSON is lazily loaded there on purpose. SwiftShader inflates a compile by roughly an order of magnitude — the pins variant measured 3.2-3.5 s there and +0.5 s of real boot delta — so the honest expectation on a GPU is a few hundred milliseconds, once, on a tab that is already loading megabytes. That is a trade I am declining to make rather than one I missed. Verified: gates:pre 12/12. Needs eyes: rega, the same WAGF Regions row click — the highlight should now still be there at 5 s, and the variant compile may appear on that click once the selection sticks.
This commit is contained in:
parent
daa6ee6eda
commit
cdb3ee1e81
1 changed files with 21 additions and 0 deletions
|
|
@ -288,6 +288,27 @@ watch(() => props.open, (v) => {
|
|||
})
|
||||
|
||||
watch(() => safeNodes.value, () => {
|
||||
// ★ A DATA REFRESH IS NOT A DESELECTION (rega, WAGF Regions on Simple Research, 2026-09-28). The
|
||||
// polygon highlight landed 51-85 ms after the click and was wiped 433-667 ms later, leaving no row
|
||||
// marked and every region at full colour — which reads exactly like the Driver's "the legend isn't
|
||||
// connecting with the map", and is worse, because it connected and then undid itself.
|
||||
//
|
||||
// This watcher fires on the IDENTITY of the nodes array, and every tree here is a computed that
|
||||
// returns a NEW array on each evaluation. So any late async that touches a dependency — pins still
|
||||
// arriving, a lazily loaded layer, a refresh — rebuilt the array with IDENTICAL CONTENT and this
|
||||
// reset cancelled the user's selection. The reset itself is right for genuinely new data; what was
|
||||
// wrong is that it could not tell one case from the other.
|
||||
//
|
||||
// So ask whether the selected row still EXISTS. If it does, re-point at the new object and leave the
|
||||
// tab and the expansion alone: nothing about the reader's state has become invalid. If it is gone,
|
||||
// the reset below is correct and the deselect is honest.
|
||||
const keepId = selectedNode.value?.id || null
|
||||
if (keepId) {
|
||||
const survivor = findNodeById(safeNodes.value, keepId)
|
||||
// Re-pointed, not kept: the old object belongs to the discarded array, and holding it would make
|
||||
// the highlight compare against a node the tree no longer contains.
|
||||
if (survivor) { selectedNode.value = survivor; return }
|
||||
}
|
||||
// Falls back to defaultTab, not a hardcoded 0 — otherwise a profile that opens on
|
||||
// a deeper generation silently reverts to the top the first time its nodes change.
|
||||
// ⚠ AND THE STORE ONLY COUNTS ONCE IT HAS BEEN TOUCHED. This read `instance?.activeTab.value`
|
||||
|
|
|
|||
Loading…
Reference in a new issue