Merge origin/development into studio/batch (upg100's example images)

This commit is contained in:
Doxa Maps 2026-09-29 22:33:34 +00:00
commit e3c631932f
184 changed files with 202 additions and 107 deletions

View file

@ -105,17 +105,40 @@ const heading = computed(() => config.value.title
//
// Set on the HOST rather than on .bp-root, deliberately: `dir` takes effect through the inherited
// CSS `direction`, and inheritance crosses shadow boundaries — so one attribute covers this
// element's own chrome AND both nested elements beneath it. Only ever SET, never cleared: a host
// element's own chrome AND both nested elements beneath it. Set and CLEARED, but only ever ours: a host
// page that already declares its own dir owns that decision.
const textDir = computed(() => {
const l = hostLang.value || getRuntimeLocale() || detectLocale()
return RTL_LOCALES.has(l) ? 'rtl' : 'ltr'
})
const _self = getCurrentInstance()
// ★ WE CLEAR ONLY THE DIRECTION WE SET. The line below used to be a one-way
// `if (rtl) setAttribute('dir','rtl')` with no else, so a reader who switched from an RTL locale to
// an LTR one (ar -> zh) kept a right-to-left layout for the rest of the session: Chinese laid out
// backwards, which reads as a broken page rather than a stale setting.
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _self?.proxy?.$el?.getRootNode?.()?.host
if (host && textDir.value === 'rtl') host.setAttribute('dir', 'rtl')
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
host.setAttribute('dir', 'rtl')
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}
onMounted(reflectDir)

View file

@ -300,7 +300,7 @@ watchEffect(() => { if (hostLang.value) setRuntimeLocale(hostLang.value) })
// Set on the custom-element HOST rather than on the layout inside it, deliberately: `dir` takes
// effect through the inherited CSS `direction`, and inheritance crosses shadow boundaries — so one
// attribute on the host covers this composite's own chrome AND every nested element beneath it,
// which is the half the audit found missing. Only ever SET, never cleared: a host page that already
// which is the half the audit found missing. Set and CLEARED, but only ever ours: a host page that already
// declares its own dir owns that decision.
const textDir = computed(() => {
const l = hostLang.value || getRuntimeLocale() || detectLocale()
@ -316,10 +316,29 @@ const textDir = computed(() => {
// it is". Measured in this tree before, under Vue 3.5.42, when research-map.vue's emitFilterState
// died the same way; the fix is its _selfInstance, and this is the same fix.
const _selfInstance = getCurrentInstance()
// ★ WE CLEAR ONLY THE DIRECTION WE SET. This was a one-way `if (rtl) setAttribute` with no else,
// so ar -> zh kept a right-to-left layout for the rest of the session. The host page's own dir is
// still respected: the value found before we first wrote is restored verbatim.
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _selfInstance?.proxy?.$el?.getRootNode?.()?.host
if (host && textDir.value === 'rtl') host.setAttribute('dir', 'rtl')
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
host.setAttribute('dir', 'rtl')
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}
onMounted(reflectDir)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View file

@ -278,16 +278,39 @@ const languageCode = computed(() =>
// page it owns and useless for an element dropped into someone else's, which is this grid's whole
// job. Set on the HOST, like both composites: `dir` takes effect through the inherited CSS
// `direction`, and inheritance crosses shadow boundaries, so one attribute turns the element and
// everything it renders. Only ever SET, never cleared — a host page that declares its own dir owns
// everything it renders. Set and CLEARED, but only ever ours — a host page that declares its own dir owns
// that decision.
const textDir = computed(() => {
const l = languageCode.value || getRuntimeLocale() || detectLocale()
return RTL_LOCALES.has(String(l).split('-')[0].toLowerCase()) ? 'rtl' : 'ltr'
})
// ★ WE CLEAR ONLY THE DIRECTION WE SET. The line below used to be a one-way
// `if (rtl) setAttribute('dir','rtl')` with no else, so a reader who switched from an RTL locale to
// an LTR one (ar -> zh) kept a right-to-left layout for the rest of the session: Chinese laid out
// backwards, which reads as a broken page rather than a stale setting.
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = hostEl()
if (host && host.setAttribute && textDir.value === 'rtl') host.setAttribute('dir', 'rtl')
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
host.setAttribute('dir', 'rtl')
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}
watch(textDir, reflectDir)

View file

@ -152,7 +152,7 @@ const hostList = computed(() => (typeof hostConfig.value?.list === 'string' ? ho
// Set on the custom-element HOST rather than on the layout inside it, deliberately: `dir` takes
// effect through the inherited CSS `direction`, and inheritance crosses shadow boundaries — so one
// attribute on the host covers this composite's own chrome AND every nested element beneath it,
// which is the half the audit found missing. Only ever SET, never cleared: a host page that already
// which is the half the audit found missing. Set and CLEARED, but only ever ours: a host page that already
// declares its own dir owns that decision.
const textDir = computed(() => {
const l = hostLang.value || getRuntimeLocale() || detectLocale()
@ -168,10 +168,33 @@ const textDir = computed(() => {
// it is". Measured in this tree before, under Vue 3.5.42, when research-map.vue's emitFilterState
// died the same way; the fix is its _selfInstance, and this is the same fix.
const _selfInstance = getCurrentInstance()
// ★ WE CLEAR ONLY THE DIRECTION WE SET. The line below used to be a one-way
// `if (rtl) setAttribute('dir','rtl')` with no else, so a reader who switched from an RTL locale to
// an LTR one (ar -> zh) kept a right-to-left layout for the rest of the session: Chinese laid out
// backwards, which reads as a broken page rather than a stale setting.
// The original reason for never clearing is still honoured, and is why this remembers rather than
// just removing: a host page that declares its OWN dir owns that decision, so the value found before
// we first wrote is restored verbatim, and removed only when there was none.
// ★ CAPTURED AT MOUNT, NOT AT FIRST WRITE. The capture used to sit inside the rtl branch, so it
// recorded whatever `dir` happened to be on the host the first time WE wrote — and if anything else
// had already set rtl by then, that value was recorded as "the page's own" and faithfully restored
// forever after. MEASURED on the Big Picture leaf: ar left rtl, and zh put rtl straight back.
// Reading it on the FIRST call instead, before any write of ours, is the only moment the attribute
// is certainly the page's and not ours.
let _hostDir = null
let _weSetDir = false
function reflectDir() {
try {
const host = _selfInstance?.proxy?.$el?.getRootNode?.()?.host
if (host && textDir.value === 'rtl') host.setAttribute('dir', 'rtl')
if (!host || !host.setAttribute) return
if (_hostDir === null) _hostDir = host.getAttribute('dir') || ''
if (textDir.value === 'rtl') {
host.setAttribute('dir', 'rtl')
_weSetDir = true
} else if (_weSetDir) {
if (_hostDir) host.setAttribute('dir', _hostDir); else host.removeAttribute('dir')
_weSetDir = false
}
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}
onMounted(reflectDir)
@ -429,7 +452,13 @@ function pushToChildren() {
// 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
// BOTH KEYS, and `locale` is the one that decides. Every reader takes locale FIRST and falls
// back to lang — ProfileLoader.vue:189 and people-groups-grid.vue:273/380 all read
// `config.locale || config.lang`. Writing only `lang` is therefore correct exactly while no
// child config carries a `locale`, and silently stops working the day one does: a stale locale
// from a boot copy or a host-list path would shadow every fresh lang we push, forever. Writing
// both keeps the two in step whatever put them there.
if (hostLang.value) { cfg.lang = hostLang.value; cfg.locale = hostLang.value }
map.setAttribute('profile-config', JSON.stringify(cfg))
}
const grid = rootEl.querySelector('people-groups-grid')
@ -446,8 +475,9 @@ function pushToChildren() {
if (hostLang.value) {
let gcfg = {}
try { gcfg = JSON.parse(grid.getAttribute('profile-config') || '{}') } catch (_) { gcfg = {} }
if (gcfg.lang !== hostLang.value) {
if (gcfg.lang !== hostLang.value || gcfg.locale !== hostLang.value) {
gcfg.lang = hostLang.value
gcfg.locale = hostLang.value
grid.setAttribute('profile-config', JSON.stringify(gcfg))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show more