Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed
This commit is contained in:
commit
178d7ac3e8
21 changed files with 397 additions and 68 deletions
185
internal/check-experimental-shelf.mjs
Normal file
185
internal/check-experimental-shelf.mjs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-experimental-shelf — "all experimental plugins working", as a measurement.
|
||||
*
|
||||
* THE SHELF is derived, never listed: every bundle in the built manifest that the Research Gallery
|
||||
* would discover into its Experimental folder, i.e. not a curated tab and not in NOT_EXPERIMENTAL.
|
||||
* Both sets are read from plugin-designs/doxa-maps-spa/doxa-maps-spa.vue, so a bundle that joins or
|
||||
* leaves the shelf changes what this gate measures without an edit here.
|
||||
*
|
||||
* PER BUNDLE, PER ENGINE (mapbox, maplibre), on TWO surfaces: its own leaf page (A) and its tab
|
||||
* inside the Research Gallery (D). A surface passes when:
|
||||
* - the element mounted with content (its shadow tree has real nodes),
|
||||
* - 0 page errors and 0 failed requests (4xx/5xx; Mapbox telemetry and favicons excluded),
|
||||
* - no loading text is left on screen (Loading / Adding / Computing),
|
||||
* - and, for every MAP on the page: its 'load' fired, and it DRAWS its own data, meaning at
|
||||
* least one rendered feature on a layer fed by a GeoJSON source. That is the lesson from the
|
||||
* PPLR check: 'load' alone is not loaded, and an empty GeoJSON source still counts as a
|
||||
* source.
|
||||
* Maps are found without touching any bundle: an init script wraps the engine's Map class as the
|
||||
* engine script defines it, so every map the page constructs is recorded.
|
||||
*
|
||||
* IDENTITY: the gate serves THIS checkout's public/ itself, on a port the OS hands out, so it
|
||||
* cannot grade another worktree's tree (the fixed-port trap internal/_serves-this-tree.mjs names).
|
||||
*
|
||||
* SELF-TEST (--self-test, and always first unless --no-self-test): the first shelf bundle's script
|
||||
* is replaced in flight by one that throws. That bundle MUST fail on both surfaces; if it does not,
|
||||
* the gate is blind and exits 3 before grading anything.
|
||||
*
|
||||
* Usage: node internal/check-experimental-shelf.mjs [--engines mapbox,maplibre] [--only a,b]
|
||||
* [--no-self-test] [--self-test]
|
||||
* Exit: 0 all pass · 1 a bundle failed · 3 self-test could not see a broken bundle · 2 setup.
|
||||
* Run browser gates through internal/browser-slot.sh.
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:net'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const argv = process.argv.slice(2)
|
||||
const arg = (n, d) => { const i = argv.indexOf(n); return i >= 0 && argv[i + 1] ? argv[i + 1] : d }
|
||||
const ENGINES = arg('--engines', 'mapbox,maplibre').split(',')
|
||||
const ONLY = arg('--only', '') ? arg('--only', '').split(',') : null
|
||||
const SELF_TEST = !argv.includes('--no-self-test')
|
||||
const WAIT_MS = Number(process.env.SHELF_WAIT_MS || 75000)
|
||||
|
||||
const { chromium } = await import(resolve(ROOT, 'node_modules/playwright/index.mjs'))
|
||||
const manifest = JSON.parse(readFileSync(resolve(ROOT, 'public/doxa-maps/manifest.json'), 'utf8'))
|
||||
const spa = readFileSync(resolve(ROOT, 'plugin-designs/doxa-maps-spa/doxa-maps-spa.vue'), 'utf8')
|
||||
const cfg = JSON.parse(readFileSync(resolve(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8'))
|
||||
const TK = process.env.MAPBOX_TOKEN || cfg.token || ''
|
||||
|
||||
// ── the shelf, derived from the Gallery's own rules ───────────────────────────
|
||||
const notExp = new Set((spa.match(/const NOT_EXPERIMENTAL = new Set\(\[([\s\S]*?)\]\)/) || [, ''])[1].match(/'([a-z0-9-]+)'/g)?.map(s => s.slice(1, -1)) || [])
|
||||
const curated = new Set([...spa.matchAll(/bundle:\s*'([a-z0-9-]+)'/g)].map(m => m[1]))
|
||||
if (!notExp.size || !curated.size) { console.error('check-experimental-shelf: could not read NOT_EXPERIMENTAL / curated tabs from doxa-maps-spa.vue'); process.exit(2) }
|
||||
const shelf = manifest.bundles
|
||||
.filter(b => !notExp.has(b.bundle) && !curated.has(b.bundle) && (b.profiles || []).length)
|
||||
.filter(b => !ONLY || ONLY.includes(b.bundle))
|
||||
.map(b => {
|
||||
// the entry profile the Gallery opens: a map profile, else the one named like the bundle, else the first
|
||||
const p = b.profiles.find(x => x.isMap) || b.profiles.find(x => x.profile === b.bundle) || b.profiles[0]
|
||||
return { bundle: b.bundle, profile: p.profile, isMap: !!p.isMap, tag: p.element || (b.elements || [])[0] || b.bundle }
|
||||
})
|
||||
if (!shelf.length) { console.error('check-experimental-shelf: the shelf is empty'); process.exit(2) }
|
||||
|
||||
// ── SEEDS: bundles whose data is the USER'S, so a fresh browser correctly shows none ─────────
|
||||
// Seeded before load, then required to be DRAWN, which is stronger than exempting them. Printed on
|
||||
// every run with the reason, so a seed can never quietly hide a regression.
|
||||
const SEED = {
|
||||
'prayer-walk-planner': {
|
||||
why: 'walk zones are user-drawn and kept in localStorage; a fresh browser has none, so one known zone is seeded and must be drawn',
|
||||
keyPrefix: 'prayer-walk-planner:v1:',
|
||||
value: JSON.stringify([{ id: 'shelf-seed', title: 'Shelf check zone', coords: [[0, 10], [20, 10], [20, 25], [0, 25]] }]),
|
||||
},
|
||||
}
|
||||
|
||||
// ── our own server, on a port nobody else can be holding ──────────────────────
|
||||
const port = await new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
|
||||
const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', 'public'], { cwd: ROOT, stdio: 'ignore' })
|
||||
const reap = () => { try { srv.kill() } catch (_) {} }
|
||||
process.on('exit', reap); for (const s of ['SIGINT', 'SIGTERM']) process.on(s, () => { reap(); process.exit(130) })
|
||||
const BASE = `http://127.0.0.1:${port}/doxa-maps`
|
||||
for (let i = 0; i < 40; i++) { try { if ((await fetch(BASE + '/manifest.json')).ok) break } catch (_) {} await new Promise(r => setTimeout(r, 250)) }
|
||||
|
||||
// ── every Map the page builds, recorded without touching the bundle ───────────
|
||||
const RECORD_MAPS = () => {
|
||||
window.__shelfMaps = []
|
||||
for (const name of ['mapboxgl', 'maplibregl']) {
|
||||
let v
|
||||
Object.defineProperty(window, name, {
|
||||
configurable: true,
|
||||
get() { return v },
|
||||
set(val) {
|
||||
if (val && val.Map && !val.Map.__shelfWrapped) {
|
||||
const Orig = val.Map
|
||||
class Recorded extends Orig { constructor(...a) { super(...a); window.__shelfMaps.push(this); this.once('load', () => { this.__shelfLoaded = true }) } }
|
||||
Recorded.__shelfWrapped = true
|
||||
try { val.Map = Recorded } catch (_) { try { Object.defineProperty(val, 'Map', { value: Recorded }) } catch (_) {} }
|
||||
}
|
||||
v = val
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
const PROBE = (tag) => `(() => {
|
||||
const acc = []; const w = (r) => { for (const el of r.querySelectorAll('*')) { acc.push(el); if (el.shadowRoot) w(el.shadowRoot) } }; w(document)
|
||||
// CONTENT is the plugin's own subtree, not the page: a small card on a bare leaf and a card inside the
|
||||
// Gallery's chrome must be judged the same way.
|
||||
const host = acc.find(e => e.tagName === ${JSON.stringify('__TAG__')}.toUpperCase())
|
||||
let own = 0, sized = false
|
||||
if (host) { const inner = []; const w2 = (r) => { for (const el of r.querySelectorAll('*')) { inner.push(el); if (el.shadowRoot) w2(el.shadowRoot) } }; w2(host.shadowRoot || host); own = inner.length
|
||||
const hr = host.getBoundingClientRect(); sized = hr.width > 100 && hr.height > 100 }
|
||||
const busy = acc.filter(e => e.children.length === 0 && /Loading|Adding|Computing/i.test(e.textContent || '') && e.getBoundingClientRect().height > 0).length
|
||||
const maps = (window.__shelfMaps || []).filter(m => { try { return m.getContainer() && m.getContainer().isConnected } catch (_) { return false } }).map(m => {
|
||||
let drawn = 0, geo = 0
|
||||
try {
|
||||
const st = m.getStyle(); const srcs = st.sources || {}
|
||||
const geoIds = Object.keys(srcs).filter(k => srcs[k].type === 'geojson'); geo = geoIds.length
|
||||
const layers = (st.layers || []).filter(l => geoIds.includes(l.source)).map(l => l.id)
|
||||
if (layers.length) drawn = m.queryRenderedFeatures({ layers }).length
|
||||
} catch (_) {}
|
||||
return { loaded: !!(m.__shelfLoaded || (m.isStyleLoaded && m.isStyleLoaded())), geo, drawn }
|
||||
})
|
||||
return { own, sized, host: !!host, busy, maps }
|
||||
})()`.replace('__TAG__', tag)
|
||||
|
||||
const b = await chromium.launch({ headless: true, args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--enable-webgl', '--no-sandbox'] })
|
||||
|
||||
async function measure(url, s, poisonBundle) {
|
||||
const p = await b.newPage({ viewport: { width: 1280, height: 800 } })
|
||||
await p.addInitScript(RECORD_MAPS)
|
||||
const seed = SEED[s.bundle]
|
||||
if (seed) await p.addInitScript(({ keyPrefix, value }) => {
|
||||
const get = Storage.prototype.getItem
|
||||
Storage.prototype.getItem = function (k) { const v = get.call(this, k); return (v == null && String(k).startsWith(keyPrefix)) ? value : v }
|
||||
}, seed)
|
||||
if (poisonBundle) await p.route(new RegExp(`/${poisonBundle}/${poisonBundle}\\.js(\\?|$)`), r => r.fulfill({ status: 200, contentType: 'text/javascript', body: 'throw new Error("shelf self-test: deliberately broken bundle")' }))
|
||||
const errs = [], fails = []
|
||||
p.on('pageerror', e => errs.push(e.message.slice(0, 140)))
|
||||
p.on('response', r => { if (r.status() >= 400 && !/events\.mapbox\.com|favicon/.test(r.url())) fails.push(r.status() + ' ' + r.url().replace(/\?.*/, '').slice(-80)) })
|
||||
await p.goto(url)
|
||||
let st = null
|
||||
const t0 = Date.now()
|
||||
while (Date.now() - t0 < WAIT_MS) {
|
||||
await p.waitForTimeout(2500)
|
||||
st = await p.evaluate(PROBE(s.tag))
|
||||
const mapsReady = st.maps.every(m => m.loaded && m.drawn > 0)
|
||||
if (st.own > 8 && st.sized && st.busy === 0 && mapsReady && Date.now() - t0 > 6000) break
|
||||
}
|
||||
await p.close()
|
||||
const why = []
|
||||
if (!st || !st.host) why.push(`<${s.tag}> never appeared`)
|
||||
else if (st.own <= 8 || !st.sized) why.push(`<${s.tag}> mounted empty (${st.own} node(s)${st.sized ? '' : ', not sized'})`)
|
||||
if (st && st.busy) why.push(`${st.busy} loading label(s) left`)
|
||||
if (st) st.maps.forEach((m, i) => { if (!m.loaded) why.push(`map ${i + 1}: 'load' never fired`); else if (!m.drawn) why.push(`map ${i + 1}: no own data drawn (${m.geo} geojson source(s))`) })
|
||||
if (errs.length) why.push(`page error: ${errs[0]}`)
|
||||
if (fails.length) why.push(`failed request: ${fails[0]}`)
|
||||
return { pass: !why.length, why, maps: st ? st.maps.length : 0 }
|
||||
}
|
||||
|
||||
const leaf = (s, eng) => `${BASE}/${s.bundle}/${s.profile}/index.html?provider=${eng}` + (eng === 'mapbox' ? `&tk=${TK}` : '')
|
||||
const gallery = (s, eng) => `${BASE}/doxa-maps-spa/doxa-maps-spa/index.html?tab=${s.bundle}&provider=${eng}` + (eng === 'mapbox' ? `&tk=${TK}` : '')
|
||||
|
||||
let exit = 0
|
||||
try {
|
||||
if (SELF_TEST) {
|
||||
const s = shelf[0]
|
||||
const a = await measure(leaf(s, 'maplibre'), s, s.bundle), d = await measure(gallery(s, 'maplibre'), s, s.bundle)
|
||||
if (a.pass || d.pass) { console.error(`SELF-TEST FAILED: a deliberately broken ${s.bundle} still passed (${a.pass ? 'leaf' : ''} ${d.pass ? 'gallery' : ''}). This gate cannot see a broken bundle.`); exit = 3 }
|
||||
else console.log(`self-test: a deliberately broken ${s.bundle} fails as it must (leaf: ${a.why[0]}; gallery: ${d.why[0]})`)
|
||||
}
|
||||
if (exit !== 3) {
|
||||
for (const [k, v] of Object.entries(SEED)) if (shelf.some(s => s.bundle === k)) console.log(`SEEDED ${k}: ${v.why}`)
|
||||
console.log(`EXPERIMENTAL SHELF — ${shelf.length} bundle(s): ${shelf.map(s => s.bundle).join(', ')} · engines: ${ENGINES.join(', ')}`)
|
||||
for (const s of shelf) for (const eng of ENGINES) for (const [surf, url] of [['A leaf', leaf(s, eng)], ['D gallery', gallery(s, eng)]]) {
|
||||
const r = await measure(url, s)
|
||||
if (!r.pass) exit = 1
|
||||
console.log(`${r.pass ? 'PASS' : 'FAIL'} ${s.bundle.padEnd(24)} ${eng.padEnd(9)} ${surf.padEnd(10)} ${r.maps ? r.maps + ' map(s)' : 'no map'}${r.pass ? '' : ' — ' + r.why.join('; ')}`)
|
||||
}
|
||||
console.log(exit ? 'EXPERIMENTAL-SHELF: FAIL' : `EXPERIMENTAL-SHELF: PASS — every experimental plugin loads on its own page and in the Gallery, under ${ENGINES.join(' and ')}`)
|
||||
}
|
||||
} finally { await b.close(); reap() }
|
||||
process.exit(exit)
|
||||
114
internal/check-gospel-english-leaks.mjs
Normal file
114
internal/check-gospel-english-leaks.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* check-gospel-english-leaks.mjs — a translated key must not render in English.
|
||||
*
|
||||
* THE METHOD, from rega by way of dx-shepherd. A string is a LEAK when the catalogue has a
|
||||
* DIFFERENT value for the locale and the page still shows the English one. That is a render
|
||||
* or wiring fault, and it is a different thing from an untranslated string, which is a
|
||||
* catalogue gap. Only the first is this check's business.
|
||||
*
|
||||
* WHAT IT DOES NOT COUNT, so the number means something:
|
||||
* - third-party strings, e.g. the Mapbox and geoBoundaries attributions, which are licence
|
||||
* obligations and must never be translated;
|
||||
* - DATA, the people-group and place names that come from the publishers;
|
||||
* - PLACEHOLDER-ONLY strings;
|
||||
* - the explanatory PROSE this bundle deliberately keeps in English, which is declared by
|
||||
* being absent from the catalogue entirely — if there is no translated value, there is
|
||||
* nothing to leak.
|
||||
*
|
||||
* SCOPE: THE LEAF, AND THAT BOUNDARY IS THE POINT. Six keys were reported leaking in the
|
||||
* gallery. Measured here, the standalone leaf is clean in ar, zh, ru and hi — and the
|
||||
* gallery's iframe src is built without a lang parameter, so the embedded leaf never learns
|
||||
* the locale and correctly falls back to English. That is an embedder defect, not a bundle
|
||||
* one, and a check that conflated them would have sent someone to translate strings that are
|
||||
* already translated.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const REPO = resolve(HERE, '..')
|
||||
const PORT = Number(process.env.PORT || 5399)
|
||||
const BASE = process.env.BASE_URL || ('http://127.0.0.1:' + PORT)
|
||||
const LOCALES = (process.env.LOCALES || 'ar,zh,ru,hi').split(',')
|
||||
const LEAVES = ['resources-map', 'resources-list', 'data-tables', 'charts']
|
||||
|
||||
let srv = null
|
||||
if (!process.env.BASE_URL) srv = serveReaped(resolve(REPO, 'public'), PORT)
|
||||
|
||||
const cat = (lc) => JSON.parse(readFileSync(resolve(REPO, 'library/i18n/locales/' + lc + '/gospel.json'), 'utf8'))
|
||||
const flat = (o, p = []) => Object.entries(o).flatMap(([k, v]) =>
|
||||
k.startsWith('_') ? [] : (v && typeof v === 'object' ? flat(v, [...p, k]) : [[[...p, k].join('.'), String(v)]]))
|
||||
|
||||
let browser
|
||||
try {
|
||||
browser = await chromium.launch()
|
||||
const en = Object.fromEntries(flat(cat('en')))
|
||||
const leaks = []
|
||||
let compared = 0
|
||||
|
||||
for (const leaf of LEAVES) {
|
||||
for (const lc of LOCALES) {
|
||||
const loc = Object.fromEntries(flat(cat(lc)))
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
|
||||
await page.goto(BASE + '/doxa-maps/gospel-resources/' + leaf + '/index.html?lang=' + lc,
|
||||
{ waitUntil: 'networkidle', timeout: 90000 })
|
||||
await page.waitForTimeout(8000)
|
||||
/* ═══ READ ONLY THE CHROME, NOT THE DECLARED-ENGLISH PROSE ═══════════════════════
|
||||
* The first version read the whole shadow root and reported 113 leaks, nearly all
|
||||
* false: "JESUS Film" and "Scripture" appear inside the explanatory paragraphs this
|
||||
* bundle deliberately keeps in English, and three-letter catalogue entries like "for"
|
||||
* match inside ordinary English sentences. Both are noise, and a gate that cries 113
|
||||
* when 0 are real is worse than none — someone would have gone and "fixed" strings
|
||||
* that were already correct.
|
||||
* 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(() => {
|
||||
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) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
walk(sr)
|
||||
return parts.join(' ').replace(/\s+/g, ' ')
|
||||
})
|
||||
await page.close()
|
||||
if (!text) { leaks.push(leaf + ' ' + lc + ': rendered nothing at all'); continue }
|
||||
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
|
||||
compared++
|
||||
if (text.includes(e) && !text.includes(t)) leaks.push(leaf + ' ' + lc + ' ' + key + ': shows English "' + e.slice(0, 40) + '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' compared ' + compared + ' key/locale pair(s) across ' + LEAVES.length + ' leaves and ' + LOCALES.length + ' locales')
|
||||
if (leaks.length) {
|
||||
/* exitCode, NOT process.exit(). process.exit() terminates before pending stdout/stderr
|
||||
writes flush, and this gate proved it on itself: its first real run returned 1 with
|
||||
every explanatory line missing, so it failed without saying why. A gate that cannot
|
||||
explain its own failure is barely better than no gate. */
|
||||
console.error('GOSPEL-ENGLISH-LEAKS: FAIL — ' + leaks.length + ' translated key(s) rendering in English:')
|
||||
for (const l of leaks.slice(0, 12)) console.error(' ' + l)
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
console.log('GOSPEL-ENGLISH-LEAKS: PASS — every key with a translation renders in it.')
|
||||
}
|
||||
} finally {
|
||||
await browser?.close()
|
||||
srv?.stop()
|
||||
}
|
||||
10
package.json
10
package.json
|
|
@ -204,7 +204,10 @@
|
|||
"check:gospel-all": "node internal/check-gospel-all.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:poster-wall-localised": "node internal/check-poster-wall-localised.mjs"
|
||||
"check:english-leaks": "node internal/check-gospel-english-leaks.mjs",
|
||||
"check:poster-wall-localised": "node internal/check-poster-wall-localised.mjs",
|
||||
"check:poster-wall-localised": "node internal/check-poster-wall-localised.mjs",
|
||||
"check:experimental-shelf": "node internal/check-experimental-shelf.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.",
|
||||
|
|
@ -309,7 +312,10 @@
|
|||
"check:leaves-mount": "DID THE ELEMENT ACTUALLY MOUNT, on all five gospel leaves. Exists because a ReferenceError thrown during setup dies INSIDE the custom-element boundary: the page loads, every asset returns 200, pageerror fires zero times, and the element sits there with an empty shadow root. A healthy-looking dead page. This asks the cheap unambiguous question the rich gates are too slow to ask on every leaf — is anything there — in five steps that each name their own failure: tag defined, host present, shadowRoot attached, shadow root non-empty, visible text rendered. It does NOT trust the absence of console errors; zero errors is not the same as working. --poison for the negative test.",
|
||||
"check:gospel-all": "EVERYTHING gospel-resources owns, in one command. Its ten gates were all hand-run, which is how a dead bundle shipped: a duplicate module killed all five leaves and the one gate run that day was pointed at the map. check:no-orphan-gates was right that they were reachable — reachable is not run. Deliberately NOT in gates:pre/post: three need a browser, the shared wave holds browser gates under load, and a bundle owner should not be able to slow everyone else's build by adding to a list they do not own. Static gates run first and stop the run on failure, so a scarce browser slot is never spent confirming a table is inconsistent. Exit 75 = NOT MEASURED, never folded into a pass.",
|
||||
"check:no-conflict-markers": "Nothing tracked may carry an unresolved merge. Exists because a merge from this worktree committed a BUILT bundle with 21 conflict markers - both sides concatenated, 1.13MB - and every gospel leaf rendered dead while the SRI hash matched and every request returned 200. The cause was a landing script that resolved one expected file then ran \"git add -A public\", staging every other file including the ones still holding markers. For a built file: take either side, REBUILD, commit the rebuild, then run this before pushing. --poison for the negative test.",
|
||||
"check:basemap-labels": "the BASEMAP's place names follow the reader's language on BOTH engines (library/maps/localize-labels.js): reads the RENDERED country labels at ?lang=en and ?lang=ru — Mapbox via the Standard basemap's place-labels featureset, MapLibre via each symbol's evaluated text — and requires them to flip. Serves this checkout on an OS-assigned port; --dir <tree> for a negative control. Negative-tested: a bundle built before 319064a1 FAILS on both engines."
|
||||
"check:english-leaks": "A translated key must not render in English. rega's method: a string is a LEAK when the catalogue has a DIFFERENT value for the locale and the page still shows the English one - a render/wiring fault, not a missing translation. Third-party attributions, data names, placeholder-only strings and the prose this bundle deliberately keeps in English are all excluded, the last by simply not being in the catalogue. Scoped to the LEAF on purpose: six keys were reported leaking in the gallery and the leaf is clean in ar/zh/ru/hi - the gallery builds its iframe src with no lang parameter, so the embed never learns the locale. Embedder defect, not a bundle one.",
|
||||
"check:basemap-labels": "the BASEMAP's place names follow the reader's language on BOTH engines (library/maps/localize-labels.js): reads the RENDERED country labels at ?lang=en and ?lang=ru — Mapbox via the Standard basemap's place-labels featureset, MapLibre via each symbol's evaluated text — and requires them to flip. Serves this checkout on an OS-assigned port; --dir <tree> for a negative control. Negative-tested: a bundle built before 319064a1 FAILS on both engines.",
|
||||
"check:basemap-labels": "the BASEMAP's place names follow the reader's language on BOTH engines (library/maps/localize-labels.js): reads the RENDERED country labels at ?lang=en and ?lang=ru — Mapbox via the Standard basemap's place-labels featureset, MapLibre via each symbol's evaluated text — and requires them to flip. Serves this checkout on an OS-assigned port; --dir <tree> for a negative control. Negative-tested: a bundle built before 319064a1 FAILS on both engines.",
|
||||
"check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/android": "^7.6.9",
|
||||
|
|
|
|||
|
|
@ -239,6 +239,9 @@ function setupMapLayers() {
|
|||
map.value.addLayer({
|
||||
id: 'pwp-polygons-label', type: 'symbol', source: POLY_SRC,
|
||||
layout: {
|
||||
// A font OpenFreeMap's glyph server has. With no text-font, MapLibre asks for its default
|
||||
// (Open Sans / Arial Unicode MS), which OpenFreeMap 404s, and no zone title ever drew.
|
||||
'text-font': ['Noto Sans Regular'],
|
||||
'text-field': ['get', 'title'],
|
||||
'text-size': 12,
|
||||
'text-anchor': 'center'
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
* token-less via the gated host hook). Lives inside the doxa-upg-neighbors merged profile
|
||||
* as the "Flowers" view; reuses the granular per-PG coords from UPGN-MERGE.
|
||||
*/
|
||||
import { labelFont } from '../labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -222,7 +223,7 @@ function paint() {
|
|||
})
|
||||
if (!m.getLayer(L_CORE_LBL)) m.addLayer({
|
||||
id: L_CORE_LBL, type: 'symbol', source: SRC_CORE, minzoom: 2.2,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.4], 'text-anchor': 'top',
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.4], 'text-anchor': 'top',
|
||||
'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': C_RED_DK, 'text-halo-color': '#fff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* (this child's own <style> would be dropped by defineCustomElement).
|
||||
*/
|
||||
|
||||
import { labelFont } from '../labelFont.js'
|
||||
import { inject, ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -197,7 +198,7 @@ function ensureLayers(m) {
|
|||
})
|
||||
if (!m.getLayer(L_GREEN_LBL)) m.addLayer({
|
||||
id: L_GREEN_LBL, type: 'symbol', source: SRC_GREEN, minzoom: 3.4,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 10, 'text-offset': [0, 1.1],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 10, 'text-offset': [0, 1.1],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': '#0b5e33', 'text-halo-color': '#ffffff', 'text-halo-width': 1.3 }
|
||||
})
|
||||
|
|
@ -212,7 +213,7 @@ function ensureLayers(m) {
|
|||
})
|
||||
if (!m.getLayer(L_RED_LBL)) m.addLayer({
|
||||
id: L_RED_LBL, type: 'symbol', source: SRC_RED, minzoom: 2.4,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.2],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.2],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': COLORS.RED_DK, 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
17
plugin-designs/upg-neighbor/src/labelFont.js
Normal file
17
plugin-designs/upg-neighbor/src/labelFont.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* labelFont.js — a text-font the map's own glyph server actually has.
|
||||
*
|
||||
* A symbol layer with no text-font asks for the engine default ("Open Sans Regular, Arial Unicode MS
|
||||
* Regular"), and the bold labels here named Mapbox's "DIN Offc Pro Bold". Mapbox serves both.
|
||||
* OpenFreeMap, the basemap under provider=maplibre, serves neither: measured, the glyph request is
|
||||
* a 404 and the labels never draw. So under MapLibre the labels use Noto Sans, which it does serve;
|
||||
* under Mapbox nothing changes.
|
||||
*
|
||||
* Spread into a layer's layout: layout: { 'text-field': …, ...labelFont(m) }
|
||||
*/
|
||||
export function labelFont(map, bold = false) {
|
||||
const ml = typeof window !== 'undefined' && window.maplibregl
|
||||
const isMaplibre = !!(ml && ml.Map && map instanceof ml.Map)
|
||||
if (isMaplibre) return { 'text-font': [bold ? 'Noto Sans Bold' : 'Noto Sans Regular'] }
|
||||
return bold ? { 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'] } : {}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
* "dataUrl":"<reached-neighbors.json>", "instanceId":"..." }
|
||||
*/
|
||||
|
||||
import { labelFont } from './labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -318,7 +319,7 @@ function paintAll() {
|
|||
})
|
||||
if (!m.getLayer(L_CL_LABEL)) m.addLayer({
|
||||
id: L_CL_LABEL, type: 'symbol', source: SRC_CL, minzoom: 2.6,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.3],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.3],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': C_RED_DK, 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
* }
|
||||
*/
|
||||
|
||||
import { labelFont } from './labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -308,7 +309,7 @@ function syncLayers() {
|
|||
id: L_SENDER_LABEL, type: 'symbol', source: SRC_SENDERS,
|
||||
minzoom: 3.4,
|
||||
layout: {
|
||||
'text-field': ['get', 'name'], 'text-size': 10,
|
||||
...labelFont(m), 'text-field': ['get', 'name'], 'text-size': 10,
|
||||
'text-offset': [0, 1.1], 'text-anchor': 'top', 'text-allow-overlap': false
|
||||
},
|
||||
paint: { 'text-color': '#0b5e33', 'text-halo-color': '#ffffff', 'text-halo-width': 1.3 }
|
||||
|
|
@ -329,7 +330,7 @@ function syncLayers() {
|
|||
m.addLayer({
|
||||
id: L_TARGET_LABEL, type: 'symbol', source: SRC_TARGET,
|
||||
layout: {
|
||||
'text-field': ['get', 'name'], 'text-size': 13, 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'],
|
||||
'text-field': ['get', 'name'], 'text-size': 13, ...labelFont(m, true),
|
||||
'text-offset': [0, -1.4], 'text-anchor': 'bottom', 'text-allow-overlap': true
|
||||
},
|
||||
paint: { 'text-color': '#8a3800', 'text-halo-color': '#ffffff', 'text-halo-width': 1.6 }
|
||||
|
|
|
|||
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
|
|
@ -863,7 +863,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-rI097ecm/idym9j/Od/aOtgqhk0kB/1CwMpal9jTLm/ZyNKdlo6eYRMwV+J0rJyV",
|
||||
"integrity": "sha384-v0hrmAp17LGeHHfWbgdEkrTILIqrZRQh2QDFT1tgxN359vVW/Xt7t+GAQS16uA4U",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -879,7 +879,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-rI097ecm/idym9j/Od/aOtgqhk0kB/1CwMpal9jTLm/ZyNKdlo6eYRMwV+J0rJyV\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-v0hrmAp17LGeHHfWbgdEkrTILIqrZRQh2QDFT1tgxN359vVW/Xt7t+GAQS16uA4U\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1059,7 +1059,7 @@
|
|||
"bundle": "upg-neighbor",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"output": "doxa-maps/upg-neighbor/upg-neighbor.js",
|
||||
"integrity": "sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN",
|
||||
"integrity": "sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi",
|
||||
"elements": [
|
||||
"upg-neighbor"
|
||||
],
|
||||
|
|
@ -1075,7 +1075,7 @@
|
|||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -863,7 +863,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-rI097ecm/idym9j/Od/aOtgqhk0kB/1CwMpal9jTLm/ZyNKdlo6eYRMwV+J0rJyV",
|
||||
"integrity": "sha384-v0hrmAp17LGeHHfWbgdEkrTILIqrZRQh2QDFT1tgxN359vVW/Xt7t+GAQS16uA4U",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -879,7 +879,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-rI097ecm/idym9j/Od/aOtgqhk0kB/1CwMpal9jTLm/ZyNKdlo6eYRMwV+J0rJyV\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-v0hrmAp17LGeHHfWbgdEkrTILIqrZRQh2QDFT1tgxN359vVW/Xt7t+GAQS16uA4U\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1059,7 +1059,7 @@
|
|||
"bundle": "upg-neighbor",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"output": "doxa-maps/upg-neighbor/upg-neighbor.js",
|
||||
"integrity": "sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN",
|
||||
"integrity": "sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi",
|
||||
"elements": [
|
||||
"upg-neighbor"
|
||||
],
|
||||
|
|
@ -1075,7 +1075,7 @@
|
|||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <prayer-walk-planner> custom element. -->
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-rI097ecm/idym9j/Od/aOtgqhk0kB/1CwMpal9jTLm/ZyNKdlo6eYRMwV+J0rJyV" crossorigin="anonymous"></script>
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-v0hrmAp17LGeHHfWbgdEkrTILIqrZRQh2QDFT1tgxN359vVW/Xt7t+GAQS16uA4U" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
86
public/doxa-maps/upg-neighbor/upg-neighbor.js
generated
86
public/doxa-maps/upg-neighbor/upg-neighbor.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue