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. |
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| scripts | ||
| stores | ||
| styles | ||
| utils | ||
| vendor/cldr | ||
| index.js | ||
| ProfileLoader.vue | ||
| README.md | ||
library/ (@map) — the shared map template
The other half of the architecture (see ../plugin-designs/README.md):
every map in plugin-designs/<your-map>/ composes the reusable, parameterized pieces that live here.
Fix something here and every map benefits on the next build; break something here and every map
breaks — read components/WARNING.md before touching shared pieces.
| Folder | What lives there |
|---|---|
ProfileLoader.vue |
The root of every bundle: parses profile-config, loads the profile, provides the stores + config |
components/ |
Shared UI — map, legends, controls, popup (PeopleGroupDetail), tables, charts, hints (README) |
composables/ |
Shared logic — map engine (useMapInstance), provider switch, shadow-DOM styles, theming |
stores/ |
Per-instance Pinia stores (ui / map / data) — provided by ProfileLoader, injected by children |
api/ |
Data-source registry (sources.json) + fetch layer — the JAMstack runtime wiring |
colors/ |
Color sets maps opt into via profile-config.colorSet |
i18n/ |
The shared string catalogs (all maps speak the same languages) |
data/ |
Small shared datasets baked at build time (big served data lives in ../data/) |
constants/ |
Shared constants |
scripts/ |
Maintainer pipelines (posters, research) — not part of any bundle |
The rule of thumb (same as WARNING.md): custom code in your app-profile, reusable code here — and things only move from the first to the second once they've earned it.