poster cards: the fits gate sweeps LOCALES, not just widths
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
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.
This commit is contained in:
parent
6dc979c3ea
commit
a3729129b1
1 changed files with 31 additions and 8 deletions
|
|
@ -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.')
|
||||
|
|
|
|||
Loading…
Reference in a new issue