diff --git a/internal/check-embedded-rtl.mjs b/internal/check-embedded-rtl.mjs index a46b2cc3d..28ed3bf9f 100644 --- a/internal/check-embedded-rtl.mjs +++ b/internal/check-embedded-rtl.mjs @@ -3,17 +3,41 @@ * 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. + * ⚠ THE TWO DEFECTS BELOW WERE FIXED. This section is HISTORY, kept because it explains what the + * check asserts and why — not a description of the code today. Read it in the past tense; verified + * against main 2026-09-28. * - * 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). + * WHAT USED TO BE WRONG, both in one block (library/i18n/index.js, createAppI18n): + * 1. TIMING. It reflected 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 reached it, so Arabic text rendered inside a left-to-right layout. Measured at the time: + * htmlDir null on every host-channel run, including leaves whose chrome was fully Arabic. + * 2. TARGET. It wrote document.documentElement.dir unconditionally. An EMBEDDED widget doing that + * flips the partner's WHOLE PAGE. The comment of the day worried about the host ("a WordPress + * RTL theme owns that decision") but guarded only against CLEARING dir, not against setting it. + * + * HOW THEY WERE FIXED, and where to look before believing either is still live: + * - (2) library/i18n/index.js guards the documentElement write with + * `const ownsPage = window.MAP_OWNS_PAGE === true`, so only a page the plugin OWNS gets /. An embed cannot touch the partner's page. (lang travels with dir there: an + * owned page that flipped to rtl while keeping lang="en" had a screen reader speak Arabic in an + * English voice.) + * - (1) direction on the plugin's own root is ProfileLoader's job, not this block's: + * ProfileLoader.vue computes dirAttr from effectiveLocale and binds :dir="dirAttr" on + * .profile-loader (see its :208 and :285). effectiveLocale is `lang.value || appI18n locale || + * 'en'`, and lang.value IS the profile-config channel — so a host-supplied locale does flip the + * embedded root. + * + * ★ A STALE HEADER IS A TRAP, WHICH IS WHY THIS ONE IS DATED. Written in the present tense, this + * block read as a live defect long after both were fixed, and cost a reader a wrong bug report: + * the header was cited as current instead of the code being read. A gate's header is evidence of + * what WAS true when it was written. If you are about to act on one, check the source it names. + * + * SO WHAT THIS CHECK ASSERTS, which has not changed: 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). It reads .profile-loader inside the shadow root for the plugin's + * own direction, and records partnerHtmlDir separately, because for an embed that one must stay + * unset. * * ⚠ 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.