From a3729129b1d466cd366585d0239fe8b3adfa6cc7 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:37:47 +0000 Subject: [PATCH] poster cards: the fits gate sweeps LOCALES, not just widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Driver's requirement was "full words visible AND support localization". This gate only ever ran in English, which is the shortest of the eleven -- so the half of the requirement about translation had never been measured. MEASURED at 1440px, the three print labels: en Prayer List 76px · Card Deck 72px · Poster Map 79px ro Listă de rugăciune 122px · Pachet de cartonașe 133px · Harta afișului 92px zh 禱告名單 56px · 禱告卡組 56px · 海报地图 56px ★ THE WORST CASE IS RENDERED WIDTH, NOT STRING LENGTH, and picking by the wrong one would have picked the wrong locale. Chinese has the FEWEST characters of the three and is nowhere near the widest, because each glyph is near double-width; Romanian is +75% on English. The gate asserts scrollWidth > clientWidth, which is pixels, so the sweep is pixels too. en is the control, ro and zh the measured extremes; CARD_FIT_LANG overrides. MUTATION-TESTED, and the result is the whole argument for this change. Restoring the ellipsis the Driver rejected, at 1440px: en 0 clipped <- passes ro 54 clipped <- fails zh 0 clipped <- passes A regression that clips half the controls for Romanian readers would have shipped completely green under the English-only gate. Restore the wrap and all 15 combinations pass. Failures now name the locale ("ro 768px: 54 of 108 clipped"), because "768px" alone is a report nobody can reproduce. The PASS line reports widths x locales rather than their product -- it used to say "at 15 width(s)" for 5 widths in 3 languages, and a summary that overstates its own coverage is how a green line stops meaning anything. Also verified, separately from the sweep: a PARTIAL translation degrades per key. German renders translated (Gebetsliste, Kartenset, Posterkarte); an unknown locale falls back to English; both with 0 blank labels, 0 raw i18n keys visible and 0 clipped. --- internal/check-poster-card-fits.mjs | 39 +++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/internal/check-poster-card-fits.mjs b/internal/check-poster-card-fits.mjs index 6f8713b34..a18dc629d 100644 --- a/internal/check-poster-card-fits.mjs +++ b/internal/check-poster-card-fits.mjs @@ -32,6 +32,21 @@ const LEAF = process.argv[2] || DEFAULT_LEAF const WIDTHS = (process.env.CARD_FIT_WIDTH || '390,768,1024,1440,1920') .split(',').map((n) => Number(n.trim())).filter(Boolean) +// ★ AND SWEEP LOCALES, for exactly the same reason the widths are swept. The Driver's requirement +// was "full words visible AND support localization"; this gate only ever ran in English, which is +// the shortest of the eleven. MEASURED 2026-09-24 at 1440px, the three print labels: +// en Prayer List 76px · Card Deck 72px · Poster Map 79px +// ro Listă de rugăciune 122px · Pachet de cartonașe 133px · Harta afișului 92px (+75%) +// zh 禱告名單 56px · 禱告卡組 56px · 海报地图 56px +// Romanian is the widest RENDERED, which is the property that matters — not the longest string. +// Chinese is only 4 characters yet each glyph is near double-width, so a character-count heuristic +// would have picked the wrong worst case. The gate asserts scrollWidth > clientWidth, which is +// pixels, so the sweep has to be pixels too. +// en is always included as the control; ro and zh are the measured extremes. Override with +// CARD_FIT_LANG to check a specific locale. +const LANGS = (process.env.CARD_FIT_LANG || 'en,ro,zh') + .split(',').map((s) => s.trim()).filter(Boolean) + let reachable = false try { const r = await fetch(LEAF, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ } if (!reachable) { @@ -44,10 +59,15 @@ const WALK = `(root, out) => { out = out || []; const rec = (r) => { for (const const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader'] }) -async function measure(width) { +async function measure(width, lang) { const mobile = width < 500 const page = await browser.newPage({ viewport: { width, height: 1000 }, isMobile: mobile, hasTouch: mobile }) - await page.goto(LEAF, { waitUntil: 'load', timeout: 45000 }) + // ?lang= is how the app selects a locale (library/i18n/index.js:83). Appended rather than + // replaced so a caller can still pass a leaf URL that already carries query params. + const url = lang && lang !== 'en' + ? LEAF + (LEAF.includes('?') ? '&' : '?') + 'lang=' + encodeURIComponent(lang) + : LEAF + await page.goto(url, { waitUntil: 'load', timeout: 45000 }) await page.waitForTimeout(10000) // ★ HOVER IS INVISIBLE TO A STATIC PROBE, which is exactly how the blue outline stayed @@ -164,12 +184,12 @@ async function measure(width) { controls: ctrls.length, tiny: tiny.length } })()`) await page.close() - return { width, hover, ...out } + return { width, lang, hover, ...out } } const results = [] try { - for (const w of WIDTHS) results.push(await measure(w)) + for (const lang of LANGS) for (const w of WIDTHS) results.push(await measure(w, lang)) } finally { await browser.close() } @@ -177,7 +197,7 @@ try { console.log('POSTER-CARD-FITS') console.log(` leaf : ${LEAF}`) for (const r of results) { - console.log(` ${String(r.width).padStart(4)}px cards ${String(r.cards).padStart(2)}` + console.log(` ${(r.lang || 'en').padEnd(2)} ${String(r.width).padStart(4)}px cards ${String(r.cards).padStart(2)}` + ` · overflow ${r.overflowing}${r.worst ? ` (${r.worst}px)` : ''}` + ` · neighbour ${r.intrusions}` + ` · over-preview ${r.coveringPreview}${r.worstY ? ` (${r.worstY}px)` : ''}` @@ -193,7 +213,7 @@ for (const r of results) { const fails = [] for (const r of results) { - const at = `${r.width}px` + const at = `${r.lang || 'en'} ${r.width}px` // PRECONDITION: no cards means nothing was tested at this width — not a pass. if (!r.cards) { fails.push(`${at}: no poster cards rendered — wrong url, or the wall did not mount`); continue } if (!r.rows) { fails.push(`${at}: ${r.cards} card(s) but none carry a .pfs print-options row`); continue } @@ -274,6 +294,9 @@ if (fails.length) { for (const f of fails) console.error(' - ' + f) process.exit(1) } -console.log(`\nPOSTER-CARD-FITS: PASS — at ${results.length} width(s) (${WIDTHS.join(', ')}px) every ` +// Report widths x LOCALES, not the product. "15 width(s)" misdescribes 5 widths in 3 languages, +// and a summary that overstates its own coverage is how a green line stops meaning anything. +console.log(`\nPOSTER-CARD-FITS: PASS — ${WIDTHS.length} width(s) (${WIDTHS.join(', ')}px) ` + + `x ${LANGS.length} locale(s) (${LANGS.join(', ')}) = ${results.length} combination(s). Every ` + 'print-options row sits inside its own card and below its preview, with every control still ' - + 'big enough to press.') + + 'big enough to press and its label fully readable in every locale checked.')