From b0d08224c1b3357c51ff3445e0fc3b5504b450dc Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 19:29:12 +0000 Subject: [PATCH] A live language switch never reached the panes inside Simple Research The composite re-pushed its FILTER state to the map and the grid on every change, and nothing else. So a host switching language moved the composite's own chrome and stopped there: the outer element received the new profile-config, the push re-parsed each child's EXISTING attribute, and the boot `lang` survived every write. Measured on a live en to ar switch: the map and the grid both stayed English while the frame around them changed. The panes were never at fault. Each child's own chain is fully reactive -- it is reactive to its own prop, and its prop was frozen. The map now carries the locale in the same push that carries the filters. The grid needs its own line, because its three linked facets travel as separate attributes while its language travels in profile-config, which this composite set only when a host supplied a list, and never again. And a locale change now triggers a push at all: the existing watchers cover filter state only. The grid write is conditional so an unchanged locale writes nothing. That matters here more than it looks: setting a pane's attribute makes it re-filter and emit, and the composite reads those emissions -- which is what the `pushing` guard above exists for, after a measured round trip once cancelled a selection the composite had just applied. Verified two-sided by driving the real pushToChildren against stub elements: the locale reaches both panes, and a repeat at the same locale adds no write. Removing the map's line fails that assertion alone and leaves the grid's passing. --- .../research-map-plus-grid.vue | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue b/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue index d3a13ba7e..26d39dcfc 100644 --- a/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue +++ b/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue @@ -423,6 +423,13 @@ function pushToChildren() { let cfg = {} try { cfg = JSON.parse(map.getAttribute('profile-config') || '{}') } catch (_) { cfg = {} } cfg.externalFilters = { region: linked.region.value, religion: linked.religion, language: linked.language.label, fitSlugs: gridSlugs.value } + // ★ AND THE LOCALE. This rewrote the FILTERS and nothing else, so a live language switch moved + // the composite's own chrome and never reached the panes: the outer element got the new + // profile-config, this re-parsed the child's EXISTING attribute, and the boot `lang` survived + // every push. MEASURED: the map stayed English on a live en->ar switch while the composite + // around it changed. The leaf's own chain is fully reactive and was never at fault — it is + // reactive to its own prop, and its prop was frozen. + if (hostLang.value) cfg.lang = hostLang.value map.setAttribute('profile-config', JSON.stringify(cfg)) } const grid = rootEl.querySelector('people-groups-grid') @@ -433,8 +440,21 @@ function pushToChildren() { // so a stable ISO code matches in every locale while a display name only matches in the one it // was fetched in. grid.setAttribute('primary-language', gridLanguage()) + // The grid's three linked facets travel as their own attributes, but its LANGUAGE does not — + // it reads that from profile-config, which this composite set only when a host list was + // supplied, and never again. Same frozen-prop defect as the map above, one channel over. + if (hostLang.value) { + let gcfg = {} + try { gcfg = JSON.parse(grid.getAttribute('profile-config') || '{}') } catch (_) { gcfg = {} } + if (gcfg.lang !== hostLang.value) { + gcfg.lang = hostLang.value + grid.setAttribute('profile-config', JSON.stringify(gcfg)) + } + } } } +// A locale change has to reach the panes too; `linked` and `gridSlugs` only cover filter state. +watch(hostLang, pushToChildren) watch(linked, pushToChildren, { deep: true }) // The slug set changes without any facet changing (a search term, a cleared filter), so it needs // its own trigger — `linked` alone would leave the camera behind.