Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
build-gate / build (push) Waiting to run

This commit is contained in:
doxa-maps-88655c70 2026-09-24 11:34:09 +00:00
commit 670a2dc3f7
5 changed files with 108 additions and 6 deletions

View file

@ -22,7 +22,11 @@ The scripts these commands are grounded in (from this branch's `package.json`):
// BUILD_LOCK_WAIT=600 to QUEUE rather than collide. Note the lock serializes but does NOT
// dedupe: N queued builds = N full rebuilds.
// ★ THE PIPELINE RUNS IN PHASES, NOT AS ONE `&&` CHAIN (2026-09-23). Measured on the build
// box: the 29 gates cost 84.5s chained, but 21 of them finish under 700ms — they were
// box AS IT WAS THAT DAY — the numbers below are a DATED BENCHMARK, not a running total.
// The gate set has grown since (36 steps as of 2026-09-24: 7 pre + 29 post); the timings
// and the "31 in, 31 out" count are kept verbatim because they are the EVIDENCE that the
// phase change preserved the step set. Restating them as current would destroy that.
// Then: the 29 gates cost 84.5s chained, but 21 of them finish under 700ms — they were
// queueing behind check:mjs-parse (26.9s) and check:parse (18.7s), not working. The 22 vite
// builds cost 129.0s and were never ordered at all: vite.config.js gives each bundle its own
// outDir with emptyOutDir:false, so they only ever needed to not overwrite each other.
@ -36,13 +40,16 @@ The scripts these commands are grounded in (from this branch's `package.json`):
// `&&` BETWEEN phases is load-bearing and stays: a source gate still blocks the build, and
// the build still blocks every gate that reads its output. Within a phase the runner reports
// ALL failures instead of stopping at the first, so three broken gates cost one cycle, not three.
// ⚠ check:quoted-scripts IS ITSELF IN gates:post, so adding or removing ANY gate changes the
// very line this doc quotes — the update is self-referential and takes two passes: wire the
// gate, then re-transcribe. That is the mechanism working, not a bug.
// If you edit package.json, re-transcribe this block — a doc that QUOTES a config is
// claiming to be ground truth, and this one had silently stopped being it.
"dev": "bun x vite",
"build": "bash internal/build-lock.sh",
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts",
"build:bundles": "node internal/run-parallel.mjs --bundles", // the 22 vite builds, 4 at a time
"preview": "bun x vite preview",
"serve": "node internal/serve-strict.mjs", // 404s a directory, like production

View file

@ -107,6 +107,39 @@ if (orphans.length) {
process.exit(1)
}
// ── SECOND, DISTINCT QUESTION: does it RUN? ────────────────────────────────
// Reachable and wired are not the same property, and conflating them is how 70 of
// 100 check:* scripts came to sit outside every gate wave without anyone noticing —
// including this gate itself. A script entry means a human CAN run it; membership in
// gates:pre/gates:post means it runs on every build.
//
// This REPORTS rather than fails, deliberately. Most unwired gates are correctly
// hand-run: they need a served tree (check:mobile, check:shots), a device, a token,
// or an argument (check:packet-render takes a path). Failing the build on those
// would be wrong and the gate would be disabled within a day.
//
// ⚠ AND SOME MUST NEVER BE WIRED, which "it passes when I run it" cannot tell you.
// check:settled asserts the tree is QUIET — no builder running, no recent churn — so
// it fails inside a build BY DESIGN. Measured 2026-09-24: it passed twice standalone,
// was wired on that evidence, and failed the very next build (29/30). The test for
// wave membership is not "does it pass" but "is its assertion compatible with running
// mid-build". What it must not do is
// stay silent — a gate that could run and does not is indistinguishable from one
// that is broken, which is the exact failure verify-shell-scroll had.
const pkg = JSON.parse(pkgRaw)
const waves = `${pkg.scripts['gates:pre'] || ''} ${pkg.scripts['gates:post'] || ''}`
const checks = Object.keys(pkg.scripts).filter((k) => /^(check|verify|smoke):/.test(k)
&& !k.endsWith(':self-test'))
const unwired = checks.filter((k) => !waves.includes(k))
console.log(` wave membership : ${checks.length - unwired.length}/${checks.length} `
+ `check/verify script(s) run in gates:pre or gates:post`)
if (unwired.length) {
console.log(` ${unwired.length} run only by hand — expected for gates needing a served tree, a`)
console.log(' device, a token or an argument. If one of these needs no precondition, wire it:')
for (const k of unwired.slice(0, 8)) console.log(` · ${k}`)
if (unwired.length > 8) console.log(` …and ${unwired.length - 8} more`)
}
console.log(`NO-ORPHAN-GATES: PASS — ${checked} gate-shaped file(s); all reachable (${tagged} tagged @one-shot).`)
console.log(' Reachable means named in package.json or imported by something that is. The @one-shot')
console.log(' tag exists so a dated diagnostic can be KEPT as a reproduction recipe without either')

View file

@ -58,6 +58,39 @@ const lang = await page.evaluate(() => document.documentElement.lang || 'en')
// the bug it is, and no other assertion here would notice.
const RTL = new Set(['ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi'])
const dir = await page.evaluate(() => document.documentElement.dir || 'ltr')
// ── AND THE TEXT MUST ACTUALLY BE LAID OUT THAT WAY ─────────────────────────
// dir="rtl" is a DECLARATION, not a layout. It can be set correctly on <html>
// while an inner element overrides it, or while the sheet keeps its LTR flow —
// and every other assertion here would still pass, because an Arabic packet with
// readable glyphs in LTR order looks like a styling quirk rather than the bug it
// is. So measure where the painted text actually sits.
//
// ⚠ MEASURE THE INLINE TEXT RUN, NOT THE BLOCK. A first attempt compared each
// element's own box to its parent's and got IDENTICAL numbers for Arabic and
// English — it was measuring container geometry, which does not move when
// direction flips. A Range over the text node gives the painted extent, which
// does: in ar the run pins to the RIGHT edge (rightGap 0, slack on the left), in
// en/hi to the LEFT. Centred text is skipped because it proves nothing either
// way, and so is text that fills its box — no room to tell.
const layout = await page.evaluate(() => {
const out = { pinnedRight: 0, pinnedLeft: 0, sampled: 0, rtlComputed: 0 }
for (const el of document.querySelectorAll('.sheet *')) {
if (el.childElementCount) continue
if ((el.textContent || '').trim().length < 8) continue
const cs = getComputedStyle(el)
if (cs.textAlign === 'center') continue
const box = el.getBoundingClientRect()
const rng = document.createRange(); rng.selectNodeContents(el)
const tb = rng.getBoundingClientRect()
if (tb.width < 20 || box.width < 50 || box.width - tb.width < 12) continue
if (cs.direction === 'rtl') out.rtlComputed += 1
if (Math.round(box.right - tb.right) <= 1) out.pinnedRight += 1
else if (Math.round(tb.left - box.left) <= 1) out.pinnedLeft += 1
out.sampled += 1
if (out.sampled >= 25) break
}
return out
})
// SCRIPT FONT. 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,
// it must actually LOAD; a declared-but-failed @font-face is silent, and the page
@ -240,6 +273,20 @@ if (fontStatus.declared && fontStatus.loaded !== fontStatus.declared) {
}
if (RTL.has(lang) && dir !== 'rtl') fails.push(`lang="${lang}" is right-to-left but dir="${dir}"`)
if (!RTL.has(lang) && dir === 'rtl') fails.push(`lang="${lang}" is not RTL but dir="rtl"`)
// The attribute is necessary; this is the part that proves it took effect.
if (layout.sampled >= 5) {
const wantRight = RTL.has(lang)
const pinned = wantRight ? layout.pinnedRight : layout.pinnedLeft
if (pinned < Math.ceil(layout.sampled * 0.8)) {
fails.push(`lang="${lang}" should paint text to the ${wantRight ? 'RIGHT' : 'LEFT'}, but only `
+ `${pinned}/${layout.sampled} sampled runs do (right ${layout.pinnedRight}, left ${layout.pinnedLeft}) — `
+ `dir="${dir}" is declared and not taking effect`)
}
if (RTL.has(lang) && layout.rtlComputed < layout.sampled) {
fails.push(`${layout.sampled - layout.rtlComputed}/${layout.sampled} sampled elements compute `
+ 'direction:ltr inside an RTL packet — something overrides the document direction')
}
}
if (translated === 0) {
fails.push(`lang="${lang}" but no string differs from English — `
+ 'the language block is missing, mis-keyed, or the renderer ignored it')
@ -373,6 +420,8 @@ console.log(` page size : ${boxes[0] ? boxes[0].join(' x ') : '?'} pt`
console.log(` layout : ${r.cards} cards / ${PER_PAGE} per page `
+ `= ${expectedSheets} sheets expected, ${r.sheets} rendered`)
console.log(` script font : ${fontStatus.declared ? fontStatus.names.join(', ') : 'system stack'}`)
console.log(` text layout : ${layout.pinnedRight} right / ${layout.pinnedLeft} left of `
+ `${layout.sampled} sampled run(s)${layout.sampled < 5 ? ' (too few to assert)' : ''}`)
console.log(` language : ${lang} (dir=${dir}) (${r.rhythms}/${r.rhythmsTotal} rhythms in ${lang}`
+ `${translated === null ? '' : `, ${translated} strings translated`})`)

View file

@ -128,7 +128,14 @@ const r = await pg.evaluate(async ({ src, frame, packetRender, cardRender, qr, s
out.packetSheets = pdoc.querySelectorAll('.sheet').length
out.packetCards = pdoc.querySelectorAll('.card').length
out.packetWheel = pdoc.querySelectorAll('svg path[fill^="hsl"]').length
out.packetMark5 = /Gadarenes/.test(pdoc.body.innerText)
// ⚠ NOT /Gadarenes/. That is an ENGLISH proper noun from the World English Bible
// text, so this assertion would go RED on a CORRECTLY TRANSLATED packet and passes
// today only because point4_passage is still English in all ten non-English blocks
// (DECIDE-MARK5-SCRIPTURE-IS-ENGLISH-IN-EVERY-TRANSLATED-PACKET). The check and the
// bug were locked together — the same fix already applied in check-packet-render
// and check-packet-browser. Assert the passage BLOCK and its length, which holds in
// any language.
out.packetMark5Len = (pdoc.querySelector('.p4-passage') || { textContent: '' }).textContent.trim().length
// ── without a provider: must FAIL HONESTLY, not throw ────────────────────
const noProv = usePosterPrints({ label: (i) => i.label })
@ -155,13 +162,19 @@ if (!/no records provider/i.test(r.noProvMsg)) {
if (r.packetSheets !== 4) fails.push(`the packet printed ${r.packetSheets} sheets, expected 4`)
if (r.packetCards !== 2) fails.push(`the packet printed ${r.packetCards} people cards, expected 2`)
if (r.packetWheel !== 12) fails.push(`the prayer wheel has ${r.packetWheel} segments, expected 12`)
if (!r.packetMark5) fails.push('the packet is missing Mark 5:1-20 in full')
// The English passage is 2,141 chars; 1,200 is a floor no truncation or placeholder
// survives, with room for languages that render the same text more compactly.
if (r.packetMark5Len < 1200) {
fails.push(r.packetMark5Len === 0
? 'the packet is missing Mark 5:1-20 in full (no .p4-passage block)'
: `Mark 5:1-20 looks truncated: ${r.packetMark5Len} chars`)
}
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
console.log(` list print : ${r.listRows} rows, checkbox=${r.listHasCheckbox}`)
console.log(` without provider : phase=${r.noProvPhase} "${r.noProvMsg.slice(0, 60)}"`)
console.log(` packet : ${r.packetSheets} sheets, ${r.packetCards} cards, `
+ `${r.packetWheel} wheel segments, Mark5=${r.packetMark5}`)
+ `${r.packetWheel} wheel segments, Mark5=${r.packetMark5Len} chars`)
console.log(` page errors : ${errs.length}`)
if (fails.length) {

View file

@ -141,7 +141,7 @@
"check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test",
"check:legend-source": "node internal/check-legend-matches-source.mjs # the numbers the maps SHOW must match the numbers the live SOURCE has. Every other gate checks INTERNAL consistency — a page can agree with itself perfectly while its legend has stopped describing reality. Offline SKIPs (exit 0); only a reachable endpoint that disagrees fails.",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts",
"build:bundles": "node internal/run-parallel.mjs --bundles",
"check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs",
"check:tabs-localized": "node internal/check-tabs-localized.mjs # a chosen locale must CHANGE the rendered tab text. check:i18n-parity compares catalogs to each OTHER, so it cannot tell a shipped translation from a dead one; tabs.json is reached only via a DYNAMIC key (SemanticTreeLegend.vue:40), invisible to any grep. Renders it and reads the result.",