Merge development into repo/print-first-poster

This commit is contained in:
Doxa Maps 2026-10-02 19:14:20 +00:00
commit 9d0a818768
7 changed files with 43 additions and 19 deletions

View file

@ -20,6 +20,7 @@
* D. posterverse.meta.json `composes` == the bundle/profile of every row whose bundle exists in
* plugin-designs/ (two-sided: nothing declared that the rail cannot show, nothing shown undeclared).
* E. strings: every locale carries every English key, and every key the shell reads exists in English.
* F. a rail label named in LABEL_FOLLOWS equals that plugin's own catalogue title, in every locale.
*
* Headless, no browser. Exit 0 pass · 1 fail · 75 not measured (no built manifest).
*/
@ -40,10 +41,14 @@ const GATED = 'key-cities'
// Rows whose plugin is a natural-height page: mounted unpinned so the pane scrolls them. Named, because a
// pinned scroll page traps its own content and nothing else would notice.
const EXPECT_SCROLL = ['bless-india-posters']
// Rail labels that must EQUAL the plugin's own title (vocabulary follows the plugin), per locale:
// row id -> [catalogue area file under library/i18n/locales/<locale>/, key inside it]. Copied, not read at
// runtime: naming the area from the shell would ship the plugin's whole catalogue inside posterverse.js.
const LABEL_FOLLOWS = { 'bless-india-posters': ['blessIndiaPosters.json', 'title'] }
const sameList = (a, b) => a.length === b.length && a.every((x, i) => x === b[i])
export function checkShelf({ SECTIONS, TABS, NEVER, resolveTabs, manifest, meta, bundleExists, STRINGS }) {
export function checkShelf({ SECTIONS, TABS, NEVER, resolveTabs, manifest, meta, bundleExists, STRINGS, pluginTitles = {} }) {
const errs = []
const notes = []
// A. exact membership, per section, in order
@ -104,6 +109,14 @@ export function checkShelf({ SECTIONS, TABS, NEVER, resolveTabs, manifest, meta,
const v = STRINGS[l][k]
if (typeof v !== 'string' || !v.trim()) errs.push(`E: ${l} is missing "${k}"`)
}
// F. labels that follow a plugin's own title, per locale
for (const [id, byLocale] of Object.entries(pluginTitles)) {
for (const [l, want] of Object.entries(byLocale)) {
const got = (STRINGS[l] || {})['tab.' + id]
if (want === undefined) errs.push(`F: ${l}: the plugin's own title for ${id} is missing from its catalogue`)
else if (got !== want) errs.push(`F: ${l}: tab.${id} is ${JSON.stringify(got)}, the plugin's own title is ${JSON.stringify(want)}`)
}
}
return { errs, notes, counts: { rows: TABS.length, resolved: resolved.length, composes: composes.length, locales: locales.length, keys: Object.keys(en).length } }
}
@ -115,7 +128,17 @@ async function load() {
const mf = join(ROOT, 'public', TREE_NAME, 'manifest.json')
const manifest = existsSync(mf) ? JSON.parse(readFileSync(mf, 'utf8')) : null
const bundleExists = (b, p) => existsSync(join(ROOT, 'plugin-designs', b, `${p}.vue`))
return { ...tabs, STRINGS, meta, manifest, bundleExists }
const pluginTitles = {}
for (const [id, [file, key]] of Object.entries(LABEL_FOLLOWS)) {
pluginTitles[id] = {}
for (const l of Object.keys(STRINGS)) {
const f = join(ROOT, 'library/i18n/locales', l, file)
let v
try { const j = JSON.parse(readFileSync(f, 'utf8')); v = key.split('.').reduce((o, k) => (o == null ? o : o[k]), j) } catch { v = undefined }
pluginTitles[id][l] = v
}
}
return { ...tabs, STRINGS, meta, manifest, bundleExists, pluginTitles }
}
function selfTest(base) {
@ -130,6 +153,7 @@ function selfTest(base) {
['C: a composite row falls back to a leaf', (c) => { c.manifest = clone(c.manifest); const b = c.manifest.bundles.find((x) => x.bundle === 'my-upg-100-list'); b.profiles = b.profiles.filter((p) => p.profile !== 'upg-100-mfe') }, /^C: row upg-100 opens/],
['D: composes drift', (c) => { c.meta = { ...c.meta, composes: c.meta.composes.slice(1) } }, /^D: the rail shows/],
['D: composes a ref the rail cannot show', (c) => { c.meta = { ...c.meta, composes: [...c.meta.composes, 'key-cities/key-cities'] } }, /^D: meta composes/],
['F: a rail label drifts from the plugin title', (c) => { c.STRINGS = clone(c.STRINGS); c.STRINGS.ar['tab.bless-india-posters'] = 'باركوا الهند: ملصقات' }, /^F: ar: tab\.bless-india-posters is/],
['E: a locale loses a key', (c) => { c.STRINGS = clone(c.STRINGS); delete c.STRINGS.ar['tab.upg-100'] }, /^E: ar is missing "tab.upg-100"/],
['E: a row without an English label', (c) => { c.STRINGS = clone(c.STRINGS); delete c.STRINGS.en['tab.bible-posters'] }, /^E: the shell reads "tab.bible-posters"/],
]

View file

@ -35,7 +35,7 @@ export const STRINGS = {
'tab.poster-design': 'Diseño de carteles', 'sub.poster-design': 'todos los estilos y tamaños de cartel',
'tab.wall-of-people-groups': 'Muro de los pueblos',
'tab.bible-posters': 'Carteles bíblicos',
'tab.bless-india-posters': 'Bendigan a la India: carteles',
'tab.bless-india-posters': 'Pósteres «Bendice a la India»',
experimentalSub: 'mapa experimental', lab: 'beta',
loading: 'Cargando {label}…', loadFailed: 'No se pudo cargar {label}.',
manifestHint: 'Esta pestaña se compone a partir de manifest.json: compila el árbol o sirve el árbol compilado para cargarla.',
@ -50,7 +50,7 @@ export const STRINGS = {
'tab.poster-design': 'Conception d’affiches', 'sub.poster-design': 'tous les styles et formats d’affiche',
'tab.wall-of-people-groups': 'Mur des peuples',
'tab.bible-posters': 'Affiches bibliques',
'tab.bless-india-posters': 'Bénissez l’Inde : affiches',
'tab.bless-india-posters': 'Affiches « Bénis l’Inde »',
experimentalSub: 'carte expérimentale', lab: 'bêta',
loading: 'Chargement de {label}…', loadFailed: 'Impossible de charger {label}.',
manifestHint: "Cet onglet est composé à partir de manifest.json : construisez l'arborescence ou servez l'arborescence construite pour le charger.",
@ -65,7 +65,7 @@ export const STRINGS = {
'tab.poster-design': 'Postergestaltung', 'sub.poster-design': 'alle Posterstile und -größen',
'tab.wall-of-people-groups': 'Wand der Volksgruppen',
'tab.bible-posters': 'Bibelposter',
'tab.bless-india-posters': 'Segnet Indien: Poster',
'tab.bless-india-posters': '„Segne Indien“-Poster',
experimentalSub: 'experimentelle Karte', lab: 'Beta',
loading: '{label} wird geladen…', loadFailed: '{label} konnte nicht geladen werden.',
manifestHint: 'Dieser Tab wird aus manifest.json zusammengesetzt – bauen Sie den Baum oder stellen Sie den gebauten Baum bereit, um ihn zu laden.',
@ -80,7 +80,7 @@ export const STRINGS = {
'tab.poster-design': 'Design de cartazes', 'sub.poster-design': 'todos os estilos e tamanhos de cartaz',
'tab.wall-of-people-groups': 'Mural dos povos',
'tab.bible-posters': 'Cartazes bíblicos',
'tab.bless-india-posters': 'Abençoem a Índia: cartazes',
'tab.bless-india-posters': 'Pôsteres «Abençoe a Índia»',
experimentalSub: 'mapa experimental', lab: 'beta',
loading: 'Carregando {label}…', loadFailed: 'Não foi possível carregar {label}.',
manifestHint: 'Esta aba é composta a partir do manifest.json — compile a árvore ou sirva a árvore compilada para carregá-la.',
@ -95,7 +95,7 @@ export const STRINGS = {
'tab.poster-design': 'Design dei poster', 'sub.poster-design': 'tutti gli stili e i formati dei poster',
'tab.wall-of-people-groups': 'Muro dei popoli',
'tab.bible-posters': 'Poster biblici',
'tab.bless-india-posters': 'Benedite l’India: poster',
'tab.bless-india-posters': 'Poster «Benedici l’India»',
experimentalSub: 'mappa sperimentale', lab: 'beta',
loading: 'Caricamento di {label}…', loadFailed: 'Impossibile caricare {label}.',
manifestHint: "Questa scheda è composta da manifest.json: compila l'albero o pubblica l'albero compilato per caricarla.",
@ -110,7 +110,7 @@ export const STRINGS = {
'tab.poster-design': 'Design de afișe', 'sub.poster-design': 'toate stilurile și dimensiunile de afiș',
'tab.wall-of-people-groups': 'Zidul popoarelor',
'tab.bible-posters': 'Afișe biblice',
'tab.bless-india-posters': 'Binecuvântați India: afișe',
'tab.bless-india-posters': 'Postere „Binecuvântează India”',
experimentalSub: 'hartă experimentală', lab: 'beta',
loading: 'Se încarcă {label}…', loadFailed: '{label} nu a putut fi încărcat.',
manifestHint: 'Această filă este compusă din manifest.json — construiți arborele sau serviți arborele construit pentru a o încărca.',
@ -125,7 +125,7 @@ export const STRINGS = {
'tab.poster-design': 'Дизайн плакатов', 'sub.poster-design': 'все стили и размеры плакатов',
'tab.wall-of-people-groups': 'Стена народов',
'tab.bible-posters': 'Библейские плакаты',
'tab.bless-india-posters': 'Благословляйте Индию: плакаты',
'tab.bless-india-posters': 'Плакаты «Благослови Индию»',
experimentalSub: 'экспериментальная карта', lab: 'бета',
loading: 'Загрузка: {label}…', loadFailed: 'Не удалось загрузить: {label}.',
manifestHint: 'Эта вкладка собирается из manifest.json — соберите дерево или разместите собранное дерево, чтобы загрузить её.',
@ -140,7 +140,7 @@ export const STRINGS = {
'tab.poster-design': 'تصميم الملصقات', 'sub.poster-design': 'كل أنماط الملصقات وأحجامها',
'tab.wall-of-people-groups': 'جدار الشعوب',
'tab.bible-posters': 'ملصقات الكتاب المقدس',
'tab.bless-india-posters': 'باركوا الهند: ملصقات',
'tab.bless-india-posters': 'ملصقات باركوا الهند',
experimentalSub: 'خريطة تجريبية', lab: 'تجريبي',
loading: 'جارٍ تحميل {label}…', loadFailed: 'تعذّر تحميل {label}.',
manifestHint: 'تُبنى علامة التبويب هذه من manifest.json — ابنِ الشجرة أو قدّم الشجرة المبنية لتحميلها.',
@ -155,7 +155,7 @@ export const STRINGS = {
'tab.poster-design': 'पोस्टर डिज़ाइन', 'sub.poster-design': 'हर पोस्टर शैली और आकार',
'tab.wall-of-people-groups': 'जनसमूहों की दीवार',
'tab.bible-posters': 'बाइबल पोस्टर',
'tab.bless-india-posters': 'भारत को आशीष दो: पोस्टर',
'tab.bless-india-posters': 'भारत को आशीष दें — पोस्टर',
experimentalSub: 'प्रायोगिक मानचित्र', lab: 'बीटा',
loading: '{label} लोड हो रहा है…', loadFailed: '{label} लोड नहीं हो सका।',
manifestHint: 'यह टैब manifest.json से बनता है — इसे लोड करने के लिए ट्री बनाएँ या बना हुआ ट्री सर्व करें।',
@ -170,7 +170,7 @@ export const STRINGS = {
'tab.poster-design': '海报设计', 'sub.poster-design': '所有海报样式与尺寸',
'tab.wall-of-people-groups': '民族之墙',
'tab.bible-posters': '圣经海报',
'tab.bless-india-posters': '祝福印度:海报',
'tab.bless-india-posters': '祝福印度海报',
experimentalSub: '实验性地图', lab: '测试版',
loading: '正在加载{label}…', loadFailed: '无法加载{label}。',
manifestHint: '此标签页由 manifest.json 组成——请构建目录树或提供已构建的目录树以加载它。',

View file

@ -1382,7 +1382,7 @@
"bundle": "posterverse",
"scriptUrl": "./posterverse/posterverse.js",
"output": "dx-maps/posterverse/posterverse.js",
"integrity": "sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9",
"integrity": "sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq",
"elements": [
"posterverse-app"
],
@ -1409,7 +1409,7 @@
"scriptUrl": "./posterverse/posterverse.js",
"embed": {
"iframe": "<iframe src=\"./posterverse/posterverse/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
},
"instances": [
{

View file

@ -1382,7 +1382,7 @@
"bundle": "posterverse",
"scriptUrl": "./posterverse/posterverse.js",
"output": "dx-maps/posterverse/posterverse.js",
"integrity": "sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9",
"integrity": "sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq",
"elements": [
"posterverse-app"
],
@ -1409,7 +1409,7 @@
"scriptUrl": "./posterverse/posterverse.js",
"embed": {
"iframe": "<iframe src=\"./posterverse/posterverse/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
},
"instances": [
{

File diff suppressed because one or more lines are too long

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
<script src="../../posterverse.js" integrity="sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9" crossorigin="anonymous"></script>
<script src="../../posterverse.js" integrity="sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
<script src="../posterverse.js" integrity="sha384-HTRxdoXjuz/Pt8Z0ReAFj1n2FoKHopWMbxkQ0Ph3lmaonh3ux0r5Gd4z7F8MoIj9" crossorigin="anonymous"></script>
<script src="../posterverse.js" integrity="sha384-fhGrEVacRshXwvvIkRf5ki1FVUD3KZ+f5BoNMKO/n7AnkUnjRzhQsZb5/N9zO7Lq" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: