From 3345779bdb6b6be6a929ec5ec494ea2f9f95a830 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 05:39:01 +0000 Subject: [PATCH] upg-100's first click: onLegendSelect was bailing on an unready layer, and the request was thrown away MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rega's re-shoot on main 27eed341 answered it in one run, because the apply now reports its own verdict. Timeline, ms from navigation: t=2958 click 'Show Northwest India on the map' t=3015 select-zone = "asia-india-rajasthan" written on the map pane t=4887 data-select-zone-state = "applied-but-no-selection" t=4900 the same pair AGAIN, 13 ms later, still no selection t=5308 the doxa:select-zone listener first appears So delivery was fine, the id was right, the node WAS found, onLegendSelect WAS called — and the selection did not hold, twice. THE CAUSE is onLegendSelect's own first line: if (!map || !map.getLayer('tapestry-fill')) return It returns silently when the map's tapestry layer is not up yet, so selectedKey is never set. At t=4.9s that layer did not exist. Both applies came from watch([legendNodes, mapRef]), which is why they landed before the listener did. So the readiness signal was never legendNodes and never mapRef. It is THE LAYER, and I had guessed at the other two in turn. This is the THIRD layer of one defect — apply once against unready state and discard — and each layer hid the next because every failure was silent. Two changes, both from the measurement rather than a theory: 1. applySelectZone RETAINS the request when the effect did not take. It used to clear _pendingZoneId right after calling onLegendSelect, treating "called" as "done"; now a state of applied-but-no-selection keeps the id outstanding. 2. ensureLayers() drains it. That function adds the tapestry source and both layers, so it is the exact moment onLegendSelect can hold a selection. Coverage is complete either way round: if ensureLayers runs first the layer already exists and the ordinary apply succeeds; if it runs later, the retained request drains. A click after everything is up still goes through the attribute observer. Branch was RESET onto main 27eed341 and carries this one commit only — the handoff lesson from last time: a merged branch's tree holds my stale copies of everyone else's files, so replaying it (the sane thing for a recipient) reverts their work. Source only; my-upg-100-list needs a rebuild. --- .../my-upg-100-list/upg-100-list.vue | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/plugin-designs/my-upg-100-list/upg-100-list.vue b/plugin-designs/my-upg-100-list/upg-100-list.vue index f28cbcd7e..ad25996aa 100644 --- a/plugin-designs/my-upg-100-list/upg-100-list.vue +++ b/plugin-designs/my-upg-100-list/upg-100-list.vue @@ -626,6 +626,16 @@ function ensureLayers() { // F2-D2: brighter, thicker outline gives contrast on dark without washing out light. paint: { 'line-color': matchColorExpr, 'line-width': 1.2, 'line-opacity': 0.9 }, }) + + // ★ THE REAL READINESS SIGNAL. onLegendSelect() returns early unless map.getLayer('tapestry-fill') + // exists, so a "Show on map" request that arrives before this function runs cannot hold a + // selection however correctly it was delivered. This is the third layer of the same defect -- + // apply once against unready state, then discard -- and this is the point where the state is + // finally ready. Drains any request retained by applySelectZone(). + if (_pendingZoneId) { + const id = _pendingZoneId + nextTick(() => { if (_pendingZoneId === id) applySelectZone(id) }) + } } // ── 09b shift-select tool: selection highlight layers (D-2) ────────────────── @@ -1606,14 +1616,21 @@ function applySelectZone(zoneId) { const node = findLegendNode(legendNodes.value, zoneId); if (!node) { _pendingZoneId = zoneId; _markZoneState('unresolved', zoneId); return false; } // not ready -- REMEMBER it onLegendSelect(node); - _pendingZoneId = undefined; // selectedKey (:163, "currently isolated zoneKey (or null)") is this component's own record of the // isolation, so 'applied-but-no-selection' means the path was REACHED and its EFFECT lost -- a // different bug from delivery, and the one rega's timeline points at. // ★ I first wrote selectedZoneKey here, which does not exist; the try/catch would have swallowed // the ReferenceError and reported 'applied' every time. A diagnostic that cannot report failure is // worse than none. main's check:no-free-identifiers (6b05f513) now catches this class. - _markZoneState(!selectedKey.value ? 'applied-but-no-selection' : 'applied', zoneId); + // ★ IF THE EFFECT DID NOT TAKE, THE REQUEST IS STILL OUTSTANDING. rega's re-shoot on 27eed341 + // read exactly this state twice, 13 ms apart, with the right id -- so the node WAS found and + // onLegendSelect WAS called, and the selection still did not hold. The cause is onLegendSelect's + // own first line: `if (!map || !map.getLayer('tapestry-fill')) return`. It bails silently when the + // map's tapestry layer is not up, which at t=4.9s it was not. So the readiness signal was never + // legendNodes or mapRef -- it is THE LAYER, and the old code cleared _pendingZoneId here as though + // the work were done. Keeping it means ensureLayers() can drain it the moment the layer exists. + if (!selectedKey.value) { _pendingZoneId = zoneId; _markZoneState('applied-but-no-selection', zoneId); return false; } + _markZoneState('applied', zoneId); return true; }