The deck gate asserts its portraits, and can finally run in every locale
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
Two gaps on the same surface.
1. "116 portraits" was collected, printed, and NEVER asserted — only the QR codes
were checked against the card count, so a deck of 116 cards with ZERO portraits
passed. A prayer card without a face is a data tile, which is the whole difference
between the deck and a spreadsheet. Not hypothetical: card-render.js records that
the deck once shipped with no photos at all, because image_url was never requested
in fetchListRecords.
THE COUNT CANNOT BE PINNED — roughly half the groups have no portrait and the CDN
drops some — so two invariants instead: portraits + typographic === cards (a card
that is NEITHER means a third shape nobody checks), and a deck drawn from records
carrying image_url must have SOME, with DECK_EXPECT_PORTRAITS=0 to declare a
deliberately photo-less deck out loud.
2. The gate matched /card deck/i, so it could ONLY run in English. Pointed at
?lang=es it reported "no Card Deck button" and listed 28 buttons all reading
"Baraja de tarjetas" — the UI correctly translated, the GATE unable to see it. The
card deck is a core prayer artefact and had zero coverage in any of the ten
non-English locales, which is exactly where a rendering bug is most likely. The
label now resolves from the catalog for the page's own locale.
MUTATION-TESTED THROUGH THE REAL CAUSAL PATH, not synthetic DOM edits:
- image_url removed from the records provider (the historical regression):
FAIL, "116 cards and NOT ONE portrait", naming the cause.
- the no-photo branch renamed so cards are NEITHER shape:
FAIL, "116 cards but 0 portrait + 0 typographic = 0".
- DECK_EXPECT_PORTRAITS=0 on that same deck: PASS, sum invariant still holding.
- es cardDeck changed in the CATALOG without rebuilding: FAIL — so this now also
catches catalog/bundle drift instead of passing on a stale bundle.
VERIFIED IN FOUR LOCALES, each matching its OWN label — en "Card Deck",
es "Baraja de tarjetas", ar "مجموعة البطاقات", hi "कार्ड सेट" — 116 cards, 116
portraits, 116 QR codes, @page 2.5in x 3.5in, 0 page errors in every one.
Same two patterns have now recurred three times: a count printed without an
assertion, and a gate keyed on English text that silently excludes every
translated surface. Worth checking for on any new gate.
This commit is contained in:
parent
aff07bc234
commit
53ae954a7f
1 changed files with 51 additions and 2 deletions
|
|
@ -14,12 +14,31 @@
|
|||
//
|
||||
// Usage: node internal/check-deck-click.mjs <wallUrl> [stockId] [expectedPage]
|
||||
import { chromium } from 'playwright'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const URL_ = process.argv[2]
|
||||
if (!URL_) { console.error('usage: check-deck-click.mjs <wallUrl> [stock] [expectedPage]'); process.exit(2) }
|
||||
const STOCK = process.argv[3] || ''
|
||||
const EXPECT_PAGE = process.argv[4] || (STOCK === 'pokerLandscape' ? '3.5in 2.5in' : '2.5in 3.5in')
|
||||
|
||||
// ★ THE LABEL COMES FROM THE CATALOG FOR THIS PAGE'S LOCALE, never a hardcoded
|
||||
// English string. This matched /card deck/i, so it could only ever run in English:
|
||||
// pointed at ?lang=es it reported "no Card Deck button" and listed 28 buttons all
|
||||
// reading "Baraja de tarjetas" — the UI correctly translated, the GATE unable to
|
||||
// see it. The card deck is a core prayer artefact and it had ZERO coverage in the
|
||||
// ten locales where a rendering bug is most likely.
|
||||
const REPO = process.env.DOXA_REPO || resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const localeOf = (u) => { try { return new URL(u).searchParams.get('lang') || 'en' } catch { return 'en' } }
|
||||
const deckLabel = (loc) => {
|
||||
const f = join(REPO, 'library/i18n/locales', loc, 'buttons.json')
|
||||
if (!existsSync(f)) return null
|
||||
try { return JSON.parse(readFileSync(f, 'utf8')).cardDeck || null } catch { return null }
|
||||
}
|
||||
const LOC = localeOf(URL_)
|
||||
const WANT_DECK = deckLabel(LOC) || deckLabel('en') || 'Card Deck'
|
||||
|
||||
const b = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader'] })
|
||||
const pg = await b.newPage({ viewport: { width: 1280, height: 1000 } })
|
||||
const errs = []
|
||||
|
|
@ -27,6 +46,8 @@ pg.on('pageerror', (e) => errs.push(String(e).slice(0, 140)))
|
|||
await pg.goto(URL_, { waitUntil: 'load', timeout: 45000 })
|
||||
await pg.waitForTimeout(9000)
|
||||
|
||||
await pg.evaluate((want) => { window.__WANT_DECK = want }, WANT_DECK)
|
||||
|
||||
await pg.evaluate(() => {
|
||||
window.__printed = ''
|
||||
const real = document.createElement.bind(document)
|
||||
|
|
@ -50,7 +71,7 @@ const r = await pg.evaluate(async (stock) => {
|
|||
if (!host) return { error: 'no poster wall found on this page' }
|
||||
const sr = host.shadowRoot
|
||||
const btns = [...sr.querySelectorAll('.pbtn')]
|
||||
const deck = btns.find((x) => /card deck/i.test(x.textContent))
|
||||
const deck = btns.find((x) => (x.textContent || '').trim().includes(window.__WANT_DECK))
|
||||
if (!deck) return { error: 'no Card Deck button', buttons: btns.map((x) => x.textContent.trim()) }
|
||||
if (stock) {
|
||||
const sel = sr.querySelector('.pstock')
|
||||
|
|
@ -66,6 +87,7 @@ const r = await pg.evaluate(async (stock) => {
|
|||
cards: doc.querySelectorAll('.card').length,
|
||||
qrs: doc.querySelectorAll('.qr svg').length,
|
||||
imgs: doc.querySelectorAll('img.ph').length,
|
||||
noph: doc.querySelectorAll('.ph-none').length,
|
||||
page: (window.__printed.match(/@page\{size:([^;}]*)/) || [])[1] || '',
|
||||
}
|
||||
}, STOCK)
|
||||
|
|
@ -84,10 +106,37 @@ if (r.page.trim() !== EXPECT_PAGE) {
|
|||
+ (STOCK ? ' — the stock picker is not reaching the renderer' : ''))
|
||||
}
|
||||
if (r.qrs !== r.cards) fails.push(`${r.qrs} QR code(s) for ${r.cards} cards`)
|
||||
|
||||
// ── "116 PORTRAITS" WAS PRINTED AND NEVER ASSERTED ──────────────────────────
|
||||
// A prayer card without a face is a data tile. This gate collected the count,
|
||||
// displayed it, and checked only the QR codes — so a deck of 116 cards with ZERO
|
||||
// portraits passed. That is not hypothetical: card-render.js records that the deck
|
||||
// once shipped with no photos at all, because image_url was never requested in
|
||||
// fetchListRecords (measured 95/95 groups DO have a portrait, so the omission cost
|
||||
// every card its picture).
|
||||
//
|
||||
// ⚠ THE COUNT CANNOT BE PINNED, for the same reason the packet's could not: roughly
|
||||
// half the people groups have no portrait, and the CDN drops some. So two invariants
|
||||
// instead of a number:
|
||||
// 1. every card is one shape or the other — portraits + typographic === cards.
|
||||
// A card that is NEITHER means the renderer emitted a third nobody checks.
|
||||
// 2. a deck drawn from records that carry image_url must have SOME portraits.
|
||||
// DECK_EXPECT_PORTRAITS=0 declares a deliberately photo-less deck out loud,
|
||||
// rather than leaving "none" indistinguishable from "silently none".
|
||||
if (r.cards && r.imgs + r.noph !== r.cards) {
|
||||
fails.push(`${r.cards} cards but ${r.imgs} portrait + ${r.noph} typographic = ${r.imgs + r.noph}`
|
||||
+ ' — every card must be exactly one of the two')
|
||||
}
|
||||
if (r.cards && process.env.DECK_EXPECT_PORTRAITS !== '0' && r.imgs === 0) {
|
||||
fails.push(`${r.cards} cards and NOT ONE portrait — a prayer card without a face is a data tile. `
|
||||
+ 'This is what a missing image_url in the records provider looks like; set '
|
||||
+ 'DECK_EXPECT_PORTRAITS=0 to declare a deliberately photo-less deck.')
|
||||
}
|
||||
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
|
||||
|
||||
console.log(` locale : ${LOC} (matched ${JSON.stringify(WANT_DECK)})`)
|
||||
console.log(` stock : ${STOCK || '(default)'}`)
|
||||
console.log(` cards printed : ${r.cards} (${r.imgs} portraits, ${r.qrs} QR codes)`)
|
||||
console.log(` cards printed : ${r.cards} (${r.imgs} portraits, ${r.noph} typographic, ${r.qrs} QR codes)`)
|
||||
console.log(` @page size : ${r.page.trim()} (want ${EXPECT_PAGE})`)
|
||||
console.log(` page errors : ${errs.length}`)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue