From 96d7902a9517697e1317bfa6f33742e6a73c2611 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:00:29 +0000 Subject: [PATCH 1/4] Sweep every browser gate for English-text matchers; fix the fifth and last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same defect had surfaced in FOUR gates across previous ticks — a gate keyed on English prose silently excludes every translated surface, or goes red on a correctly translated one. Finding a fifth by accident would mean the pattern was still being discovered rather than eliminated, so this sweeps all 25 gates that drive a browser instead of waiting for the next one. 9 flagged, 8 benign: CSS class names (.vtl-fab, .stl-panel) and CSS property VALUES (/paint|layout|strict|content/) are locale-independent by definition. ONE REAL HIT — check-poster-prints.mjs:131, `/Gadarenes/.test(...)`. That is the World English Bible's proper noun, so the assertion and the bug were locked together: it passes today ONLY because point4_passage is still English in all ten non-English blocks. The day a licensed translation lands it would have gone red on a CORRECT packet. Now asserts the .p4-passage block by length, the same structural fix already applied in check-packet-render and check-packet-browser. Proved on the case the old matcher got wrong: renaming Gadarenes -> gadarenos, as any translation would, now PASSES where /Gadarenes/ would have failed. MUTATION-TESTED both directions — a passage truncated to 180 chars FAILS ("looks truncated: 259 chars"), and the .p4-passage block renamed in the MARKUP FAILS ("no .p4-passage block"). ★ ONE MUTATION SILENTLY DID NOT LAND, and it is worth recording. My first attempt at the second mutation used a count=1 replace, which hit the explanatory COMMENT rather than the markup — "p4-passage" appears twice in packet-render.js. The gate "passed" and I nearly recorded a hole in the assertion. The assertion was sound; the mutation was not. The sweep made the same class of error in the other direction, flagging check-packet-render for a "Gadarenes" that lives only in a comment explaining why it was removed. Five instances found and fixed across five ticks; none remain in live code. The rule for any new gate: if it matches on user-visible text, resolve the expected string from the catalog for the page language, or assert on STRUCTURE — a class hook, a length — instead. --- internal/check-poster-prints.mjs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/internal/check-poster-prints.mjs b/internal/check-poster-prints.mjs index c3f15377a..e5962470e 100644 --- a/internal/check-poster-prints.mjs +++ b/internal/check-poster-prints.mjs @@ -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) { From 18db274d72b386f9cf618d44395b70319b7a3562 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:10:23 +0000 Subject: [PATCH 2/4] The packet gate asserts RTL LAYOUT, not just the dir attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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. --- internal/check-packet-render.mjs | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index a43e56bff..0c5d67b2b 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -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 +// 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`})`) From 482e6d4ca0cf77fe7c5efe7868942cc4296abdf4 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:24:01 +0000 Subject: [PATCH 3/4] Wire the seven gates that could run, and make wave membership visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MEASURED: 70 of 100 check/verify scripts were in package.json but in NEITHER gates:pre nor gates:post. I had fixed this same "present but unwired" failure three times for individual gates without ever asking how many others were in the state. ★ THE GATE THAT EXISTS TO CATCH THIS WAS ITSELF UNWIRED. check:no-orphan-gates asserts every gate-shaped file is REACHABLE — named in package.json or imported — and it passed, 87/87. But reachable and WIRED are different properties, and conflating them is exactly how 70 scripts sat outside every build. A script entry means a human CAN run it; wave membership means it RUNS. Tested each unwired gate standalone: 7 pass with no precondition and were simply never wired (now in gates:post, 22 -> 29 steps, +1.5s on a ~150s build); 6 FAIL on pre-existing documentation drift nobody sees — check:verification-tags alone lists 61+ untagged cards — which is its own project, and wiring a red gate only gets it disabled; the rest legitimately need a served tree, a device, a token or an argument. ★ ONE I WIRED WRONGLY, AND THE MISTAKE IS THE USEFUL PART. check:settled asserts the tree is QUIET — no builder running, no recent churn — so it fails inside a build BY DESIGN. It passed twice standalone, I wired it on that evidence, and the next authoritative build came back 29/30 with check:settled the only red. Running a gate twice while IDLE proves repeatability, not compatibility with running mid-build. The question for wave membership is not "does it pass" but "is its assertion compatible with a build in flight". Unwired, and the reasoning is recorded in check-no-orphan-gates so nobody re-adds it on the same evidence. check:no-orphan-gates now reports wave membership as a second, distinct question from reachability — "37/93 run in gates:pre or gates:post" — naming the first eight unwired so the debt is visible rather than silent. It REPORTS rather than fails, because most of the remaining 56 are correctly hand-run. Mutation-tested: removing check:zone-census from gates:post moves the count 37 -> 36. Build: 7/7 pre, 23/23 bundles, 29/29 post, exit 0. --- internal/check-no-orphan-gates.mjs | 33 ++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/internal/check-no-orphan-gates.mjs b/internal/check-no-orphan-gates.mjs index 21fcfbed0..9499add44 100644 --- a/internal/check-no-orphan-gates.mjs +++ b/internal/check-no-orphan-gates.mjs @@ -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') diff --git a/package.json b/package.json index 52f776d73..f39f82916 100644 --- a/package.json +++ b/package.json @@ -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", "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.", From 856a350c6acf9b5450b3fc9ae01cc3c5f14fefee Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:30:49 +0000 Subject: [PATCH 4/4] =?UTF-8?q?The=20CLI=20doc=20quoted=2018=20gates=20whi?= =?UTF-8?q?le=2029=20ran=20=E2=80=94=20sync=20it,=20and=20wire=20the=20gat?= =?UTF-8?q?e=20that=20noticed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last tick left six documentation-drift gates failing and NAMED on every build. This is the smallest — one finding — and it guards something real: a doc that quotes a config is claiming to be ground truth. THE DRIFT WAS ENTIRELY MINE. Every one of the eleven missing steps is a gate added over the past ticks, all four i18n gates among them. ★ AND IT DRIFTED IN THE UNUSUAL DIRECTION. The doc did not OVERSTATE coverage, it UNDERSTATED it: a reader consulting README-SECTION-3 would believe eleven checks were not in the chain. That is precisely what the gate's own header warns about — "there is no wrong character to notice, just a check the reader believes is not in the chain." ★ THE JUDGEMENT CALL. The surrounding prose said "the 29 gates cost 84.5s" and "31 in, 31 out" — figures from the 2026-09-23 phase refactor, now 36. I DATED them rather than restating them as current. Those numbers are the EVIDENCE that the phase change preserved the step set; rewriting them to 36 would destroy that evidence while looking like an update. The gate compares quoted SCRIPT LINES only, so this prose drift was invisible to it either way. Wired into gates:post (29 -> 30) after confirming it reads only package.json and a markdown file, neither of which the build mutates — the precondition test I got wrong with check:settled last tick, where "passes twice standalone" proved repeatability rather than compatibility with running mid-build. ★ WIRING IT IS SELF-REFERENTIAL and takes two passes: adding the gate to gates:post CHANGES the line the gate quotes, so it failed immediately after being wired and needed one more doc sync. That is the mechanism working, and it is now noted in the doc so the next editor is not surprised by it. MUTATION-TESTED: removing a single step from the DOC only makes it FAIL by name — the invisible-omission case it exists for. Build: 7/7 pre, 23/23 bundles, 30/30 post, exit 0. --- documentation/README-SECTION-3-cli-commands.md | 11 +++++++++-- package.json | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/documentation/README-SECTION-3-cli-commands.md b/documentation/README-SECTION-3-cli-commands.md index 062a053ec..770c1427d 100644 --- a/documentation/README-SECTION-3-cli-commands.md +++ b/documentation/README-SECTION-3-cli-commands.md @@ -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 diff --git a/package.json b/package.json index f39f82916..eb0c8eca7 100644 --- a/package.json +++ b/package.json @@ -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 check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates", + "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.",