From 0f6d5049152656c2a8afddde82a456dd61fd8984 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:49:36 +0000 Subject: [PATCH] An RTL locale turns the plugin, not the partner's page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arabic text was rendering inside a left-to-right layout: readable glyphs in the wrong place, which reads as a styling quirk rather than the bug it is. TWO DEFECTS IN ONE BLOCK (library/i18n/index.js, createAppI18n): TARGET — it wrote document.documentElement.dir. An EMBEDDED widget doing that flips the PARTNER'S WHOLE PAGE: their headings, their nav, their footer. The existing comment already worried about the host owning that decision, but guarded only against CLEARING dir, never against setting it. TIMING — it ran at createAppI18n() construction, reading detectLocale(), which sees only the URL and . A locale arriving later through profile-config — the channel a HOST uses — never reached it. Measured across the previous landing: htmlDir null on every host-channel run, including leaves whose chrome was fully Arabic. Direction now binds on ProfileLoader's own root, driven by effectiveLocale — the locale actually in force AFTER profile-config, so it re-evaluates when a host changes the language. This element cannot tell "I am the page" from "I am a widget", and guessing wrong is exactly the failure above, so it no longer touches the document at all. MEASURED on a partner page at a FOREIGN ORIGIN with profile-config.locale='ar' (same gate, before and after): before upg-100-mfe plugin root ltr, arabic 1765 FAIL poster-wall plugin root ltr, arabic 1738 FAIL after upg-100-mfe plugin root RTL, arabic 1765 pass poster-wall plugin root RTL, arabic 1738 pass partner //

stayed ltr throughout. Standalone leaves are unchanged and still get from ?lang=ar — verified, both profiles — because a leaf page DOES own its document. createAppI18n's document write therefore stays for now; replacing it needs a page-ownership signal that touches every generated leaf, which is its own change rather than a rider on this one. ⚠ THE GATE REFUSES TO JUDGE WHAT IT CANNOT SEE, and that guard earned its place twice before this was measurable. Its first two runs reported "the element never mounted" — a HARNESS gap, not the product: a partner embed uses crossorigin="anonymous" (SRI requires it), so the tree must send Access-Control-Allow-Origin, which `bun run serve` does not. The gate now proxies the tree through its own CORS-enabled port. Without that guard those runs would have read as fails-first evidence for a defect nothing had actually exercised. It also fails if the locale produced zero Arabic chrome, because direction cannot be assessed on a page whose language never applied. --- internal/check-embedded-rtl.mjs | 152 +++++++++++++ library/ProfileLoader.vue | 26 ++- package.json | 1 + .../all-people-groups/all-people-groups.js | 44 ++-- .../all-people-groups/index.html | 2 +- .../cities-internet/cities-internet.js | 54 ++--- .../connectivity-list/index.html | 2 +- .../connectivity-map/index.html | 2 +- .../cities-internet/data-tables/index.html | 2 +- .../country-posters/countries-map/index.html | 2 +- .../country-poster-wall/index.html | 2 +- .../country-posters-mfe/index.html | 2 +- .../country-posters/country-posters.js | 214 +++++++++--------- public/doxa-maps/demo-map/demo-map.js | 58 ++--- public/doxa-maps/demo-map/demo-map/index.html | 2 +- .../demo-map/demo-map/maplibre/index.html | 2 +- public/doxa-maps/doxa-maps-spa/manifest.json | 76 +++---- .../doxa-research-map/doxa-research-map.js | 96 ++++---- .../doxa-research-map/research-map/index.html | 2 +- .../research-map/maplibre/index.html | 2 +- .../doxa-simple-map/doxa-simple-map.js | 66 +++--- .../doxa-simple-map/adoption/index.html | 2 +- .../doxa-simple-map/engagement/index.html | 2 +- .../doxa-simple-map/index.html | 2 +- .../doxa-simple-map/prayer/index.html | 2 +- .../gospel-resources/charts/index.html | 2 +- .../gospel-resources/data-tables/index.html | 2 +- .../gospel-dashboard/index.html | 2 +- .../gospel-resources/gospel-resources.js | 40 ++-- .../resources-list/index.html | 2 +- .../gospel-resources/resources-map/index.html | 2 +- public/doxa-maps/index.html | 2 +- public/doxa-maps/key-cities/key-cities.js | 128 +++++------ public/doxa-maps/manifest.json | 76 +++---- .../my-upg-100-list/my-upg-100-list.js | 110 ++++----- .../my-upg-100-list/poster-wall/index.html | 2 +- .../my-upg-100-list/upg-100-list/index.html | 2 +- .../my-upg-100-list/upg-100-mfe/index.html | 2 +- .../pplr-data-maps/pplr-data-maps.js | 52 ++--- .../pplr-data-maps/pplr-data-maps/index.html | 2 +- .../region-dashboards/index.html | 2 +- .../regions-dashboard/regions-dashboard.js | 146 ++++++------ .../regions-dashboard/regions-list/index.html | 2 +- .../regions-dashboard/regions-mfe/index.html | 2 +- .../research-dashboard/research-dashboard.js | 60 ++--- .../research-dashboard/index.html | 2 +- .../template-bundle/template-bundle.js | 30 +-- public/doxa-maps/upg-neighbor/upg-neighbor.js | 54 ++--- .../upg-neighbor/bloom-cockpit/index.html | 2 +- .../upg-neighbor/flower-mesh/index.html | 2 +- .../upg-neighbor/upg-neighbor/index.html | 2 +- .../upg-neighbor/lang-cluster/index.html | 2 +- .../upg-neighbor/send-map/index.html | 2 +- .../upg-neighbor/upg-neighbors/index.html | 2 +- .../wall-of-people-groups.js | 34 +-- .../wall-of-people-groups/index.html | 2 +- 56 files changed, 882 insertions(+), 707 deletions(-) create mode 100644 internal/check-embedded-rtl.mjs diff --git a/internal/check-embedded-rtl.mjs b/internal/check-embedded-rtl.mjs new file mode 100644 index 000000000..1fdfbec97 --- /dev/null +++ b/internal/check-embedded-rtl.mjs @@ -0,0 +1,152 @@ +#!/usr/bin/env node +/** + * check-embedded-rtl — an RTL locale must turn the PLUGIN right-to-left, and must NEVER + * turn the PARTNER'S PAGE right-to-left. + * + * TWO DEFECTS, one block (library/i18n/index.js, createAppI18n): + * 1. TIMING. It reflects RTL at CONSTRUCTION time from detectLocale(), which reads only the URL + * and . A locale arriving later through profile-config — the channel a HOST uses — + * never reaches it, so Arabic text renders inside a left-to-right layout. Measured: htmlDir + * null on every host-channel run, including leaves whose chrome was fully Arabic. + * 2. TARGET. It writes document.documentElement.dir. An EMBEDDED widget doing that flips the + * partner's WHOLE PAGE. The existing comment already worries about the host ("a WordPress RTL + * theme owns that decision") but only guards against CLEARING it, not against setting it. + * + * So direction belongs on the plugin's OWN element, driven by the locale ProfileLoader actually + * applies; stays only where the plugin owns the page (a standalone leaf). + * + * ⚠ THE HOST PAGE IS ON A DIFFERENT ORIGIN, deliberately: same-origin would let a bug pass that + * only bites a real partner. The tree is served on one port, the partner page on another. + * + * Usage: node internal/check-embedded-rtl.mjs [baseUrl] + */ +import { chromium } from 'playwright' +import { createServer } from 'node:http' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { servesThisTree } from './_serves-this-tree.mjs' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const BASE = process.argv[2] || process.env.BASE_URL || 'http://127.0.0.1:4173' +const HOST_PORT = Number(process.env.RTL_HOST_PORT || 47393) + +let reachable = false +try { const r = await fetch(`${BASE}/doxa-maps/manifest.json`, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ } +if (!reachable) { console.log(`EMBEDDED-RTL: SKIP — nothing served at ${BASE}.`); process.exit(0) } +const treeCheck = await servesThisTree(BASE, ROOT) +if (!treeCheck.ok) { console.error(`EMBEDDED-RTL: NOT MEASURED — ${treeCheck.why}`); process.exit(2) } + +// A PARTNER PAGE on a foreign origin: its own is LTR and must stay that way. +const CASES = [ + { id: 'my-upg-100-list', tag: 'my-upg-100-list-map', profile: 'upg-100-mfe' }, + { id: 'my-upg-100-list', tag: 'my-upg-100-list-map', profile: 'poster-wall' }, +] +const TREE_PORT = Number(process.env.RTL_TREE_PORT || 47395) +const TREE = `http://127.0.0.1:${TREE_PORT}` +const hostHtml = (c) => ` +partner + +

Partner page

+ +