17 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
59f497eb67 |
Six more browser gates measure THIS build or nothing
|
||
|
|
8a3d836e44 |
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.
|
||
|
|
51a3abe482 |
packets: render the MIXED photo case, and assert the fallback says something
Some checks are pending
build-gate / build (push) Waiting to run
Every packet ever produced was all-photo (laos: 95 photos, 0 typographic) or
all-typographic (every fixture: 0 photos, N typographic). THE MIXED CASE HAD
NEVER BEEN RENDERED — and it is the realistic one. fetchPhotos already degrades
per-photo on a failed fetch, so any zone where a few images 404 puts exactly
that layout in front of a reader.
PHOTOS_EVERY=N keeps only every Nth photo, deterministic by INDEX rather than
random so the same command always yields the same packet — a fixture that
differs run to run cannot be a gate. The build stamp gains `mixedphotos`,
because a deliberately mixed packet and a partly-failed photo fetch deserve
opposite verdicts and nothing could tell them apart.
★ IT IS ALSO AN I18N SURFACE NOBODY HAD SEEN. The fallback card renders
t('no_photo'), translated in all 11 catalogs, and the gate COUNTED .nophoto
elements without ever looking inside one. A blank grey box passed every
assertion: `photos + typographic === cards` held, the page count held, nothing
was broken. Counting proves the element exists; it says nothing about whether
the reader sees a label or a hole.
MEASURED on the first mixed packet (PHOTOS_EVERY=3 over laos, 31 photos and 64
typographic): all 64 read "no photo available", 0 blank, all 64 with a real box.
In Spanish "sin fotografía disponible", in Arabic "لا توجد صورة متاحة" — 63 and
64 cards, 0 blank in both.
MUTATION-TESTED: emptying the fallback to `<div class="nophoto"></div>` fails
the gate with "63 of 63 typographic card(s) render an EMPTY box", exit 1.
Restored, and check:packets-all stays green across all five shapes.
One trap worth recording: the language variable is PACKET_LANG, not LANG. The
file warns about it — process.env.LANG is the shell's POSIX locale
("en_US.UTF-8") — and I still lost a run to it, regenerating three full-photo
packets that proved nothing.
|
||
|
|
3c17245c73 |
packets: give CI something to check, instead of a sweep that always SKIPs
Some checks are pending
build-gate / build (push) Waiting to run
check:packets-all is in gates:post. CI runs it on every build. It has SKIPped every single time, because library/scripts/posters/out/ is gitignored — both the records cache and every generated packet — so a clean checkout has none. Every packet assertion in this tree has therefore never once been evaluated there: the 12 prayer points, the wheel's 12 labels, page 1's geography paths, the print layer, the build stamp. Several ticks of work accumulating on a gate that had only ever graded files in my own working tree. A SKIP that is always taken is indistinguishable from an unwired gate — and the wave-membership census cannot tell them apart, because membership measures invocation, not evaluation. That census is how check:packets-all was created in the first place, which makes this the same class recurring one level up. I raised this at the end of the last tick and deferred it on the premise that closing it "adds a live-API call to every build". The premise was wrong. packet.mjs already had an OFFLINE mode, and a fixture needs no network at all. The fixture is the smallest zone — Tibet, 43 slugs, 20KB — with image_url stripped so CI can never fetch 43 photos from someone else's CDN. It is DERIVED, not hand-maintained: membership comes from the committed clusters.json, so check:packet-fixture regenerates and content-compares it. Re-adding an image_url fails; dropping a record fails naming the slug. A fixture packet stamps itself "fixture", and "fixture nomap" in CI, which has no poster sources either. `fixture` implies nophotos in the gate — without that it reported "every source record carries an image_url", a true sentence about real records and a false one about this packet. A real bug in my own change, caught only by running the script through its REAL invocation path: packet.mjs fails closed on a missing VITE_API_BASE_URL BEFORE loadRecords() ever looks at PACKET_RECORDS, so `bun run packet:ci` exited 1 on a tree that had everything it needed. Every hand-test had passed because I was supplying a dummy host. The guard now exempts fixture builds and still fails closed otherwise. From a genuinely clean state — the whole out/ directory moved away — the sweep now reports "1 packet(s) checked, 1 passed". i18n verified through that same path: a partial block (1 string, 5/12 points, 3/12 rhythms) renders 12 points, 12/12 wheel labels, wheel 3 translated and 9 English, 0 blanks. check:packet-fixture joins gates:post (34 -> 35), README quote re-synced in the same commit. Wave 35/35, packets-all 11/11 here and 2/2 in the studio. |
||
|
|
64fed43449 |
packet: stamp how a packet was built, into the packet
Some checks are pending
build-gate / build (push) Waiting to run
out/packets/ is gitignored, so check:packets-all grades whatever .html files happen to be sitting there — an accidental set, not a current one. On this machine that was 11 freshly generated packets. In doxa-maps-plugin-studio, which shares main with this worktree, it was two NOPHOTOS=1 layout probes of mine from earlier ticks, and the sweep was RED there for anyone who ran the wave. The gate was honest about each file it checked; the set it checked was accidental. The underlying problem is that a fast layout-iteration build and a shippable one were byte-indistinguishable. The packet carried lang and dir and nothing about its own provenance, so the only way to tell them apart was for the person running the gate to remember and pass PACKET_EXPECT_PHOTOS=0. packet.mjs now stamps data-packet-build with the flags it generated under, and the gate reads it. The GENERATOR writes it rather than the gate inferring it: had the gate guessed "no photos present, so probably a probe", it would have excused genuine photo-fetch failures. Tested explicitly — a packet stamped "full" whose map svg is then stripped STILL FAILS. The stamp records intent and cannot excuse a defect the build did not declare. Deliberately not a timestamp. An mtime staleness rule was considered and rejected on evidence from this same session: packet-strings.json was 303s newer than the packets while byte-identical to HEAD, because a probe had restored it from backup, so an mtime rule would have failed a correct tree — the exact mistake fixed two commits ago in the library-index gate. Content comparison is not available here (regenerating hits a live API and takes minutes), so the build records its intent instead of the clock. The studio sweep goes from "2 checked, 0 passed" to "1 checked, 1 passed", with the packet self-declaring build="nophotos nomap". Env vars stay as an override for pre-stamp artefacts, and the declared-deliberate note now names whichever source actually declared it rather than always crediting the env var. All 11 locales stamp "full". i18n re-verified with a partial block (1 string, 3/12 points, 6/12 rhythms): wheel 6 translated and 6 English, 12 point rows with 6 translated, 0 blanks. Wave 34/34, packets-all 11/11 here and 1/1 in the studio. |
||
|
|
588d0da48a |
packet gate: look inside the PDF it was already making
Some checks are pending
build-gate / build (push) Waiting to run
check-packet-render has called page.pdf() for a long time and then read only
MediaBox and /Type /Page. Eight blank sheets of the right size would have passed
every assertion in it.
That matters because the packet exists to be PRINTED — it is a standalone file
with photos and fonts inlined precisely so it prints offline — and until now
every check was on-screen. A print-only CSS rule can strip content that the
on-screen checks still see.
The first mutation is the argument for the whole change. Adding
`@media print{.card img{display:none}}` left the on-screen line reading
"95 photos, 0 typographic" — every pre-existing check green — while the PDF
carried 2 images instead of 85. The second, hiding sheet contents in print,
fires all three new assertions at once on a packet that is still 8 correctly
sized pages.
Images are compared against DISTINCT srcs, not cards. A PDF de-duplicates
identical images, and laos has 95 cards and 95 <img> but only 85 distinct
sources, because ten groups share a portrait — a fact about the upstream photo
set, not a rendering fault. Comparing against 95 would have reported "8 photos
dropped at print time" on a correct packet. I nearly did exactly that.
A scare investigated and cleared rather than reported: the PDF names only
DejaVuSerif, which looked like the vendored Devanagari face failing to reach
print. It is not. Under print media emulation the stack still resolves to
"Noto Serif Devanagari", and the same string measures 210px in it against 420px
in plain serif, so the face is genuinely in use and the PDF names are Chromium
subset aliases. Corroborated independently: hi is the largest packet at 3.2MB
with the fewest faces at 7, which is one big vendored face, not a fallback. A
ToUnicode-based check was discarded after calibration found only 30 Latin
codepoints in a known-good English PDF — an incomplete parser whose
zero-Devanagari result proved nothing.
All 11 locales embed fonts and carry every photo. i18n re-verified through the
new assertions with a partial block (2 strings, 4/12 points, 5/12 rhythms):
2.8MB, 11 fonts, 87 images, wheel 5 translated and 7 English, 12 point rows,
0 blanks. The studio's declared map-less photo-less packet still passes — the
image assertion skips at 0 distinct sources, so the opt-outs compose. Wave 34/34.
|
||
|
|
d561e35d33 |
packet gate: a map one-pager with no map on it is not a map one-pager
Some checks are pending
build-gate / build (push) Waiting to run
Deleting the map svg from page 1 entirely left this gate reporting
"map: palette resolves, not black — PASS, map painted".
mapFaults queried `document.querySelector('.sheet svg')` — the first svg
ANYWHERE in the document. With page 1 empty the query silently retargeted to the
PRAYER WHEEL's svg two sheets later, and the wheel has no CSS variables to leave
unresolved and no black fills, so both checks passed vacuously. The sheet count
stayed 8 because the empty sheet still exists.
A fallback selector turns an absence into a pass: when the thing being asserted
about disappears, the query finds a different object that happens to satisfy the
checks — and here the gate printed a stronger claim than it had evidence for.
"page 1 = map one-pager" is the first line of the packet spec.
Now scoped to sheet 0, which the page order fixes as the map page. And it
asserts the map paints GEOGRAPHY, because palette-resolves and not-black are
both properties a map with zero country shapes satisfies perfectly. Geography is
paths whose `d` exceeds 200 chars — a coastline, not an icon. The floor of 5 is
set from a measured spread rather than guessed: laos 13, brazil 18,
middle-east-core 55, tibet 69, uncovered-scatter 74, varying with how many
countries a zone spans.
Mutation-tested in three shapes: no svg fails naming page 1; an svg present but
drawing nothing fails at "only 0 geography path(s)"; the good packet passes at
13. All 11 locales report 13 — the map is data, not text.
Syncing the gate surfaced a second thing. doxa-maps-plugin-studio ships 0 poster
html files against wt-packet's 28, and packet.mjs deliberately "still builds
(cover page without the map) rather than failing" when out/posters/<zone>.html is
absent. That soft-fail is intentional but it was invisible: the degraded packet
has 8 sheets, an <svg> present (the wheel's), and no marker at all, so it is
structurally indistinguishable from a good one while page 1 promises a map and
delivers an empty sheet. PACKET_EXPECT_MAP=0 follows the PACKET_EXPECT_PHOTOS
precedent — fail by default with the remedy named, pass when the degradation is
declared.
Also fixed the summary line, which read "map painted" unconditionally and so
described a declared map-less packet as having a painted map. It now reads
"NO map (declared)". The summary is the part a reader quotes.
i18n verified with a 2-key partial block: page 1 renders the translated keys,
falls back to English elsewhere, 0 blank runs. Wave 34/34, packets-all 11/11.
|
||
|
|
53fea6ee38 |
packet gate: a prayer wheel with no words on it is not a prayer wheel
Some checks are pending
build-gate / build (push) Waiting to run
Blanking every wheel segment label and regenerating left check:packet-render reporting "12 segments, 11/11 rhythms, content intact — PASS" on a wheel that was a twelve-colour pie chart. Both printed numbers stayed true, which is why nothing caught it. The twelve segments are <path> elements and survive their labels being removed. And the rhythm assertion searched document.body.innerText, where the SAME twelve rhythm words are rendered TWICE in every packet — once per wheel segment, once as a prayer-point heading. So "every rhythm word appears in the document" was satisfied by the points page alone and said nothing about the wheel. That is the self-consistency-vs-completeness family again, with a new twist: when two surfaces share one vocabulary, a document-wide string search cannot attribute a hit to the surface being asserted. The fix is scoping the query to the surface, not a better word list. wheelLabels counts the non-numeric <text> runs inside the .wheel-title sheet's own svg, excluding the twelve "5" pips and the "5 minutes x 12" footer. It counts ELEMENTS, NOT DISTINCT WORDS, deliberately: `praise` and `praise_end` collapse to one word in English — which is exactly why the existing line reads 11/11 and not 12/12 — and that collision is locale-dependent, so a distinct-word count would be a different number per language. The Driver's spec names the wheel as one of the packet's four sections, so 12 labels is a contract. Mutated packet reports 0/12 and fails; restored reports 12/12. i18n verified at the rendered layer with a partial locale block (4 of 12 rhythms translated): the wheel painted four XX- labels with the other eight falling back to English and zero blank — and the split pair is visible, segment 1 English PRAISE beside segment 12 XX-PRAISE_END, which is what per-key resolution looks like. All 11 locales regenerated with 95 photos each: check:packets-all 11/11, every one 12/12 labelled, including RTL ar and Devanagari hi. Wave 34/34. |
||
|
|
e8ad1d56e6 |
packet: a partial translation renders 12 prayer points, not 3
Some checks are pending
build-gate / build (push) Waiting to run
packet-render.js resolved the prayer points with `L.points || EN.points`. That is a whole-block fallback when the key is ABSENT, and TRUNCATING when a translator has done some of the 12 — a 3-element array is truthy. Measured at the rendered layer with a deliberately partial locale block: a page headed "12 Prayer Points" containing THREE, points 4..12 silently gone, including the Mark 5:1-20 passage point 4 prints in full. Nothing blank, no error raised. Invisible in all 11 shipping locales because every one of them is complete. It would have appeared on the next language added — and "a new language is a NEW BLOCK, never a code change" only holds if a PARTIAL block degrades per key, since half-finished is the normal state of a language in progress. Now merges per point and per field against English: English fixes the LENGTH and supplies every missing field, so a point translated for `text` but not `sub` keeps the English `sub` — and keeps its NUMBER, which the merge supplies. The sheet-sum invariant could not see this: a 3-point page is still exactly one sheet, so every number check-packet-render printed stayed correct while nine points were missing. It now measures and asserts the count. Mutation-tested — reverting the fix prints "prayer points : 2 / 12" and fails. All 11 locales regenerated with photos and swept: check:packets-all 11/11, each 12/12 points, including RTL ar and Devanagari hi. Renderer, generator and gate synced to doxa-maps-plugin-studio, which also lacked the .points-title and .wheel-title test hooks the new assertion needs. Also wires check:capability-exemplars into gates:post (33 -> 34 steps). It reads public/doxa-maps/manifest.json, a build artifact, so ordering mattered: `tree` writes the manifest and is &&-chained immediately before gates:post, and the gate SKIPs rather than fails when the manifest is absent. Full wave: 34/34. The quoted gates:post line in the README is re-synced in the same commit, because check:quoted-scripts goes red on the drift this commit creates. |
||
|
|
18db274d72 |
The packet gate asserts RTL LAYOUT, not just the dir attribute
Some checks are pending
build-gate / build (push) Waiting to run
Arabic is one of the eleven locales, and RTL is the one layout property that can be
declared correctly and still not take effect. The gate asserted
document.documentElement.dir === 'rtl' — necessary, and not sufficient. Any inner
rule can override it while every other assertion here stays green, and an Arabic
packet with readable glyphs in LTR order reads as a styling quirk rather than the
bug it is.
It now samples up to 25 leaf text runs inside .sheet and measures the painted
extent against the element box. An RTL packet must pin its runs to the RIGHT edge,
an LTR one to the LEFT. The signal is unambiguous: en 0 right / 25 left,
ar 25 right / 0 left. Threshold is 80% of sampled runs, so one legitimately mixed
line cannot trip it.
★ MEASURE THE INLINE TEXT RUN, NOT THE BLOCK. My first probe compared each
element's box to its parent's and returned IDENTICAL numbers for Arabic and
English — it was measuring container geometry, which does not move when direction
flips, and would have "passed" an RTL packet laid out LTR. A Range over the text
node gives the painted extent, which does move. Centred text is skipped because it
proves nothing either way, and so is text that fills its box — no room to tell.
MUTATION-TESTED with the exact failure this guards: injected .sheet{direction:ltr}
so dir="rtl" REMAINS DECLARED on <html> while the layout reverts. FAIL, both
symptoms named — "only 0/25 sampled runs do (right 0, left 25) — dir=\"rtl\" is
declared and not taking effect", and "25/25 sampled elements compute
direction:ltr". All 11 locale packets pass with it live.
Verified a NEW RTL LANGUAGE end to end: Urdu added as a DATA BLOCK ONLY — 2 of 21
keys, no code change — renders dir=rtl with 25/25 runs painted right, 95 photos,
translated keys in Urdu, untranslated falling back to English, zero blank headings.
The packet's i18n contract holding for a language that did not exist when the tick
began.
|
||
|
|
6732c8d430 |
Guard the packet catalog: the 12 prayer points and the wheel had no parity gate
check:i18n-parity covers library/i18n/locales — the APP catalogs. Nothing covered library/scripts/posters/packet-strings.json, which is a separate file with its own shape and is where the 12 PRAYER POINTS and the PRAYER WHEEL live. SIX gates read it and not one checked that a language block is complete. All ten locales happened to be healthy — 19/21 string keys, 12 points with every field, 12 rhythms — but nothing held them there. A point dropped from one block renders that slot through the per-key English fallback: correct runtime behaviour, invisible defect, on a page the reader is meant to pray through. ★ IT ASSERTS PRESENCE, NOT TRANSLATEDNESS, and that is the load-bearing choice. check-packet-render printed "19 strings translated" and only ever asserted `!== 0`, so a locale regressing from 19 to 1 passed silently. The fix is NOT to pin 19: measured, de translates 18 of the same 19 keys and fr 17, because a word or two legitimately matches English — a hardcoded count fails two CORRECT catalogs. So parity asserts every English key EXISTS in every block, a structural fact no translation choice can change, and the render gate gains a FLOOR of two-thirds derived from the catalog itself. point4_passage + point4_passage_credit are DECLARED-ABSENT with the reason on record (Mark 5:1-20, public-domain licensing per language). Naming the exception beats tolerating it silently: the day a licensed translation lands, deleting the entry tightens the gate by itself. MUTATION-TESTED, five defect classes, each caught with a precise message: a dropped string key, an 11th-of-12 prayer point, an EMPTY point text, a missing wheel rhythm, and a block reverted wholesale to English. The floor was tested separately by reverting de to 3 translated of 19 — "only 3 differ from English (floor 13)". ★ THE TWO GATES ANSWER DIFFERENT QUESTIONS, and a partial language shows both at once. Swahili at 3 of 21 keys, no points, no rhythms: check-packet-render PASSES — a partial translation renders, Swahili where translated, English where not, zero blank slots — while check:packet-strings-parity FAILS and names exactly what is missing, including that the points and wheel would fall back wholesale. Before this, only the first question had a gate, so a half-finished block was indistinguishable from a complete one at build time. gates:post is now 22 steps; 11/11 packets still pass the sweep. |
||
|
|
95b5586cc5 |
Assert the packet photos the spec names — "20 per page WITH IMAGES"
Some checks are pending
build-gate / build (push) Waiting to run
check-packet-render printed "95 cards (0 photos, 95 typographic)" and PASSED, in
every locale. The packet spec names the photos explicitly, so that was the last
unasserted number on this surface.
The cause was my own fixtures, not the product: I regenerated the packets with
NOPHOTOS=1 two ticks ago for speed. Regenerating properly gives 95 photos, 3.5MB,
0 broken. But a gate that passes a packet violating a core spec line is the defect
whoever made the artefact, and this one would not have noticed a real photo-fetch
failure either.
★ THE COUNT CANNOT BE PINNED, so this is two invariants rather than "=== 95".
Roughly half the groups genuinely have no photo, the CDN drops some — de came back
94/95 on this very run, which is exactly why a hard count would have been wrong —
and NOPHOTOS=1 is a SUPPORTED dev mode for fast layout iteration. So:
1. photos + typographic === cards. Every card is exactly one shape; a card that
is neither means the renderer emitted a third nobody is checking.
2. a photo-enabled packet must have SOME photos. Zero is acceptable only when the
run declares itself photo-less via PACKET_EXPECT_PHOTOS=0 — "deliberately
none" said out loud, rather than indistinguishable from "silently none".
MUTATION-TESTED BOTH. Removing a single .card img yields "95 cards but 94 photo +
0 typographic = 94". The photo-less packet FAILS by default and PASSES under the
opt-out. (My first attempt at the sum mutation silently did nothing — the card
blocks are 11,340 chars and I edited a 1,200-char window — so the assertion only
counts as tested because the second attempt targeted the real .card img.)
All 11 locales regenerated WITH photos and re-swept: 11/11 pass. i18n re-verified
end to end — a partial Swahili block, 3 of 23 keys, added as DATA ONLY with no code
change: translated keys render in Swahili, untranslated fall back per key to
English, zero blank slots.
|
||
|
|
e952e01e4c |
Assert the packet numbers that were only being printed, and run the gate on every locale
check-packet-render's own header states the law it exists for — "never print a number
without asserting on it" — and two of the nine numbers it printed were decoration.
PAGE SIZE. Printed "612 x 792 pt", never compared. The landscape check only catches
h <= w, so a wrong PORTRAIT size sailed through: A4 is 595x842, taller-than-wide like
Letter, and would have passed on a packet printing with wrong margins on every US
printer. Not hypothetical — @page{size:...} silently falls back in Chromium, which this
repo has already paid for once. Now asserts every page's MediaBox against Letter.
CARD COUNT. `if (!r.cards)` passed a packet carrying ONE card. Counts must stay relative
(a per-zone packet has its own group count), but relative does not mean unasserted:
sheets === 1 + ceil(cards/PER_PAGE) + 2 holds for any zone.
★ That was not enough, and the mutation test is what showed it. Deleting a whole list
sheet removed 15 cards AND one sheet, so the invariant still held — 80 cards / 7 sheets
is valid arithmetic — and the packet PASSED while missing 15 people groups. Internal
consistency is not completeness. So it now cross-checks an INDEPENDENT witness: the
{from}-{to} of {count} header the renderer prints on each list page, where count comes
from groups.length and does not move when a page is lost. It also requires the ranges to
TILE [1..count], which catches a missing MIDDLE page that the totals alone cannot see.
Those headers are TRANSLATED and the languages disagree on order — en/ar read "1-20 of
95" while hi reads "95 में से 1–20" and zh "95 之中的 1–20", total FIRST. A positional
regex would read those backwards. Parsed as digits with max() as the total instead, and
locale digit forms normalised to ASCII, so it is grammar-independent. Verified by
running the missing-middle-page mutation in Hindi as well as English.
MARK 5 WAS ASSERTED BY AN ENGLISH WORD. body.includes('Gadarenes') would go RED on a
correctly-translated packet, and passes today only because every locale falls back to
English for that key — the check and the bug were locked together. The renderer now
emits class="p4-passage"/"p4-credit" as test hooks (the block is styled inline and had
no selector at all), and the gate asserts length >= 1200, which holds in any language.
check:packets-all runs the gate over EVERY generated packet rather than one hand-named
file. The single-file gate SKIPs without an argument, so it was in package.json, passing
in all 11 locales, and in NEITHER gates:pre NOR gates:post — it ran only when someone
typed a filename. Now in gates:post (21/21, 11 packets through a real browser). SKIPs
when no packets are generated, so a clean checkout is unaffected.
Mutation-tested: A4 FAILs, a dropped sheet FAILs, a missing middle page FAILs in en and
hi, a truncated passage FAILs, a removed passage FAILs. i18n verified end to end by
adding a PARTIAL new language as a data block only — 3 of 23 keys — and confirming the
translated strings render, untranslated keys fall back to English, and nothing is blank.
|
||
|
|
a9214695f4 |
test(packet): a declared @font-face must actually load
Some checks are pending
build-gate / build (push) Waiting to run
Some languages need a face the system stack does not carry — Hindi renders as
tofu boxes in Georgia/Palatino/serif. When the packet declares one, a failed
@font-face is SILENT: the text is present, the strings compare equal, every
other assertion stays green, and the page prints as empty rectangles.
The gate now asserts every declared face reaches status "loaded". Confirmed to
fire before being accepted: corrupting the inlined base64 reports
'Noto Serif Devanagari:error — text in that script will print as empty boxes'.
★ THIS ASSERTS THE MECHANICAL HALF ONLY, deliberately. A general tofu detector
was attempted twice and abandoned, and the attempts are worth recording:
- comparing shaped width against a deliberately-missing font FALSE-POSITIVES on
every system-covered script, because Cyrillic, CJK and Arabic legitimately
measure the same in both stacks when both resolve to the same system font;
- an ink-column-profile heuristic scored CORRECTLY RENDERED Devanagari the same
as tofu (3 vs 2 distinct column heights), because its baseline bar gives a
uniform profile.
Both would have blocked correct Arabic, Chinese, Russian and Hindi packets. A
detector that fails working output is worse than no detector, so glyph
correctness stays a human reading a screenshot, and the gate covers what it can
check without lying.
|
||
|
|
47c54b12ac |
test(packet): assert RTL direction, not just translated strings
Some checks are pending
build-gate / build (push) Waiting to run
An Arabic packet without dir="rtl" renders readable glyphs LEFT-aligned. Every other assertion in this gate passes on that: the strings are translated, the rhythms are present, the pages paginate. It reads as a styling quirk rather than the bug it is, and nothing here would have caught it. The gate now checks the rendered <html dir> against an RTL language set, in both directions — an RTL language must be dir="rtl", and an LTR one must not be. Confirmed to fire before being accepted: an Arabic packet relabelled dir="ltr" reports 'lang="ar" is right-to-left but dir="ltr"'. |
||
|
|
cd5e5f5590 |
test(packet): verify the packet in ITS OWN language, not in English
Some checks are pending
build-gate / build (push) Waiting to run
The gate asserted a hardcoded English word list for the prayer rhythms — which tests the opposite of what i18n means. It passed a packet whose translation silently did nothing, and FAILED every correctly translated one: the Spanish packet reported "only 0/11 prayer rhythms present" while rendering all twelve correctly as ALABANZA / ESPERAR / CONFESAR. Expected words now come from the strings catalog for the packet's OWN language, resolved per key with English fallback — the same contract the renderer honours. So the gate asserts the PROPERTY (all rhythms present, in this language) rather than a vocabulary. The catalog path is derived from the packet's own location instead of a machine-specific constant, with PACKET_STRINGS to override. Adds the assertion the tick exposed: a non-English packet must ACTUALLY DIFFER from English. That failure is invisible to every other check, because an all-English packet is a perfectly valid packet — a mis-keyed block, a block under the wrong ISO code, or a renderer ignoring its lang argument all produce one. Confirmed to fire before being accepted: a packet relabelled lang="de" with no de block reports "lang=de but no string differs from English". |
||
|
|
4d9ac89f6e |
test(packet): gate the prayer packet at the RENDERED layer
Some checks are pending
build-gate / build (push) Waiting to run
Every bug this gate exists for produced a file of the right size, with no
error, no broken image, and a generator reporting success. They were only
visible once a browser laid the document out, so this drives a real browser
and reads computed style rather than inspecting the HTML source.
★ The law it encodes: never print a number without asserting on it. The
throwaway probe this replaces PRINTED "PDF pages: 16" for several runs while
reporting "packet verified", because the failure counter never looked at that
value. A number that is displayed but not asserted on is decoration.
Two regressions it catches, both confirmed to FAIL before this was accepted:
transform instead of zoom -> 16 PDF pages for 8 sheets
map stylesheet removed -> unresolved --paper/--sea/--frameline;
660/661 map elements fill BLACK
The second is the subtler one. The map SVG carries almost no presentation
attributes; it is styled from the poster document's stylesheet. Lift the
<svg> without those rules and SVG fill falls back to its initial value, which
is black — the whole map paints as a solid rectangle with nothing to see in
the DOM, no console error, and no broken image. It is asserted as a
WHOLE-MAP property because the first attempt tested one hand-picked <rect>
(the one just repaired) and went green while 647 others were still black.
Page count is load-bearing rather than cosmetic: the packet is printed
front-and-back, so "one sheet is exactly one page" is a correctness property,
not a tidiness one.
Counts are relative (sheets vs pages, cards vs photos) so the gate is not
pinned to one zone's group count, and the packet path is an argument or
PACKET_HTML rather than a hardcoded path. With no packet given it SKIPS
rather than failing, since the packet is generated on demand.
|