diff --git a/library/composables/usePaneConfig.js b/library/composables/usePaneConfig.js
index 793e7be1b..07bcbf5dd 100644
--- a/library/composables/usePaneConfig.js
+++ b/library/composables/usePaneConfig.js
@@ -29,17 +29,25 @@
* Re-forwarding happens inside a watchEffect, so any reactive source the getter reads — the outer
* config, a computed spec, the pane's own config — triggers it.
*/
-import { watchEffect, onBeforeUnmount } from 'vue'
+import { watchEffect, onBeforeUnmount, shallowReactive } from 'vue'
/**
* @param {() => (Element|null|undefined)} hostGetter the element the panes were appended under; used
* only to drop registrations whose element has left the DOM (a pane column can be rebuilt).
- * @param {string} tag the child element's tag name, for the same staleness check.
+ * ★ IT MUST BE IN THE SAME TREE AS THE PANES. Element.contains() does NOT cross a shadow
+ * boundary, so passing the custom element while its panes live in its shadow root would make
+ * every pane look detached and skip every write, silently.
* @returns {{ register: (el: Element, configGetter: () => object) => void, count: () => number }}
*/
-export function usePaneConfig(hostGetter, tag) {
- /** @type {Array<{el: Element, get: () => object, last: string|null}>} */
- const entries = []
+export function usePaneConfig(hostGetter) {
+ // ★ shallowReactive, NOT a plain array. The watchEffect below runs once, at the FIRST register();
+ // with a plain array a LATER entry's getter is never tracked, so its pane stays frozen until
+ // something the first entry reads happens to change. MEASURED with real Vue by studio: two panes
+ // reading different refs, changing only pane 2's source produced ZERO writes. That is the very bug
+ // this helper exists to fix, reappearing for any adopter whose panes read different sources.
+ // Making the list reactive re-runs the effect on register() and re-tracks every getter.
+ /** @type {Array<{el: Element, get: () => object, last: string|null, warned: boolean}>} */
+ const entries = shallowReactive([])
let stop = null
const alive = (el) => {
@@ -55,16 +63,22 @@ export function usePaneConfig(hostGetter, tag) {
// The attribute the caller already set at mount is the baseline: recording it here means the
// first watchEffect pass does not rewrite an identical value.
const last = el.getAttribute('profile-config')
- entries.push({ el, get: configGetter, last })
+ entries.push({ el, get: configGetter, last, warned: false })
if (!stop) {
stop = watchEffect(() => {
+ // PRUNE rather than skip: an adopter that rebuilds its pane column would otherwise keep
+ // detached elements in the list and re-run their getters on every pass, forever.
+ for (let i = entries.length - 1; i >= 0; i--) if (!alive(entries[i].el)) entries.splice(i, 1)
for (const entry of entries) {
let next
// A getter that throws must not take the other panes down with it: this runs inside a
- // watchEffect, where an exception would stop every later entry in the same pass.
- try { next = JSON.stringify(entry.get()) } catch (_) { continue }
+ // watchEffect, where an exception would stop every later entry in the same pass. It is
+ // reported ONCE per entry -- silence here would recreate the symptom this helper fixes.
+ try { next = JSON.stringify(entry.get()) } catch (e) {
+ if (!entry.warned) { entry.warned = true; console.error('[usePaneConfig] pane config getter threw; this pane will not follow the outer config:', e) }
+ continue
+ }
if (next === entry.last) continue
- if (!alive(entry.el)) continue
entry.last = next
entry.el.setAttribute('profile-config', next)
}
diff --git a/plugin-designs/regions-dashboard/regions-mfe.vue b/plugin-designs/regions-dashboard/regions-mfe.vue
index 44b793b20..bccb01811 100644
--- a/plugin-designs/regions-dashboard/regions-mfe.vue
+++ b/plugin-designs/regions-dashboard/regions-mfe.vue
@@ -118,7 +118,9 @@ const INHERITED_PAGE_KEYS = ['assetsBase', 'apiBaseUrl', 'prayBaseUrl', 'tokenUr
// after the outer profile-config changes yields the NEW pane config. It used to be inline in
// paneElement, which ran once per pane in onMounted and never again -- that is the whole defect:
// the pane is handed a JSON STRING, so a later locale change reached this element and stopped.
-const paneConfig = usePaneConfig(() => stage.value, TAG)
+// stage is the element the panes are appended UNDER, in the same tree as them -- contains() does
+// not cross a shadow boundary, so the host must not be the custom element itself.
+const paneConfig = usePaneConfig(() => stage.value)
function paneConfigFor(pane) {
const host = cfg.value || {}
diff --git a/public/dx-maps/doxa-maps-spa/manifest.json b/public/dx-maps/doxa-maps-spa/manifest.json
index 6d34eeccd..aeb67bee3 100644
--- a/public/dx-maps/doxa-maps-spa/manifest.json
+++ b/public/dx-maps/doxa-maps-spa/manifest.json
@@ -1144,7 +1144,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
- "integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
+ "integrity": "sha384-z4ZtI4c3ouh3yVdWy9vGTsmNm0aK7GfGlsBMGtrGa+9UhUtf7iXng+k9XsBEfev3",
"elements": [
"regions-dashboard"
],
@@ -1167,8 +1167,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n\n\n",
- "scriptMapbox": "\n\n\n\n\n\n"
+ "script": "\n\n\n\n\n\n",
+ "scriptMapbox": "\n\n\n\n\n\n"
}
},
{
@@ -1184,7 +1184,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n"
+ "script": "\n\n\n"
}
},
{
@@ -1200,8 +1200,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n\n\n",
- "scriptMapbox": "\n\n\n\n\n\n"
+ "script": "\n\n\n\n\n\n",
+ "scriptMapbox": "\n\n\n\n\n\n"
}
}
]
diff --git a/public/dx-maps/manifest.json b/public/dx-maps/manifest.json
index 6d34eeccd..aeb67bee3 100644
--- a/public/dx-maps/manifest.json
+++ b/public/dx-maps/manifest.json
@@ -1144,7 +1144,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
- "integrity": "sha384-+u2vDSnYni3VeNKSYdNWfw5bjKtriFjik/k10lugA0nw6Ozfi/vKUYp3a9sbMAp6",
+ "integrity": "sha384-z4ZtI4c3ouh3yVdWy9vGTsmNm0aK7GfGlsBMGtrGa+9UhUtf7iXng+k9XsBEfev3",
"elements": [
"regions-dashboard"
],
@@ -1167,8 +1167,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n\n\n",
- "scriptMapbox": "\n\n\n\n\n\n"
+ "script": "\n\n\n\n\n\n",
+ "scriptMapbox": "\n\n\n\n\n\n"
}
},
{
@@ -1184,7 +1184,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n"
+ "script": "\n\n\n"
}
},
{
@@ -1200,8 +1200,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n\n\n",
- "scriptMapbox": "\n\n\n\n\n\n"
+ "script": "\n\n\n\n\n\n",
+ "scriptMapbox": "\n\n\n\n\n\n"
}
}
]
diff --git a/public/dx-maps/regions-dashboard/region-dashboards/index.html b/public/dx-maps/regions-dashboard/region-dashboards/index.html
index 1706f1e7c..0987b736b 100644
--- a/public/dx-maps/regions-dashboard/region-dashboards/index.html
+++ b/public/dx-maps/regions-dashboard/region-dashboards/index.html
@@ -70,7 +70,7 @@
-
+
-
+
-
+