Collapse the legend, tap a pin, then close the detail: the legend comes back open, though nothing asked it to. Reported against the leaf research map; reproduced at 1440 in en, collapsed-first. selectPeopleGroup saved the pre-detail legend state on every call that carried a group, not only on the call that entered detail. The second call therefore saved the 'open' the first call had just forced, on top of the 'collapsed' it correctly saved on entry, and closing the detail restored that 'open'. The measured part is why a second call happens at all. The pin click handler is registered per map layer, and every visible pin has an invisible hitbox twin at the same point -- same source, larger radius, added alongside it so taps are easier to land. A layer-scoped click fires once per layer with a feature under the point, so a single pin click calls selectPeopleGroup exactly twice, 3ms apart. Measured on two different pins, both reporting both layers at the click point; never more than twice. So ONE pick was already enough to lose the collapsed state -- it never needed two, which is why this reproduced every time rather than intermittently. The fix is to make the method idempotent: save only when entering detail (no group was selected before), restore only what was actually saved, then clear it so a later detail cannot inherit a stale value. The duplicate binding is a real defect in its own right and is tracked separately; this method should not have depended on being called exactly once. Verified two-sided against the shipped source, driving the real method body rather than a copy: seven scenarios pass on the fix and four fail with the previous body restored, while the scenarios that must be insensitive stay green both ways. The poisoned run reproduces the browser measurement value for value -- saved collapsed, overwritten to open by the second call, restored open. Trace in _qa-cycle so the next reader can re-run it. |
||
|---|---|---|
| .. | ||
| api | ||
| charts/whiteboard | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| dashboards | ||
| data | ||
| elements | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| packets | ||
| 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.