packets: assert a people-group card actually NAMES someone
Some checks are pending
build-gate / build (push) Waiting to run

The gate counted .card elements and checked the photo/typographic split. A card
with a photo and a BLANK NAME satisfied all of it: the total held, the split
held, the page arithmetic held. Counting proves the element exists; the spec
says pages 2..n carry "the people groups, 20 per page, with photos", and a slot
that names nobody is not an entry in a prayer list.

★ MY FIRST VERSION FAILED ITS OWN MUTATION TEST, and the reason is the useful
part. It tested card.textContent. Blanking every name still left 18 characters
per card, because the typographic fallback ("no photo available") lives INSIDE
.card — so the card was not empty, it just said nothing about a people group.
The assertion passed a packet where all 95 names were gone.

Now it measures .nm, the name line the renderer writes (packet-render.js:229).
Same lesson as the nophoto count one level up, and as the consumersOf substring
test: match the thing you mean, not a container that happens to include it.

MEASURED on laos: 95 names, 0 blank, shortest "1. Aheu" — a threshold of 3 sits
far below any real entry and above an empty one.

MUTATION-TESTED against the mutation that defeated the first version: blanking
<div class="nm"> fails with "95 of 95 people-group card(s) have no NAME",
exit 1. Restored, and check:packets-all stays green across all five shapes.
This commit is contained in:
doxa-maps-88655c70 2026-09-25 02:51:39 +00:00
parent 1b08b3fcac
commit 8a3d836e44

View file

@ -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 <img> 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 ────────────────