#!/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' import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') // ⚠ FILTER FLAGS OUT OF THE POSITIONAL. `--emit` was consumed as the baseUrl and the gate // reported "SKIP — nothing served at --emit". Exactly the bug I hit on check-leaf-english-leaks // last week; the same shape, in a different file, three days apart. const BASE = process.argv.slice(2).find((a) => !a.startsWith('--')) || process.env.BASE_URL || 'http://127.0.0.1:4173' const HOST_PORT = Number(process.env.RTL_HOST_PORT || 47393) // ★ --emit WRITES THE PARTNER PAGE TO DISK INSTEAD OF MEASURING IT. // Under the Playwright policy (Driver, 2026-09-25) only rega runs a browser, so this check's // result now has to be produced by someone else's eyes. But the partner page only ever existed // INSIDE this process — two createServer calls on ephemeral ports — so "open a partner page at a // foreign origin" was not a thing anyone could actually do. NEEDS-EYES rows 3, 4 and 6 name a // runnable probe or an exact URL; row 7 (mine) named a construct that lived in my own memory. // --emit closes that: it writes a self-contained page plus a two-line serve recipe, so the item // is reproducible by whoever has the browser. const EMIT = process.argv.includes('--emit') let reachable = false try { const r = await fetch(`${BASE}/${TREE_NAME}/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. // ⚠ BOTH CHANNELS. profile-config is how a HOST sets the language; ?lang= on the partner's OWN // url is how a READER does, and detectLocale() reads the URL FIRST. They reach the // document-direction code by different routes, so a check that drives only one proves nothing // about the other — which is why the URL case exists as a row here rather than as a variant of // the config one. The emit writes a fixture per row, so this array IS the needs-eyes inventory. 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' }, { id: 'my-upg-100-list', tag: 'my-upg-100-list-map', profile: 'poster-wall', urlLang: 'ar' }, ] const TREE_PORT = Number(process.env.RTL_TREE_PORT || 47395) const TREE = `http://127.0.0.1:${TREE_PORT}` // ⚠ THE URL CASE MUST NOT ALSO SET locale IN profile-config. The two channels reach the // document-dir code by different routes and the whole point of the ?lang= case is to exercise the // URL one alone. Emitting all three files byte-identical (measured: 1114 bytes each) would hand a // reader a page that cannot distinguish them — and would let the config channel supply the locale // while the reader believed the URL had. const hostHtml = (c) => ` partner

Partner page