From d26e416d036474fbdc6753dbf6dfcb22c9e6e12b Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:36:41 +0000 Subject: [PATCH 1/2] Translate the list and the table headers; correct a claim I had not checked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resources-list's chrome is translated in all eleven — the three filter tabs, the generic resource names, the people-group count — and the table's column headers are localised at render in data-tables. resources-map, data-tables and charts now report zero leaks by the corrected gate; what remains is in resources-list's data-driven rows. A CLAIM I MADE AND HAD NOT MEASURED. The previous commit said the CSV export "keeps the data file's English field names". It does not: Tabulator exports the column titles it is given, so a Russian reader's CSV heads "Народ","Страна", "Регион". I checked only after asserting the opposite. Left translated on purpose — this is a gift for people to use and someone who exports what is on their screen should get what was on their screen — with the stable machine contract being data/tables/ itself, which is untouched and English. THE GATE NEEDED A THIRD CORRECTION, and this one mattered most. Substring matching over the rendered text reported keys that were never rendered at all: "Language" matched inside the DATA — the people-group language "Sign Language" — and "Region" inside a region name. No length filter separates a label from a word sitting inside one. The unit is now an element's OWN label, and a leak is a label that EQUALS the English value while a translation exists. A language name can no longer impersonate a UI string. Scripts are alphabetised per the fleet convention, so parallel landings stop colliding on the last line of package.json. --- internal/check-gospel-english-leaks.mjs | 40 +- library/i18n/locales/ar/gospel.json | 23 + library/i18n/locales/de/gospel.json | 23 + library/i18n/locales/en/gospel.json | 23 + library/i18n/locales/es/gospel.json | 23 + library/i18n/locales/fr/gospel.json | 23 + library/i18n/locales/hi/gospel.json | 23 + library/i18n/locales/it/gospel.json | 23 + library/i18n/locales/pt/gospel.json | 23 + library/i18n/locales/ro/gospel.json | 23 + library/i18n/locales/ru/gospel.json | 23 + library/i18n/locales/zh/gospel.json | 23 + package.json | 514 +++++++++--------- .../gospel-resources/data-tables.vue | 31 +- .../gospel-resources/resources-list.vue | 25 +- public/doxa-maps/doxa-maps-spa/manifest.json | 12 +- .../gospel-resources/charts/index.html | 2 +- .../gospel-resources/data-tables/index.html | 2 +- .../gospel-dashboard/index.html | 2 +- .../gospel-resources/gospel-resources.js | 38 +- .../resources-list/index.html | 2 +- .../gospel-resources/resources-map/index.html | 2 +- public/doxa-maps/index.html | 2 +- public/doxa-maps/manifest.json | 12 +- 24 files changed, 620 insertions(+), 317 deletions(-) diff --git a/internal/check-gospel-english-leaks.mjs b/internal/check-gospel-english-leaks.mjs index f8a4a4e13..8fa7f6477 100644 --- a/internal/check-gospel-english-leaks.mjs +++ b/internal/check-gospel-english-leaks.mjs @@ -66,32 +66,42 @@ try { * Every English prose block in this bundle declares itself with dir="ltr". So the * chrome is everything OUTSIDE those subtrees, which is exactly the population a * translated key is supposed to reach. */ - const text = await page.evaluate(() => { + /* ═══ WHOLE LABELS, NOT SUBSTRINGS — the third correction, and the last ═══════════ + * Substring matching over the rendered blob reported keys that were never rendered at + * all: "Language" matched inside the DATA — the people-group language "Sign Language" — + * and "Region" inside a region name. Those are exactly the data strings rega's method + * excludes, and no amount of length filtering separates a label from a word that + * happens to sit inside one. + * So the unit is an ELEMENT'S OWN LABEL: the trimmed text a leaf element renders. A + * leak is a label that EQUALS the English value when a different translated value + * exists. That is the same population a translated key is supposed to reach, and a + * language name can no longer impersonate one. */ + const labels = await page.evaluate(() => { const sr = document.querySelector('gospel-resources-map')?.shadowRoot - if (!sr) return '' - /* A ShadowRoot is not clonable, so walk it and skip declared-English subtrees. */ - const parts = [] - const walk = (node) => { + if (!sr) return [] + const out = new Set() + const walk = (node, inProse) => { for (const child of node.childNodes) { - if (child.nodeType === 3) { parts.push(child.textContent); continue } if (child.nodeType !== 1) continue - if (child.getAttribute && child.getAttribute('dir') === 'ltr') continue if (child.tagName === 'STYLE' || child.tagName === 'SCRIPT') continue - walk(child) + const prose = inProse || (child.getAttribute && child.getAttribute('dir') === 'ltr') + const own = [...child.childNodes].filter((n) => n.nodeType === 3) + .map((n) => n.textContent.trim()).filter(Boolean).join(' ').trim() + if (own && !prose) out.add(own) + walk(child, prose) } } - walk(sr) - return parts.join(' ').replace(/\s+/g, ' ') + walk(sr, false) + return [...out] }) await page.close() - if (!text) { leaks.push(leaf + ' ' + lc + ': rendered nothing at all'); continue } + if (!labels.length) { leaks.push(leaf + ' ' + lc + ': rendered no labels at all'); continue } + const labelSet = new Set(labels) for (const [key, e] of Object.entries(en)) { const t = loc[key] - /* Short entries match inside unrelated English words; six characters is the floor - at which a match is about the key rather than about the alphabet. */ - if (!t || t === e || e.length < 6) continue + if (!t || t === e) continue compared++ - if (text.includes(e) && !text.includes(t)) leaks.push(leaf + ' ' + lc + ' ' + key + ': shows English "' + e.slice(0, 40) + '"') + if (labelSet.has(e) && !labelSet.has(t)) leaks.push(leaf + ' ' + lc + ' ' + key + ': a label reads English "' + e.slice(0, 40) + '"') } } } diff --git a/library/i18n/locales/ar/gospel.json b/library/i18n/locales/ar/gospel.json index b7378b2fc..a9d19274a 100644 --- a/library/i18n/locales/ar/gospel.json +++ b/library/i18n/locales/ar/gospel.json @@ -128,5 +128,28 @@ "listTitle": "موارد الإنجيل حسب اللغة", "chartsTitle": "موارد الإنجيل — حسب اللغة", "loadingTables": "جارٍ تحميل جداول الموارد…" + }, + "list": { + "noText": "بلا نص مقدس", + "hasScripture": "لديها الكتاب المقدس", + "allLanguages": "كل اللغات", + "peopleGroup": "شعب", + "peopleGroups": "شعوب", + "scriptureHere": "الكتاب المقدس بهذه اللغة" + }, + "columns": { + "People group": "الشعب", + "Country": "البلد", + "Region": "المنطقة", + "Language": "اللغة", + "Population": "عدد السكان", + "Religion": "الديانة", + "Gospel access": "الوصول إلى الإنجيل", + "Resources": "الموارد", + "Scripture": "الكتاب المقدس", + "Bible video": "فيديو كتابي", + "Gospel presentation": "عرض للإنجيل", + "Countries": "البلدان", + "People groups": "الشعوب" } } diff --git a/library/i18n/locales/de/gospel.json b/library/i18n/locales/de/gospel.json index 682b33bc1..8b480a808 100644 --- a/library/i18n/locales/de/gospel.json +++ b/library/i18n/locales/de/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Evangeliums-Ressourcen nach Sprache", "chartsTitle": "Evangeliums-Ressourcen — nach Sprache", "loadingTables": "Ressourcentabellen werden geladen…" + }, + "list": { + "noText": "Kein Bibeltext", + "hasScripture": "Hat Bibeltext", + "allLanguages": "Alle Sprachen", + "peopleGroup": "Volksgruppe", + "peopleGroups": "Volksgruppen", + "scriptureHere": "Bibeltext in dieser Sprache" + }, + "columns": { + "People group": "Volksgruppe", + "Country": "Land", + "Region": "Region", + "Language": "Sprache", + "Population": "Bevölkerung", + "Religion": "Religion", + "Gospel access": "Zugang zum Evangelium", + "Resources": "Ressourcen", + "Scripture": "Bibeltext", + "Bible video": "Bibelvideo", + "Gospel presentation": "Evangeliums-Darstellung", + "Countries": "Länder", + "People groups": "Volksgruppen" } } diff --git a/library/i18n/locales/en/gospel.json b/library/i18n/locales/en/gospel.json index 72975f6fc..648289969 100644 --- a/library/i18n/locales/en/gospel.json +++ b/library/i18n/locales/en/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Gospel Resources by Language", "chartsTitle": "Gospel Resources — by Language", "loadingTables": "Loading resource tables…" + }, + "list": { + "noText": "No text Scripture", + "hasScripture": "Has scripture", + "allLanguages": "All languages", + "peopleGroup": "people group", + "peopleGroups": "people groups", + "scriptureHere": "Scripture in this language" + }, + "columns": { + "People group": "People group", + "Country": "Country", + "Region": "Region", + "Language": "Language", + "Population": "Population", + "Religion": "Religion", + "Gospel access": "Gospel access", + "Resources": "Resources", + "Scripture": "Scripture", + "Bible video": "Bible video", + "Gospel presentation": "Gospel presentation", + "Countries": "Countries", + "People groups": "People groups" } } diff --git a/library/i18n/locales/es/gospel.json b/library/i18n/locales/es/gospel.json index f961602a0..4c3ce23df 100644 --- a/library/i18n/locales/es/gospel.json +++ b/library/i18n/locales/es/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Recursos del evangelio por idioma", "chartsTitle": "Recursos del evangelio — por idioma", "loadingTables": "Cargando tablas de recursos…" + }, + "list": { + "noText": "Sin texto bíblico", + "hasScripture": "Tiene Escrituras", + "allLanguages": "Todos los idiomas", + "peopleGroup": "pueblo", + "peopleGroups": "pueblos", + "scriptureHere": "Escrituras en este idioma" + }, + "columns": { + "People group": "Pueblo", + "Country": "País", + "Region": "Región", + "Language": "Idioma", + "Population": "Población", + "Religion": "Religión", + "Gospel access": "Acceso al evangelio", + "Resources": "Recursos", + "Scripture": "Escrituras", + "Bible video": "Video bíblico", + "Gospel presentation": "Presentación del evangelio", + "Countries": "Países", + "People groups": "Pueblos" } } diff --git a/library/i18n/locales/fr/gospel.json b/library/i18n/locales/fr/gospel.json index 3058432b6..859555883 100644 --- a/library/i18n/locales/fr/gospel.json +++ b/library/i18n/locales/fr/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Ressources de l'Évangile par langue", "chartsTitle": "Ressources de l'Évangile — par langue", "loadingTables": "Chargement des tableaux…" + }, + "list": { + "noText": "Pas de texte biblique", + "hasScripture": "A les Écritures", + "allLanguages": "Toutes les langues", + "peopleGroup": "peuple", + "peopleGroups": "peuples", + "scriptureHere": "Écritures dans cette langue" + }, + "columns": { + "People group": "Peuple", + "Country": "Pays", + "Region": "Région", + "Language": "Langue", + "Population": "Population", + "Religion": "Religion", + "Gospel access": "Accès à l'Évangile", + "Resources": "Ressources", + "Scripture": "Écritures", + "Bible video": "Vidéo biblique", + "Gospel presentation": "Présentation de l'Évangile", + "Countries": "Pays", + "People groups": "Peuples" } } diff --git a/library/i18n/locales/hi/gospel.json b/library/i18n/locales/hi/gospel.json index 71d8fa617..84f443ee7 100644 --- a/library/i18n/locales/hi/gospel.json +++ b/library/i18n/locales/hi/gospel.json @@ -128,5 +128,28 @@ "listTitle": "भाषा अनुसार सुसमाचार संसाधन", "chartsTitle": "सुसमाचार संसाधन — भाषा अनुसार", "loadingTables": "संसाधन तालिकाएँ लोड हो रही हैं…" + }, + "list": { + "noText": "कोई लिखित शास्त्र नहीं", + "hasScripture": "शास्त्र उपलब्ध", + "allLanguages": "सभी भाषाएँ", + "peopleGroup": "जनसमूह", + "peopleGroups": "जनसमूह", + "scriptureHere": "इस भाषा में पवित्र शास्त्र" + }, + "columns": { + "People group": "जनसमूह", + "Country": "देश", + "Region": "क्षेत्र", + "Language": "भाषा", + "Population": "जनसंख्या", + "Religion": "धर्म", + "Gospel access": "सुसमाचार पहुँच", + "Resources": "संसाधन", + "Scripture": "पवित्र शास्त्र", + "Bible video": "बाइबल वीडियो", + "Gospel presentation": "सुसमाचार प्रस्तुति", + "Countries": "देश", + "People groups": "जनसमूह" } } diff --git a/library/i18n/locales/it/gospel.json b/library/i18n/locales/it/gospel.json index 6e1b56f82..f1139e939 100644 --- a/library/i18n/locales/it/gospel.json +++ b/library/i18n/locales/it/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Risorse del Vangelo per lingua", "chartsTitle": "Risorse del Vangelo — per lingua", "loadingTables": "Caricamento delle tabelle…" + }, + "list": { + "noText": "Nessun testo biblico", + "hasScripture": "Ha le Scritture", + "allLanguages": "Tutte le lingue", + "peopleGroup": "popolo", + "peopleGroups": "popoli", + "scriptureHere": "Scritture in questa lingua" + }, + "columns": { + "People group": "Popolo", + "Country": "Paese", + "Region": "Regione", + "Language": "Lingua", + "Population": "Popolazione", + "Religion": "Religione", + "Gospel access": "Accesso al Vangelo", + "Resources": "Risorse", + "Scripture": "Scritture", + "Bible video": "Video biblico", + "Gospel presentation": "Presentazione del Vangelo", + "Countries": "Paesi", + "People groups": "Popoli" } } diff --git a/library/i18n/locales/pt/gospel.json b/library/i18n/locales/pt/gospel.json index 0010ab272..d8e40dda8 100644 --- a/library/i18n/locales/pt/gospel.json +++ b/library/i18n/locales/pt/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Recursos do evangelho por idioma", "chartsTitle": "Recursos do evangelho — por idioma", "loadingTables": "Carregando tabelas de recursos…" + }, + "list": { + "noText": "Sem texto bíblico", + "hasScripture": "Tem Escrituras", + "allLanguages": "Todos os idiomas", + "peopleGroup": "povo", + "peopleGroups": "povos", + "scriptureHere": "Escrituras neste idioma" + }, + "columns": { + "People group": "Povo", + "Country": "País", + "Region": "Região", + "Language": "Idioma", + "Population": "População", + "Religion": "Religião", + "Gospel access": "Acesso ao evangelho", + "Resources": "Recursos", + "Scripture": "Escrituras", + "Bible video": "Vídeo bíblico", + "Gospel presentation": "Apresentação do evangelho", + "Countries": "Países", + "People groups": "Povos" } } diff --git a/library/i18n/locales/ro/gospel.json b/library/i18n/locales/ro/gospel.json index 611b3d866..454866bce 100644 --- a/library/i18n/locales/ro/gospel.json +++ b/library/i18n/locales/ro/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Resurse ale Evangheliei pe limbi", "chartsTitle": "Resurse ale Evangheliei — pe limbi", "loadingTables": "Se încarcă tabelele…" + }, + "list": { + "noText": "Fără text biblic", + "hasScripture": "Are Scripturi", + "allLanguages": "Toate limbile", + "peopleGroup": "popor", + "peopleGroups": "popoare", + "scriptureHere": "Scripturi în această limbă" + }, + "columns": { + "People group": "Popor", + "Country": "Țară", + "Region": "Regiune", + "Language": "Limbă", + "Population": "Populație", + "Religion": "Religie", + "Gospel access": "Acces la Evanghelie", + "Resources": "Resurse", + "Scripture": "Scripturi", + "Bible video": "Video biblic", + "Gospel presentation": "Prezentarea Evangheliei", + "Countries": "Țări", + "People groups": "Popoare" } } diff --git a/library/i18n/locales/ru/gospel.json b/library/i18n/locales/ru/gospel.json index a7f9d6c15..5767bbc27 100644 --- a/library/i18n/locales/ru/gospel.json +++ b/library/i18n/locales/ru/gospel.json @@ -128,5 +128,28 @@ "listTitle": "Ресурсы Евангелия по языкам", "chartsTitle": "Ресурсы Евангелия — по языкам", "loadingTables": "Загрузка таблиц ресурсов…" + }, + "list": { + "noText": "Нет текста Писания", + "hasScripture": "Есть Писание", + "allLanguages": "Все языки", + "peopleGroup": "народ", + "peopleGroups": "народов", + "scriptureHere": "Писание на этом языке" + }, + "columns": { + "People group": "Народ", + "Country": "Страна", + "Region": "Регион", + "Language": "Язык", + "Population": "Численность", + "Religion": "Религия", + "Gospel access": "Доступ к Евангелию", + "Resources": "Ресурсы", + "Scripture": "Писание", + "Bible video": "Библейское видео", + "Gospel presentation": "Изложение Евангелия", + "Countries": "Страны", + "People groups": "Народы" } } diff --git a/library/i18n/locales/zh/gospel.json b/library/i18n/locales/zh/gospel.json index f3078c96c..1f5558345 100644 --- a/library/i18n/locales/zh/gospel.json +++ b/library/i18n/locales/zh/gospel.json @@ -128,5 +128,28 @@ "listTitle": "按语言列出的福音资源", "chartsTitle": "福音资源 — 按语言", "loadingTables": "正在加载资源表…" + }, + "list": { + "noText": "没有文字圣经", + "hasScripture": "有圣经", + "allLanguages": "全部语言", + "peopleGroup": "个族群", + "peopleGroups": "个族群", + "scriptureHere": "本语言的圣经" + }, + "columns": { + "People group": "族群", + "Country": "国家", + "Region": "地区", + "Language": "语言", + "Population": "人口", + "Religion": "宗教", + "Gospel access": "福音可及性", + "Resources": "资源", + "Scripture": "圣经", + "Bible video": "圣经视频", + "Gospel presentation": "福音简介", + "Countries": "国家", + "People groups": "族群" } } diff --git a/package.json b/package.json index b293642f0..97eca892c 100644 --- a/package.json +++ b/package.json @@ -6,310 +6,310 @@ "description": "A portable bundler that generates embeddable JAMstack maps — one library, many map bundles that embed on any site, compose into a lazy-loaded SPA, or wrap a Capacitor Android/iOS app.", "type": "module", "scripts": { - "dev": "bun x vite", - "build": "bash internal/build-lock.sh", - "build:watch": "bun x vite build --watch", - "preview": "bun x vite preview", - "clean": "node internal/clean.mjs", - "build:bundle": "bun x vite build", - "tree": "node internal/generate-tree.mjs", - "gallery": "node internal/generate-gallery.mjs", - "check:drop": "node internal/drop-portability-check.mjs", - "check:parse": "node internal/inline-parse-check.mjs", - "verify": "node library/scripts/research/verify-all.mjs", - "verify:full": "node library/scripts/research/verify-all.mjs --full", - "serve": "node internal/serve-strict.mjs", - "serve:shell": "python3 -m http.server 4174 --bind 127.0.0.1 --directory library/layouts/app-wrapper-material", - "cap:sync": "./node_modules/.bin/cap sync android", - "cap:verify": "test -f deployment/capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)", - "check:tauri": "node internal/check-tauri-config.mjs", - "tauri:build": "./node_modules/.bin/tauri build", "apk:debug": "bun run build && bun run cap:sync && bun run app:start-page && bun run cap:verify && cd deployment/capacitor/android && ./gradlew assembleDebug", "apk:install": "adb install -r deployment/capacitor/android/app/build/outputs/apk/debug/app-debug.apk", - "phone": "bun run apk:debug && bun run apk:install", - "shell": "node internal/install-shell.mjs --no-token", - "posters": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", - "posters:one": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", - "posters:shoot": "cd library/scripts/posters && DSF=2 node shoot.mjs", - "posters:gallery": "cd library/scripts/posters && node build-gallery.mjs && node build-hd-gallery.mjs", - "posters:export": "cd library/scripts/posters && node export-public.mjs", - "posters:previews": "cd library/scripts/posters && python3 make-previews.py", - "packets": "cd library/scripts/posters && VITE_API_BASE_URL=https://pray.doxa.life node packet.mjs", - "packet-fixture": "node internal/generate-packet-fixture.mjs", - "check:packet-fixture": "node internal/generate-packet-fixture.mjs --check", - "packet:ci": "node internal/build-packet-fixtures.mjs", - "token": "node internal/write-token.mjs", - "smoke": "node internal/smoke.mjs", - "smoke:embeds": "node internal/embed-smoke.mjs", - "smoke:gallery": "node internal/plugin-gallery-smoke.mjs", - "smoke:playground": "bun run smoke:gallery", - "smoke:share": "node internal/share-roundtrip-smoke.mjs", - "probe:legend-columns": "node internal/legend-columns-probe.mjs", - "sync:dropin": "node internal/sync-dropin.mjs", - "deploy:dev": "bun run build && node internal/sync-dropin.mjs --from-config --prune", - "check:public-hygiene": "node internal/public-hygiene-check.mjs", - "check:label-joins": "node internal/check-label-joins.mjs", + "app:start-page": "node internal/set-app-start-page.mjs", + "build": "bash internal/build-lock.sh", + "build:bundle": "bun x vite build", + "build:bundles": "node internal/run-parallel.mjs --bundles", + "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", + "build:watch": "bun x vite build --watch", + "cap:sync": "./node_modules/.bin/cap sync android", + "cap:verify": "test -f deployment/capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)", + "capability-exemplars": "node internal/generate-capability-exemplars.mjs", + "check:a11y-button-names": "node internal/check-a11y-button-names.mjs", + "check:app-start-page": "node internal/set-app-start-page.mjs --check", + "check:backlog-refs": "node internal/check-backlog-refs.mjs", + "check:backlog-status": "node internal/check-backlog-status-enum.mjs", + "check:basemap-labels": "node internal/check-basemap-labels-localized.mjs", + "check:capability-citations": "node internal/check-capability-citations.mjs", + "check:capability-exemplars": "node internal/check-capability-exemplars.mjs", + "check:card-deck": "node internal/check-card-deck.mjs", + "check:catalog-currency": "node internal/check-catalog-currency.mjs", + "check:catalog-identity": "node internal/check-catalog-identity.mjs", + "check:click-sheet": "node plugin-designs/gospel-resources/scripts/verify-gospel-click-sheet.mjs", + "check:composed-filters": "node internal/check-gospel-composed-filters.mjs", + "check:composite": "node internal/check-composite-freshness.mjs", + "check:composite-embed-knobs": "node internal/check-composite-embed-knobs.mjs", + "check:composite-reg": "node internal/check-composite-registration.mjs", + "check:composite-srcs": "node internal/check-composite-srcs.mjs", + "check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs", + "check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison", + "check:csv-shape": "node internal/check-csv-export-shape.mjs", + "check:csv-shape:self-test": "node internal/check-csv-export-shape.mjs --self-test", + "check:deck-click": "node internal/check-deck-click.mjs", + "check:dep-names": "node internal/check-dependency-names.mjs", + "check:descriptions": "node internal/check-profile-descriptions.mjs", + "check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs", + "check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test", + "check:done-evidence": "node internal/check-done-evidence.mjs", + "check:drop": "node internal/drop-portability-check.mjs", "check:dxlife": "node internal/check-dxlife-untouched.mjs", + "check:embed-base": "node internal/check-embed-base-resolution.mjs", + "check:embed-basemap": "node internal/check-embed-basemap-paints.mjs", + "check:embed-targets": "node internal/check-embed-targets-exist.mjs", + "check:embed-targets:self-test": "node internal/check-embed-targets-exist.mjs --self-test", + "check:english-leaks": "node internal/check-gospel-english-leaks.mjs", "check:facet-cascade": "node internal/check-facet-cascade.mjs", + "check:facet-cascade:offline": "node internal/check-facet-cascade.mjs --offline", "check:facet-cascade:self-test": "node internal/check-facet-cascade.mjs --self-test", - "check:facet-one-field": "node internal/check-facet-one-field.mjs", - "check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test", "check:facet-changes-map": "node internal/check-facet-changes-map.mjs", "check:facet-changes-map:built": "sh -c 'BASE_URL=${BASE_URL:-http://127.0.0.1:4173} PROBE_PATH=${PROBE_PATH:-/doxa-maps/research-dashboard/research-dashboard/index.html} exec node internal/check-facet-changes-map.mjs'", "check:facet-changes-map:self-test": "node internal/check-facet-changes-map.mjs --self-test", - "check:heartbeat": "node internal/check-heartbeat-threshold.mjs", - "check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test", - "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", - "check:card-deck": "node internal/check-card-deck.mjs", - "check:poster-raster": "node internal/check-poster-raster.mjs", - "check:deck-click": "node internal/check-deck-click.mjs", - "check:i18n-parity": "node internal/check-i18n-parity.mjs", - "check:locale-reachable": "node internal/check-locale-reachable.mjs", - "check:poster-prints": "node internal/check-poster-prints.mjs", - "check:provider-switch": "node internal/check-provider-switch.mjs", - "check:legend-export-reachable": "node internal/check-legend-export-reachable.mjs", - "check:poster-card-fits": "node internal/check-poster-card-fits.mjs", - "check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs", - "check:share-policy": "node internal/check-share-policy.mjs", - "check:zone-census": "node internal/check-zone-census.mjs", - "check:print-geometry": "node internal/check-print-geometry.mjs", - "check:packet-browser": "node internal/check-packet-browser.mjs", - "check:packet-render": "node internal/check-packet-render.mjs", - "check:template-literal": "node internal/template-literal-check.mjs", - "verify:map-paint": "node internal/verify-map-paint.mjs", - "verify:gospel-map": "node internal/verify-gospel-map.mjs", - "check:sri": "node internal/sri-consistency-check.mjs", - "fix:sri": "node internal/fix-sri.mjs", - "check:composite": "node internal/check-composite-freshness.mjs", - "check:shots": "node internal/check-shot-freshness.mjs", - "check:library-index": "node internal/check-library-index.mjs", - "check:quoted-scripts": "node internal/check-quoted-scripts.mjs", - "capability-exemplars": "node internal/generate-capability-exemplars.mjs", - "check:a11y-button-names": "node internal/check-a11y-button-names.mjs", - "check:catalog-identity": "node internal/check-catalog-identity.mjs", - "check:catalog-currency": "node internal/check-catalog-currency.mjs", - "check:capability-exemplars": "node internal/check-capability-exemplars.mjs", - "check:capability-citations": "node internal/check-capability-citations.mjs", - "check:shot-distinctness": "node internal/check-shot-distinctness.mjs", - "check:shot-fidelity": "node internal/check-shot-fidelity.mjs", - "shots": "node internal/readme-shots.mjs", - "library-index": "node internal/generate-library-index.mjs", - "check:done-evidence": "node internal/check-done-evidence.mjs", - "check:wp-embed": "php deployment/wordpress/tests/wp-embed-contract-test.php", - "check:mobile": "bun run cap:sync && bun run cap:verify", - "check:settled": "node internal/settled-tree-gate.mjs", - "verify:5-caps": "node internal/verify-5-capabilities.mjs", - "verify:gate1": "node internal/acceptance/big-picture-acceptance.mjs", - "verify:interop": "node internal/acceptance/interop-harness.mjs", - "verify:interop:self-test": "node internal/acceptance/interop-harness.mjs --self-test", - "verify:interop:self-test:camera-freeze": "node internal/acceptance/interop-harness.mjs --self-test --self-test-mode=camera-freeze", - "check:descriptions": "node internal/check-profile-descriptions.mjs", - "check:no-abs-paths": "node internal/check-no-absolute-paths.mjs", - "check:no-codename": "node internal/check-no-internal-codename.mjs", - "check:dep-names": "node internal/check-dependency-names.mjs", - "check:embed-targets": "node internal/check-embed-targets-exist.mjs", - "check:composite-srcs": "node internal/check-composite-srcs.mjs", - "check:nested-token": "node internal/check-nested-token-delivery.mjs", - "check:nav-reachable": "node internal/check-nav-reachable-mobile.mjs", + "check:facet-one-field": "node internal/check-facet-one-field.mjs", + "check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test", + "check:filter-width": "node internal/check-filter-width-stable.mjs", "check:fits-screen": "node internal/check-fits-the-screen.mjs", "check:fits-screen:self-test": "node internal/check-fits-the-screen.mjs --self-test", "check:fits-screen:self-test:b": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=b", "check:fits-screen:self-test:c": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=c", "check:fits-screen:self-test:d": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=d", "check:fits-screen:self-test:e": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=e", - "verify:browser-all": "node internal/verify-browser-all.mjs", - "check:shot-fonts": "node internal/check-shot-fonts.mjs", - "smoke:share-all": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/share-roundtrip-smoke.mjs --url \"$B/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/all-people-groups/all-people-groups/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/country-posters-mfe/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/demo-map/demo-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-research-map/research-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-simple-map/doxa-simple-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/gospel-resources/resources-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-dashboard/research-dashboard/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/my-upg-100-list/upg-100-mfe/index.html?tk=$MAPBOX_TOKEN\" --browser chromium'", - "smoke:legend-info": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-legend-info-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", - "smoke:aria-labels": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-aria-labels-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", - "check:sfc-parse": "node internal/check-sfc-script-parses.mjs", - "check:url-resolver": "node internal/check-url-resolver.mjs", - "check:studio-all": "node internal/check-studio-every-plugin.mjs", - "check:basemap-labels": "node internal/check-basemap-labels-localized.mjs", - "check:vision-card-numbers": "node internal/check-vision-card-numbers.mjs", - "check:map-embed-cells": "node internal/check-map-embed-cells.mjs", - "check:map-unmount-race": "node internal/check-map-unmount-guard.mjs", - "check:map-unmount-mid-init": "node internal/check-map-unmount-mid-init.mjs", - "smoke:studio": "node internal/smoke-studio.mjs", - "check:manifest-covers": "node internal/check-manifest-covers-discovery.mjs", - "check:csv-shape": "node internal/check-csv-export-shape.mjs", - "check:csv-shape:self-test": "node internal/check-csv-export-shape.mjs --self-test", - "check:verification-tags": "node internal/check-verification-tags.mjs", "check:fixed-anchoring": "node internal/check-fixed-anchoring.mjs", - "check:scrim-viewport": "node internal/check-scrim-covers-viewport.mjs", - "check:scrim-viewport:self-test": "node internal/check-scrim-covers-viewport.mjs --self-test", - "check:no-public-binds": "node internal/check-no-public-binds.mjs", - "check:facet-cascade:offline": "node internal/check-facet-cascade.mjs --offline", - "check:shot-fidelity:strict": "node internal/check-shot-fidelity.mjs --strict", - "check:embed-targets:self-test": "node internal/check-embed-targets-exist.mjs --self-test", - "verify:browser-all:plugin-gallery": "node internal/verify-browser-all.mjs --plugin-gallery", - "check:script-flags": "node internal/check-script-flags-reachable.mjs", - "check:shell-scroll": "node internal/verify-shell-scroll.mjs", - "check:no-orphan-gates": "node internal/check-no-orphan-gates.mjs", - "check:one-palette": "node internal/check-one-palette.mjs", - "check:kpi-unit": "node internal/check-kpi-unit-once.mjs", - "check:tab-radius": "node internal/check-tab-radius-token.mjs", - "check:tab-radius:self-test": "node internal/check-tab-radius-token.mjs --self-test", - "check:synced-css": "node internal/check-synced-css-copies.mjs", - "check:composite-reg": "node internal/check-composite-registration.mjs", - "check:filter-width": "node internal/check-filter-width-stable.mjs", "check:gallery-embed": "node internal/check-research-gallery-embed.mjs", - "check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js", "check:gallery-embed:iframe": "node internal/check-research-gallery-embed.mjs --mode iframe", - "check:gallery-embed:maplibre": "node internal/check-research-gallery-embed.mjs --provider maplibre", + "check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js", "check:gallery-embed:live-cors": "node internal/check-research-gallery-embed.mjs --live-cors", - "check:prayer-walk-planner": "node internal/check-prayer-walk-planner.mjs", - "check:spa-tab-order": "node internal/check-spa-tab-order.mjs", - "check:backlog-status": "node internal/check-backlog-status-enum.mjs", - "check:backlog-refs": "node internal/check-backlog-refs.mjs", - "check:mjs-parse": "node internal/check-mjs-parses.mjs", - "check:snapshot-guard": "node internal/check-snapshot-guard.mjs", - "check:snapshot-guard:poison": "node internal/check-snapshot-guard.mjs --poison", - "check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs", - "check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison", - "check:click-sheet": "node plugin-designs/gospel-resources/scripts/verify-gospel-click-sheet.mjs", - "check:stale-sweep-claim": "node internal/check-no-stale-sweep-claim.mjs # a caveat that has stopped being true is a wrong number. The gospel surfaces carry a paragraph saying which pools were searched; while the GRN sweep ran they truthfully said part of it was unread, the sweep finished, and the sentence stayed for four more commits. The DATA decides here: undecided gap codes mean the claim is allowed, all-decided means it must go, so it re-arms itself for the next slow pool. Does NOT flag a claim rendered under a v-if on the pending count — that form expires on its own. Self-test: `the check:stale-sweep-claim:poison entry`. Static, sub-second.", - "check:surfaces-read-the-tier": "node internal/check-surfaces-read-the-tier.mjs", - "check:surfaces-read-the-tier:poison": "node internal/check-surfaces-read-the-tier.mjs --poison", - "check:link-rot": "node internal/check-link-rot-sample.mjs", - "check:embed-basemap": "node internal/check-embed-basemap-paints.mjs", - "check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test", - "serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public", - "check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs", - "check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test", - "check:legend-source": "node internal/check-legend-matches-source.mjs", - "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 check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source check:backlog-refs check:composite-embed-knobs check:linked-vocabulary", - "build:bundles": "node internal/run-parallel.mjs --bundles", - "check:composite-embed-knobs": "node internal/check-composite-embed-knobs.mjs", - "check:linked-vocabulary": "node plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs", - "check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs", - "check:tabs-localized": "node internal/check-tabs-localized.mjs", - "check:packets-all": "node internal/check-packets-all.mjs", - "check:poster-print-fallback": "node internal/check-poster-print-fallback.mjs", - "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", - "check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs", - "check:upg-map-data": "node internal/check-upg-map-data-painted.mjs", - "check:published": "node internal/check-published-matches-build.mjs", - "check:shared-reached": "node internal/check-shared-component-reached-bundles.mjs", - "check:tree-weight": "node internal/check-published-tree-weight.mjs", + "check:gallery-embed:maplibre": "node internal/check-research-gallery-embed.mjs --provider maplibre", + "check:gospel-all": "node internal/check-gospel-all.mjs", + "check:heartbeat": "node internal/check-heartbeat-threshold.mjs", + "check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test", "check:i18n-never-translated": "node internal/check-i18n-never-translated.mjs", - "check:embed-base": "node internal/check-embed-base-resolution.mjs", - "check:composed-filters": "node internal/check-gospel-composed-filters.mjs", - "app:start-page": "node internal/set-app-start-page.mjs", - "check:app-start-page": "node internal/set-app-start-page.mjs --check", + "check:i18n-parity": "node internal/check-i18n-parity.mjs", + "check:kpi-unit": "node internal/check-kpi-unit-once.mjs", + "check:label-joins": "node internal/check-label-joins.mjs", "check:leaves-mount": "node internal/check-gospel-leaves-mount.mjs", "check:leaves-mount:poison": "node internal/check-gospel-leaves-mount.mjs --poison", - "check:gospel-all": "node internal/check-gospel-all.mjs", + "check:legend-export-reachable": "node internal/check-legend-export-reachable.mjs", + "check:legend-source": "node internal/check-legend-matches-source.mjs", + "check:library-index": "node internal/check-library-index.mjs", + "check:link-rot": "node internal/check-link-rot-sample.mjs", + "check:linked-vocabulary": "node plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs", + "check:locale-reachable": "node internal/check-locale-reachable.mjs", + "check:manifest-covers": "node internal/check-manifest-covers-discovery.mjs", + "check:map-embed-cells": "node internal/check-map-embed-cells.mjs", + "check:map-unmount-mid-init": "node internal/check-map-unmount-mid-init.mjs", + "check:map-unmount-race": "node internal/check-map-unmount-guard.mjs", + "check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs", + "check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs", + "check:mjs-parse": "node internal/check-mjs-parses.mjs", + "check:mobile": "bun run cap:sync && bun run cap:verify", + "check:nav-reachable": "node internal/check-nav-reachable-mobile.mjs", + "check:nested-token": "node internal/check-nested-token-delivery.mjs", + "check:no-abs-paths": "node internal/check-no-absolute-paths.mjs", + "check:no-codename": "node internal/check-no-internal-codename.mjs", "check:no-conflict-markers": "node internal/check-no-conflict-markers.mjs", "check:no-conflict-markers:poison": "node internal/check-no-conflict-markers.mjs --poison", - "check:english-leaks": "node internal/check-gospel-english-leaks.mjs", - "check:poster-wall-localised": "node internal/check-poster-wall-localised.mjs" + "check:no-orphan-gates": "node internal/check-no-orphan-gates.mjs", + "check:no-public-binds": "node internal/check-no-public-binds.mjs", + "check:one-palette": "node internal/check-one-palette.mjs", + "check:packet-browser": "node internal/check-packet-browser.mjs", + "check:packet-fixture": "node internal/generate-packet-fixture.mjs --check", + "check:packet-render": "node internal/check-packet-render.mjs", + "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", + "check:packets-all": "node internal/check-packets-all.mjs", + "check:parse": "node internal/inline-parse-check.mjs", + "check:poster-card-fits": "node internal/check-poster-card-fits.mjs", + "check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs", + "check:poster-print-fallback": "node internal/check-poster-print-fallback.mjs", + "check:poster-prints": "node internal/check-poster-prints.mjs", + "check:poster-raster": "node internal/check-poster-raster.mjs", + "check:poster-wall-localised": "node internal/check-poster-wall-localised.mjs", + "check:prayer-walk-planner": "node internal/check-prayer-walk-planner.mjs", + "check:print-geometry": "node internal/check-print-geometry.mjs", + "check:provider-switch": "node internal/check-provider-switch.mjs", + "check:public-hygiene": "node internal/public-hygiene-check.mjs", + "check:published": "node internal/check-published-matches-build.mjs", + "check:quoted-scripts": "node internal/check-quoted-scripts.mjs", + "check:scrim-viewport": "node internal/check-scrim-covers-viewport.mjs", + "check:scrim-viewport:self-test": "node internal/check-scrim-covers-viewport.mjs --self-test", + "check:script-flags": "node internal/check-script-flags-reachable.mjs", + "check:settled": "node internal/settled-tree-gate.mjs", + "check:sfc-parse": "node internal/check-sfc-script-parses.mjs", + "check:share-policy": "node internal/check-share-policy.mjs", + "check:shared-reached": "node internal/check-shared-component-reached-bundles.mjs", + "check:shell-scroll": "node internal/verify-shell-scroll.mjs", + "check:shot-distinctness": "node internal/check-shot-distinctness.mjs", + "check:shot-fidelity": "node internal/check-shot-fidelity.mjs", + "check:shot-fidelity:strict": "node internal/check-shot-fidelity.mjs --strict", + "check:shot-fonts": "node internal/check-shot-fonts.mjs", + "check:shots": "node internal/check-shot-freshness.mjs", + "check:snapshot-guard": "node internal/check-snapshot-guard.mjs", + "check:snapshot-guard:poison": "node internal/check-snapshot-guard.mjs --poison", + "check:spa-tab-order": "node internal/check-spa-tab-order.mjs", + "check:sri": "node internal/sri-consistency-check.mjs", + "check:stale-sweep-claim": "node internal/check-no-stale-sweep-claim.mjs # a caveat that has stopped being true is a wrong number. The gospel surfaces carry a paragraph saying which pools were searched; while the GRN sweep ran they truthfully said part of it was unread, the sweep finished, and the sentence stayed for four more commits. The DATA decides here: undecided gap codes mean the claim is allowed, all-decided means it must go, so it re-arms itself for the next slow pool. Does NOT flag a claim rendered under a v-if on the pending count — that form expires on its own. Self-test: `the check:stale-sweep-claim:poison entry`. Static, sub-second.", + "check:studio-all": "node internal/check-studio-every-plugin.mjs", + "check:surfaces-read-the-tier": "node internal/check-surfaces-read-the-tier.mjs", + "check:surfaces-read-the-tier:poison": "node internal/check-surfaces-read-the-tier.mjs --poison", + "check:synced-css": "node internal/check-synced-css-copies.mjs", + "check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test", + "check:tab-radius": "node internal/check-tab-radius-token.mjs", + "check:tab-radius:self-test": "node internal/check-tab-radius-token.mjs --self-test", + "check:tabs-localized": "node internal/check-tabs-localized.mjs", + "check:tauri": "node internal/check-tauri-config.mjs", + "check:template-literal": "node internal/template-literal-check.mjs", + "check:tree-weight": "node internal/check-published-tree-weight.mjs", + "check:upg-map-data": "node internal/check-upg-map-data-painted.mjs", + "check:url-resolver": "node internal/check-url-resolver.mjs", + "check:verification-tags": "node internal/check-verification-tags.mjs", + "check:vision-card-numbers": "node internal/check-vision-card-numbers.mjs", + "check:wp-embed": "php deployment/wordpress/tests/wp-embed-contract-test.php", + "check:zone-census": "node internal/check-zone-census.mjs", + "clean": "node internal/clean.mjs", + "deploy:dev": "bun run build && node internal/sync-dropin.mjs --from-config --prune", + "dev": "bun x vite", + "fix:sri": "node internal/fix-sri.mjs", + "gallery": "node internal/generate-gallery.mjs", + "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 check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source check:backlog-refs check:composite-embed-knobs check:linked-vocabulary", + "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", + "library-index": "node internal/generate-library-index.mjs", + "packet-fixture": "node internal/generate-packet-fixture.mjs", + "packet:ci": "node internal/build-packet-fixtures.mjs", + "packets": "cd library/scripts/posters && VITE_API_BASE_URL=https://pray.doxa.life node packet.mjs", + "phone": "bun run apk:debug && bun run apk:install", + "posters": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", + "posters:export": "cd library/scripts/posters && node export-public.mjs", + "posters:gallery": "cd library/scripts/posters && node build-gallery.mjs && node build-hd-gallery.mjs", + "posters:one": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", + "posters:previews": "cd library/scripts/posters && python3 make-previews.py", + "posters:shoot": "cd library/scripts/posters && DSF=2 node shoot.mjs", + "preview": "bun x vite preview", + "probe:legend-columns": "node internal/legend-columns-probe.mjs", + "serve": "node internal/serve-strict.mjs", + "serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public", + "serve:shell": "python3 -m http.server 4174 --bind 127.0.0.1 --directory library/layouts/app-wrapper-material", + "shell": "node internal/install-shell.mjs --no-token", + "shots": "node internal/readme-shots.mjs", + "smoke": "node internal/smoke.mjs", + "smoke:aria-labels": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-aria-labels-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", + "smoke:embeds": "node internal/embed-smoke.mjs", + "smoke:gallery": "node internal/plugin-gallery-smoke.mjs", + "smoke:legend-info": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-legend-info-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", + "smoke:playground": "bun run smoke:gallery", + "smoke:share": "node internal/share-roundtrip-smoke.mjs", + "smoke:share-all": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/share-roundtrip-smoke.mjs --url \"$B/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/all-people-groups/all-people-groups/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/country-posters-mfe/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/demo-map/demo-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-research-map/research-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-simple-map/doxa-simple-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/gospel-resources/resources-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-dashboard/research-dashboard/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/my-upg-100-list/upg-100-mfe/index.html?tk=$MAPBOX_TOKEN\" --browser chromium'", + "smoke:studio": "node internal/smoke-studio.mjs", + "sync:dropin": "node internal/sync-dropin.mjs", + "tauri:build": "./node_modules/.bin/tauri build", + "token": "node internal/write-token.mjs", + "tree": "node internal/generate-tree.mjs", + "verify": "node library/scripts/research/verify-all.mjs", + "verify:5-caps": "node internal/verify-5-capabilities.mjs", + "verify:browser-all": "node internal/verify-browser-all.mjs", + "verify:browser-all:plugin-gallery": "node internal/verify-browser-all.mjs --plugin-gallery", + "verify:full": "node library/scripts/research/verify-all.mjs --full", + "verify:gate1": "node internal/acceptance/big-picture-acceptance.mjs", + "verify:gospel-map": "node internal/verify-gospel-map.mjs", + "verify:interop": "node internal/acceptance/interop-harness.mjs", + "verify:interop:self-test": "node internal/acceptance/interop-harness.mjs --self-test", + "verify:interop:self-test:camera-freeze": "node internal/acceptance/interop-harness.mjs --self-test --self-test-mode=camera-freeze", + "verify:map-paint": "node internal/verify-map-paint.mjs" }, "//scripts": { "_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.", - "gallery": "writes public/__gallery/index.html — the ARCHIVED bundle-index gallery, from public/doxa-maps/manifest.json. It is NO LONGER the root page: as of 2026-09-22 `/` mounts in BOTH trees (the built root page is written by `bun run tree`), and the gallery is the opt-in index at /__gallery. Archived, not deleted — the dev server imports renderGallery()/readBuildConfig() from this module.", - "serve": "serves public/ the way PRODUCTION does: a DIRECTORY request returns 404, no index fallback. This was python3 -m http.server, which DID resolve a directory to index.html while the live static layer does not — so gates passed on URLs that 404 live (measured 2026-09-23; it cost a false \"publish failed\" reading and hid a bad URL in verify-shell-scroll for a whole flight). Loopback only, same as before. Override port/host with PORT/HOST.", - "serve:shell": "LOOPBACK ONLY, same reason as `serve` above — a local preview server has no business answering on a public interface.", "cap:sync": "NOT `bunx` — bunx is not on PATH in this environment (command not found, exit 127), which broke cap:sync -> apk:debug -> phone at step one. The CLI IS installed locally (@capacitor/cli 7.6.9); invoke the local bin directly. The rest of package.json already uses the spaced `bun x` form.", - "shell": "OPTIONAL, not part of build: overlays a layouts/ wrapper onto public/ for local use (the served root is the generated index.html); --no-token so the local credential is never copied into the served root", - "posters:one": "ONLY= to build one", - "packets": "the prayer packets. READS out/posters/.html for page 1's map, which `bun run posters` writes — without it the packet still builds and page 1 is simply BLANK, so the script now warns per-zone AND in its summary. PACKET_LANG= for one language, ONLY= for one zone, NOPHOTOS=1 to skip the photo fetch.", - "packet-fixture": "regenerate the committed records subset (smallest zone, image_url stripped) that lets CI build a packet at all — out/ is gitignored, so a clean checkout has neither the records cache nor any packet.", - "check:packet-fixture": "the committed fixture must still match clusters.json and must never carry image_url (CI would fetch 43 photos from someone else's CDN).", - "packet:ci": "build EVERY committed fixture, no network, each renamed to its own output so they coexist. Was one fixture (tibet); packet.mjs names its output for the ZONE not the fixture, so sequential builds overwrote each other and check:packets-all saw a single SHAPE while reporting success. The fixtures are chosen at the PER_PAGE=20 page boundary — exactly 20, 21, and 1 group — because laos (95) and tibet (43) are both mid-range remainder cases and the boundary itself had never rendered.", - "deploy:dev": "destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST= overrides the destination", + "capability-exemplars": "regenerate documentation/reference-capability-exemplars.md — the DERIVED answer to \"which bundle is the capability-N example?\", a question answered WRONGLY in three separate artifacts (a gate, the README, the /plugin-designer skill).", + "check:a11y-button-names": "every VISIBLE button must have an accessible name (aria-label / aria-labelledby / visible text — a `title` is a TOOLTIP and does NOT count). Measures the RENDERED DOM through shadow roots: a source grep cannot answer this (132 `