diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index f63216dd4..df8f27f6a 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -212,6 +212,22 @@ const r = await page.evaluate(() => { return { sheets: document.querySelectorAll('.sheet').length, cards: document.querySelectorAll('.card').length, + // ★ A CARD MUST NAME ITS PEOPLE GROUP. Counting .card proves the element exists; the spec says + // pages 2..n carry "the people groups, 20 per page, with photos" — a card with a photo and a + // BLANK NAME satisfies every count here: the total holds, the photo/typographic split holds, + // the page arithmetic holds. Same gap the .nophoto count had, one element up. + // ⚠ MEASURE THE NAME ELEMENT, NOT THE WHOLE CARD. A first version tested card.textContent and + // FAILED ITS OWN MUTATION TEST: blanking every name still left 18 characters per card, because + // the typographic fallback ("no photo available") lives INSIDE .card. The card was not empty; + // it just said nothing about a people group. .nm is the name line the spec means + // (packet-render.js:229), so that is what has to be non-empty. + // MEASURED 2026-09-25 on laos: 95 names, 0 blank, shortest "1. Aheu" — a threshold of 3 sits + // far below any real entry and above an empty one. + cardsBlank: [...document.querySelectorAll('.card')] + .filter((c) => { + const nm = c.querySelector('.nm') + return !nm || (nm.textContent || '').replace(/\s+/g, ' ').trim().length < 3 + }).length, imgs: imgs.length, // ⚠ DISTINCT sources, not the tag count. A PDF DE-DUPLICATES identical images, so the // number of /Subtype /Image objects can never equal the number of tags when two @@ -333,6 +349,12 @@ const pdfBytes = pdf.length const fails = [] if (!r.sheets) fails.push('no .sheet elements — the packet did not render') if (!r.cards) fails.push('no people-group cards') +if (r.cardsBlank) { + fails.push(`${r.cardsBlank} of ${r.cards} people-group card(s) have no NAME — a card that ` + + 'names nobody is a slot on the page, not an entry in a prayer list. The counts above cannot ' + + 'see this: the total, the photo split and the page arithmetic all hold, and so does ' + + 'card.textContent, because the "no photo available" fallback sits inside the card.') +} if (r.broken) fails.push(`${r.broken} broken photo(s)`) // ── "20 PER PAGE **WITH IMAGES**" WAS PRINTED, NEVER ASSERTED ────────────────