6 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
467b7f01a2 |
feat(packet): all 11 locales — Hindi unblocked with a vendored Devanagari face
Some checks are pending
build-gate / build (push) Waiting to run
The packet now ships in every locale the app ships: en es fr pt de it ro ru ar hi zh. Each carries all 21 translatable keys, the twelve prayer points, the eleven rhythms, both attributions and the privacy note. point4_passage stays omitted everywhere and falls back to the licensed English WEB text, for the reason already recorded — translating scripture here would put unlicensed, unverified Bible text into a printed prayer packet. HINDI WAS BLOCKED ON A FONT, not on a translation. Devanagari renders as tofu boxes in the packet's Georgia/Palatino/serif stack because this machine has no Devanagari font at all. Noto Serif Devanagari (SIL OFL) is now vendored — devanagari subset only, 124 KB, licence and provenance beside it, matching how poppins/ ships latin and latin-ext separately. It is base64-INLINED into the packet, not linked. A packet is a standalone file meant to print offline — the Node generator writes HTML with no server, and the photos are already inlined for exactly this reason — so a URL would break precisely where the packet is most used. Cost: ~165 KB on a 3.5 MB packet, and only for that language. One rule prepends the face to the stack rather than editing 22 hardcoded Georgia declarations, and the browser falls back PER CHARACTER, so Latin scripture references in a Hindi packet still set in Georgia. mr and ne map to the same face; a missing font warns loudly rather than silently producing rectangles. Chinese needed no font work — CJK already shapes in the system stack — only the translation. ★ TWO MEASUREMENTS LIED ABOUT SCRIPT COVERAGE, recorded so nobody automates this cheaply. Comparing shaped width against a missing font reported ar and zh BROKEN and hi FINE — exactly backwards, because Devanagari tofu boxes happen to match real glyph advance. An ink-column-profile heuristic then scored CORRECTLY RENDERED Devanagari the same as tofu (3 vs 2 distinct heights), because its baseline bar gives a uniform profile. Both were abandoned: a detector that blocks working packets is worse than none. Verified: all 11 languages render 8 sheets -> 8 letter portrait pages, map painted, zero overflow. The Hindi prayer-points page was READ AS A PICTURE — conjuncts and matras shaped correctly, no boxes, Latin references still Georgia. |
||
|
|
507cf68e12 |
feat(cards): horizontal prayer cards — the layout changes, not just the page
Some checks are pending
build-gate / build (push) Waiting to run
Driver asked for "horizontal cards" alongside the vertical ones, with the print dialog's aspect ratio chosen at the print point. ★ LANDSCAPE IS NOT TRANSPOSED DIMENSIONS. The portrait card stacks the photo ABOVE the text, occupying the top 46%; at 3.5 x 2.5in that same band is a strip too short to carry a face AND leave room for the rows under it. The landscape stocks put the photo BESIDE the text instead — full-bleed down the left 42%, name over a scrim at its foot, data and QR in the right column. So `side` is a LAYOUT switch the CSS reads, not a cosmetic flag. It is driven by the stock rather than by comparing width to height: a square stock would be ambiguous, and the composition is a deliberate property of the format, not an inference from its numbers. Two landscape stocks added and exposed in the poster-wall picker: pokerLandscape 3.5 x 2.5 in -> 252 x 180 pt postcardLandscape 6 x 4 in -> 432 x 288 pt Verified at the rendered layer by the new check:print-geometry, which measures the MediaBox rather than reading the CSS — and read as a picture, not only asserted on: photo left, "Aheu" over the scrim, country/people/language/religion in the right column, QR and "PRAY FOR THEM" at the foot, zero overflow. |
||
|
|
dd14b5fe76 |
feat(packet): five more languages, and right-to-left support for Arabic
Some checks are pending
build-gate / build (push) Waiting to run
The packet now ships in 9 of the app's 11 locales: en es fr pt de it ro ru ar. Each carries all 21 translatable keys — page furniture, the twelve prayer points with sub-headings, the eleven rhythms, both Metacamp attributions, the privacy note. point4_passage (Mark 5:1-20 in full) stays omitted in every non-en block and falls back to the licensed English WEB text with its credit, for the reason already recorded: translating scripture here would put unlicensed, unverified Bible text into a printed prayer packet. RTL IS NOW REAL, not assumed. The renderer emits dir on <html> from an RTL locale set. Without it an Arabic packet renders readable glyphs LEFT-aligned — which looks like a styling quirk rather than a bug, and no assertion noticed. ★ TWO BIDI FINDINGS, both from reading the render rather than the code: 1. Scripture references are LTR runs inside RTL paragraphs, and the bidi algorithm REORDERS a run that starts with a digit: "1 Peter 5:6-11" rendered as "Peter 5:6-11 1". Each reference is now dir="ltr" + unicode-bidi:isolate. 2. With isolation, the reference LIST reads right-to-left on an RTL page (first item rightmost). That is correct for an RTL reader, not a defect — recorded in _meta so nobody "fixes" it. English source order is unchanged, asserted. ★ HINDI IS DELIBERATELY NOT ADDED. Rendering each script at 48px and READING THE PIXELS: Latin, Cyrillic, Arabic and CJK all shape correctly in the packet's Georgia/serif stack; DEVANAGARI RENDERS AS TOFU BOXES, because this box has zero Devanagari fonts (fc-list). A hi block would ship a page of empty rectangles. It is blocked on vendoring a Devanagari font into public/assets/fonts, which is its own piece of work. ⚠ THE MEASUREMENT THAT LIED. My first test compared rendered text advance against a missing-font baseline and reported ar and zh BROKEN and hi FINE — the exact opposite of the truth, because Devanagari tofu boxes happen to match real glyph advance. One screenshot settled in a glance what two rounds of clever arithmetic got backwards. Recorded in _meta. Verified at the rendered layer in all nine languages: 8 sheets -> 8 letter portrait pages, 95 people cards, 12 wheel segments, map painted, zero overflowing sheets, 19 strings differing from English per locale. The Arabic prayer-points page was read as a picture, not just asserted on. |
||
|
|
d154a76f64 |
feat(posters): generate the poster in the browser when none is shipped
Some checks are pending
build-gate / build (push) Waiting to run
Driver: "it would be a big win to not have to have 80mb of images in a public folder and let the posters get generated by the website." Downloading a poster used to end at `poster not found (404)` whenever the pre-rendered .webp was absent. It now falls back to rasterising the poster's own HTML in the browser — fetch posters/<id>.html, inline its stylesheet into the <svg>, draw to a canvas, hand back a blob. Same move the prayer packet already makes: generate on click, ship nothing. This unblocks dropping the 41 MB of committed .webp posters without degrading a working feature, which is the part that had to come first. It also covers the KEY CITY poster wall for free — both walls share this component. ⚠ AN SVG LOADED THROUGH <img> IS AN ISOLATED DOCUMENT: it cannot see the host page's CSS, so every var() falls back to its initial value and `fill`'s initial value is BLACK. The packet shipped exactly that bug once (660 of 661 map elements black, no console error). The stylesheet is therefore inlined INTO the svg, and the result is checked by measuring pixels — a blank render throws rather than handing over a black rectangle. Verified by check:poster-raster: laos rasterises to 3000x4000 with luminance 229 and 54 distinct colours; strip the <style> block and it drops to 13 and 2 colours and the gate fires. WHAT I MEASURED AND REJECTED ALONG THE WAY, so nobody repeats it: shipping the poster HTML *instead of* the webp looked like a clean win on the first sample (laos: 115 KB gzipped vs 213 KB webp) and does NOT generalise — poster HTML scales with geometry complexity while webp is a fixed-resolution raster, so asia-india-rajasthan is 414 KB gzipped vs 239 KB webp. Generalising from one sample is how that idea nearly shipped. Also measured: 351 of the 378 committed posters (35.4 MB) are city-* posters belonging to the Key Cities feature, not to any of the 29 UPG-100 lists. They are a live feature, not dead weight — which is why this lands as a fallback in the shared component rather than a deletion. encodeURIComponent, not btoa, for the data URL: the posters carry non-Latin-1 people-group and place names and btoa throws on those. |
||
|
|
7bba1c0771 |
feat(upg-100): the prayer card deck prints as CARDS, not as paper
Some checks are pending
build-gate / build (push) Waiting to run
Driver: "the print dialog that opens up needs to show all the cards not in a
paper format but a CARD format, the aspect ratio that is used in the card
generator."
The deck set @page{size:letter portrait} and gave each card height:100%, so 95
cards printed as 95 mostly-empty 8.5x11 sheets — and with NO photo, because
image_url was never in the fetch. Measured: 95 of 95 groups DO have a portrait,
so the omission cost every card the one thing that makes it a prayer card.
Now the PAGE IS THE CARD. @page takes the stock's own inches, so one card is
exactly one page: 95 cards -> 95 pages at 180x252pt (2.5x3.5in). Print on
cardstock and cut, or send the PDF to a card printer. The stock is a user
choice, per the Driver's ask to "choose the layout of the print dialog to choose
the aspect ratio": poker 2.5x3.5in (default), postcard 4x6in, A6.
⚠ @page SIZE MUST BE EXPLICIT INCHES. Chromium SILENTLY falls back to letter for
a named size it does not know: `size: A6 portrait` yields 612x792pt with no
error of any kind, while `size: 2.5in 3.5in` yields exactly 180x252pt. The gate
therefore asserts the MEASURED MediaBox — a check that grepped the stylesheet
would pass a deck that prints as paper.
Card content follows doxa-life/promotional-material-generation, the Python data
pipeline feeding the InDesign template: per language it emits Description,
Religion, Area, Country, a QR to that group's page, and the URL. Ported to
JavaScript here so the deck generates in the map app on click. Description and
Area are NOT available from this API — measured 0 of 2107 records carry either —
so the card is built from what genuinely exists: portrait, name, country,
population, language, religion. An empty block reserved for data that never
arrives is worse than a card designed around the data there is.
QR is hand-rolled (library/packet/qr.js, ~200 lines): byte mode, level M,
smallest fitting version, mask 0. A symbol is pure computation, so it costs one
small file instead of a package and runs identically under Node and in a tab —
the standing constraint is "without adding a lot of dependencies on the
internet". A QR that does not scan looks exactly like one that does, so it is
verified by DECODING: an independent reverse-path decoder re-derives the data
placement, unmasks, de-interleaves and parses the header. 5/5 round-trip,
including version selection (v2 for a short URL, v3 for real ones).
I18N: the QR encodes the LOCALISED profile URL, so a Spanish card scans through
to the Spanish page — verified, pray.doxa.life/es/aheu. Strings fall back per
key; checked with a partial Spanish block ("Ora por ellos" rendering while
Language/Religion stayed English).
★ ONE BUG THE NUMERIC GATE PASSED, worth recording. The first deck rendered
"LAO", "thm", "EAN" — raw codes on a card a person is meant to pray from —
because normalizeRecords() flattens each { value, label } dict to its VALUE and
parks the label under <field>_label. A gate counting "4 rows rendered" passed
it: the rows were there, just unreadable. The gate now asserts the values are
HUMAN-READABLE, not merely present.
Verified by check:card-deck across all three stocks: 95 cards / 95 pages, 0
broken portraits, 95 QR codes with real symbols, 0 raw codes, 0 overflow.
|
||
|
|
301cc38fba |
feat(upg-100): print a prayer packet from the map legend
Some checks are pending
build-gate / build (push) Waiting to run
Driver, as a first-time user: "I select one and it gives me print options at
the top right of the legend... that's the only way that map would be usable."
The affordance was already built and switched OFF. The shared legend emits
'export' with 'poster' | 'packet' for the selected node, the profile wired
@export, and SHOW_EXPORT_BUTTON was held false with the note "until the export
packet is finished: the button currently produces a broken packet". That
precondition is now met, so the switch is on.
ONE RENDERER, TWO CALLERS. The pages move to library/packet/packet-render.js,
imported by BOTH the build-time generator and the browser export, so the printed
packet and the generated one cannot drift. The card asked for exactly this:
"reuse the existing print actions rather than growing a fourth implementation."
The extraction is behaviour-preserving — the generated packet is byte-identical
to before apart from leading whitespace (the code is now inside a factory), and
check:packet-render still passes 8/8.
★ THE PACKET IS GENERATED ON CLICK, NEVER SHIPPED. One packet is ~3.5 MB (photos
dominate) and 28 zones would add ~100 MB to a repo that ALREADY had a publish
blocked at a 74 MB asset tree. Generating client-side costs zero repository
bytes; the photos come from URLs the records already carry. This is the Driver's
instruction: "instead of fetching the poster from the public folder I want the
client side code to generate the poster real time when you click on the button
so it does not take a lot of mb storage." Bundle cost: +21 KB (494 -> 516 KB),
which includes all 12 prayer points and Mark 5:1-20 in full.
Printing goes through the SHARED openPrintFrame — a hidden same-page iframe, not
window.open(). A popup is blockable and drops the reader out of the map.
Two traps the browser path had to handle, both already known to this codebase:
- pg_slugs BYPASSES the shared selector evaluator (it intentionally throws on
that type, and 12 of the 27 lists use it). Same special-case the map and the
poster wall apply, so all three agree about who is in a list.
- evaluate() returns a Set, not an array.
VERIFIED IN A BROWSER, not by reading source (new gate check:packet-browser):
live records -> shared renderer -> page.pdf(), giving 8 sheets -> 8 pages at
612x792pt, 95 people cards, 12 wheel segments, Mark 5 in full, 2 metacamp.org
attributions, 0 page errors. I18n verified with a partial Spanish block: the
translated headings rendered in Spanish while Mark 5 and the attributions fell
back to English, per key, never blank.
Card BUILD-UPG100-MAP-LEGEND-LEADS-WITH-THE-UPG-100-LIST part (d). Parts (a) and
(b) shipped earlier. Part (c) — removing the People Group tab and retabbing the
legend — stays OPEN: that card is explicitly driver_gated on "what is the
legend's tree once Region/Zone/People Group are removed", and the current tabs
are wired to real hierarchy data.
|