Merge main into bundler-2/work
# Conflicts: # public/dx-maps/index.html
17
README.md
|
|
@ -427,7 +427,8 @@ bun run tauri:build # builds the app — needs Rust, the Tauri CLI and webvie
|
|||
|
||||
## Languages
|
||||
|
||||
**Every map speaks 11 languages** — add `?lang=<code>` to a map's page address. Arabic reads right to left.
|
||||
**Every map speaks 11 languages** — add `?lang=<code>` to a map's page address, or, when a map is embedded in
|
||||
your own page, set `"locale"` in its `profile-config`. Arabic reads right to left.
|
||||
|
||||

|
||||
|
||||
|
|
@ -447,10 +448,18 @@ bun run tauri:build # builds the app — needs Rust, the Tauri CLI and webvie
|
|||
|
||||
- **Pick a language with the address:** `…/prayer/index.html?lang=fr` shows the map in French. It
|
||||
works on every map's own page and on the Research Gallery. `?locale=` works too.
|
||||
- **Without it,** a map follows the page's own language (`<html lang>`, which WordPress sets), and
|
||||
- **Set it from your own page:** when you embed the Research Gallery or a single map with the
|
||||
JavaScript embed, put the language in its `profile-config` (`"lang"` works too). Set it again
|
||||
later and the map switches language live.
|
||||
|
||||
```html
|
||||
<doxa-maps-spa profile-config='{"locale":"ar"}'></doxa-maps-spa>
|
||||
```
|
||||
- **Without either,** a map follows the page's own language (`<html lang>`, which WordPress sets), and
|
||||
otherwise shows English.
|
||||
- **Right to left:** in Arabic the whole layout mirrors — the Research Gallery's tab rail moves to
|
||||
the right.
|
||||
- **Right to left:** in Arabic the map's layout mirrors — the Research Gallery's tab rail moves to
|
||||
the right. Set through `profile-config`, only the map's own box mirrors; the rest of your page is
|
||||
left alone. A `?lang=ar` address mirrors the whole page, which is what a map's own page wants.
|
||||
- **Where the words live:** `library/i18n/locales/<code>/`, one folder per language. Adding a
|
||||
language: [documentation/architecture/i18n.md](./documentation/architecture/i18n.md).
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 827 KiB After Width: | Height: | Size: 828 KiB |
|
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 2 MiB |
|
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 954 KiB After Width: | Height: | Size: 954 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 733 KiB After Width: | Height: | Size: 760 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 706 KiB After Width: | Height: | Size: 678 KiB |
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 702 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
|
@ -1,102 +1,102 @@
|
|||
{
|
||||
"cap1-single-element": {
|
||||
"bundle": "all-people-groups",
|
||||
"sha256": "715fa7a854ea0b819f5dbe4ae256d95264984584e6c6933ed321bbb4230b49f7"
|
||||
"sha256": "1a2ddbee5f7ade4c59be1e729334fbaabb9a4bb52fe91aaae70df0b8a3e817a2"
|
||||
},
|
||||
"cap2-adoption": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5"
|
||||
"sha256": "01973724b4f6b656d4f4743cf2167b8d5a984f48401e4738cec83fb3719d289b"
|
||||
},
|
||||
"cap2-engagement": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5"
|
||||
"sha256": "01973724b4f6b656d4f4743cf2167b8d5a984f48401e4738cec83fb3719d289b"
|
||||
},
|
||||
"cap2-prayer": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5"
|
||||
"sha256": "01973724b4f6b656d4f4743cf2167b8d5a984f48401e4738cec83fb3719d289b"
|
||||
},
|
||||
"cap3-monolithic-research": {
|
||||
"bundle": "doxa-research-map",
|
||||
"sha256": "ea9638be007ff6127514e663f1ee79644be5fee4ba7340ef8d3ad6bf18cafc69"
|
||||
"sha256": "5f73cb71c0a374a5506df426ac4b43648f28d7128b9583da1b358c1ca7519bda"
|
||||
},
|
||||
"cap4-upg100-list": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480"
|
||||
"sha256": "237de597f1080ca4893650ed19f6fc3a31b7ea30da498e8fa7d1f74ca88818b2"
|
||||
},
|
||||
"cap4-upg100-mfe": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480"
|
||||
"sha256": "237de597f1080ca4893650ed19f6fc3a31b7ea30da498e8fa7d1f74ca88818b2"
|
||||
},
|
||||
"cap4-upg100-poster-wall": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480"
|
||||
"sha256": "237de597f1080ca4893650ed19f6fc3a31b7ea30da498e8fa7d1f74ca88818b2"
|
||||
},
|
||||
"cap5-doxa-maps-spa": {
|
||||
"bundle": "doxa-maps-spa",
|
||||
"sha256": "3d5467fd47ada47979fb878a5a355329ddc3ad88071ec947bf6c4a61d53df143"
|
||||
"sha256": "9093c700a38d42a9444650690d99ce3b8b66f0c73e03e00ac49780c3833807c5"
|
||||
},
|
||||
"cap5-plugin-studio": {
|
||||
"bundle": "plugin-studio",
|
||||
"sha256": "6b8f3ce6cb17075ec26859f12ec0afe0c78bee5dc02b1f9f81b8d699931dc44a"
|
||||
"sha256": "0034c901862e25c575966dd48f78d2fa739834db401cf37f1de9653e3034ae05"
|
||||
},
|
||||
"deploy2-mobile-upg100": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480"
|
||||
"sha256": "237de597f1080ca4893650ed19f6fc3a31b7ea30da498e8fa7d1f74ca88818b2"
|
||||
},
|
||||
"exp-prayer-walk-planner": {
|
||||
"bundle": "prayer-walk-planner",
|
||||
"sha256": "a06c505f4ae24d03f29657065806f65c4dfc36348a1000178d6cb843aaf455e8"
|
||||
"sha256": "0b695f70da64b8d5c4f0c6ca765571eb621acac08f41e49bc3da57df687ed0a5"
|
||||
},
|
||||
"exp-wall-of-people-groups": {
|
||||
"bundle": "wall-of-people-groups",
|
||||
"sha256": "de94f7f53fcfbb757ed8216679e5f563db90cde005ec401104d617fc59c5f105"
|
||||
"sha256": "2dfc3a3c4918b574862c07271d8594e56b6d1d7ef96553fe67359bbe58c2cad2"
|
||||
},
|
||||
"lang-arabic-upg100": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480"
|
||||
"sha256": "237de597f1080ca4893650ed19f6fc3a31b7ea30da498e8fa7d1f74ca88818b2"
|
||||
},
|
||||
"prod-big-picture": {
|
||||
"bundle": "dashboard-plus-grid",
|
||||
"sha256": "4ec8da3d155150d391ecbc0ac2f96f60bd2efbb12b3b5e8bdf0a869846dbfa16"
|
||||
"sha256": "27b9a2e9198b0f3274218faec09796636307d3d6af18c21ed4ba580b2c515457"
|
||||
},
|
||||
"prod-cities-internet": {
|
||||
"bundle": "cities-internet",
|
||||
"sha256": "a62739180efc54c2d9e9216eafe0b290921da6ef83827749ead027945f56dbd7"
|
||||
"sha256": "65e98efcd79016282460e3141ccb2995ad5a11d7039c3303d7c4ae2a7a5ee32d"
|
||||
},
|
||||
"prod-cities-internet-phone": {
|
||||
"bundle": "cities-internet",
|
||||
"sha256": "a62739180efc54c2d9e9216eafe0b290921da6ef83827749ead027945f56dbd7"
|
||||
"sha256": "65e98efcd79016282460e3141ccb2995ad5a11d7039c3303d7c4ae2a7a5ee32d"
|
||||
},
|
||||
"prod-gospel-resources": {
|
||||
"bundle": "gospel-resources",
|
||||
"sha256": "8b31fa704a5217be541a804d350551b3f7b7d064eb1096eaecb226ad10e578f4"
|
||||
"sha256": "00c05aa0437f0304a0403037af640795f6f18797371cf14c2cd50817be1c145c"
|
||||
},
|
||||
"prod-gospel-resources-phone": {
|
||||
"bundle": "gospel-resources",
|
||||
"sha256": "8b31fa704a5217be541a804d350551b3f7b7d064eb1096eaecb226ad10e578f4"
|
||||
"sha256": "00c05aa0437f0304a0403037af640795f6f18797371cf14c2cd50817be1c145c"
|
||||
},
|
||||
"prod-prayer-phone": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5"
|
||||
"sha256": "01973724b4f6b656d4f4743cf2167b8d5a984f48401e4738cec83fb3719d289b"
|
||||
},
|
||||
"prod-regions": {
|
||||
"bundle": "regions-dashboard",
|
||||
"sha256": "3609a2c914d6af247cb6618585d234e9a2c857df83bfe03c2a699c2615d1828d"
|
||||
"sha256": "e90b0055e2251c51b5cdc8b419377463ec8919cfbb774725e3c6c47c370433d6"
|
||||
},
|
||||
"prod-regions-phone": {
|
||||
"bundle": "regions-dashboard",
|
||||
"sha256": "3609a2c914d6af247cb6618585d234e9a2c857df83bfe03c2a699c2615d1828d"
|
||||
"sha256": "e90b0055e2251c51b5cdc8b419377463ec8919cfbb774725e3c6c47c370433d6"
|
||||
},
|
||||
"prod-research-gallery-phone": {
|
||||
"bundle": "doxa-maps-spa",
|
||||
"sha256": "3d5467fd47ada47979fb878a5a355329ddc3ad88071ec947bf6c4a61d53df143"
|
||||
"sha256": "9093c700a38d42a9444650690d99ce3b8b66f0c73e03e00ac49780c3833807c5"
|
||||
},
|
||||
"prod-simple-research": {
|
||||
"bundle": "research-map-plus-grid",
|
||||
"sha256": "8091ad4c3369928fb322ca69fb9319567fc1351a381e1bffbca518a13e78cb53"
|
||||
"sha256": "110de278cb89a41a95114fb37ea6a2809b19ae9cd99a79ef2debcd63a130de68"
|
||||
},
|
||||
"prod-simple-research-phone": {
|
||||
"bundle": "research-map-plus-grid",
|
||||
"sha256": "8091ad4c3369928fb322ca69fb9319567fc1351a381e1bffbca518a13e78cb53"
|
||||
"sha256": "110de278cb89a41a95114fb37ea6a2809b19ae9cd99a79ef2debcd63a130de68"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ const MANIFEST = `${TREE_NAME}/manifest.json`
|
|||
/**
|
||||
* @param {string} base e.g. http://127.0.0.1:4173 (a trailing /doxa-maps/ is tolerated)
|
||||
* @param {string} repo absolute path to the repo root
|
||||
* @param {{assets?: string[]}} [opts] data files this gate JUDGES (tree-relative, e.g.
|
||||
* 'assets/tables/doxa-gospel-access.json'); their served bytes must equal disk too
|
||||
* @returns {Promise<{ok:boolean, why:string}>}
|
||||
*/
|
||||
export async function servesThisTree(base, repo) {
|
||||
export async function servesThisTree(base, repo, { assets = [] } = {}) {
|
||||
let local
|
||||
try {
|
||||
local = createHash('sha256').update(readFileSync(resolve(repo, 'public', MANIFEST))).digest('hex')
|
||||
|
|
@ -45,7 +47,25 @@ export async function servesThisTree(base, repo) {
|
|||
if (served !== local) {
|
||||
return { ok: false, why: `${root} serves a DIFFERENT build than public/ — another worktree, or a server that needs restarting` }
|
||||
}
|
||||
return { ok: true, why: 'same build' }
|
||||
// ★ THE MANIFEST DESCRIBES BUNDLES, NOT DATA. MEASURED 2026-09-25 (gospel): verify:gospel-map adopted
|
||||
// another worktree's server on :4173 and reported PASS — the three worktrees' manifests were
|
||||
// byte-identical because the change under test was a DATA asset (doxa-gospel-access.json), which the
|
||||
// manifest never mentions; a planted jw.org link went undetected. So a gate whose subject is a data
|
||||
// file DECLARES it (paths relative to the tree, e.g. 'assets/tables/doxa-gospel-access.json') and its
|
||||
// served bytes are compared with disk too. Any difference is a different tree for THIS gate.
|
||||
for (const rel of assets) {
|
||||
let disk
|
||||
try { disk = readFileSync(resolve(repo, 'public', TREE_NAME, rel)) } catch {
|
||||
return { ok: false, why: `no local public/${TREE_NAME}/${rel} to compare against — build the tree first` }
|
||||
}
|
||||
const a = await fetch(`${root}/${TREE_NAME}/${rel}`).catch(() => null)
|
||||
if (!a || !a.ok) return { ok: false, why: `${root} serves a DIFFERENT build — ${rel} is not served there (${a ? a.status : 'no answer'})` }
|
||||
const got = Buffer.from(await a.arrayBuffer())
|
||||
if (!got.equals(disk)) {
|
||||
return { ok: false, why: `${root} serves a DIFFERENT build — the manifest matches but ${rel} does not (another worktree's data, or a stale server)` }
|
||||
}
|
||||
}
|
||||
return { ok: true, why: assets.length ? `same build, and ${assets.length} declared data file(s) byte-identical` : 'same build' }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -100,7 +120,7 @@ export async function serveThisTree(dir) {
|
|||
* @param {string} repo absolute path to the repo root
|
||||
* @param {string} label the gate's own name, for the message
|
||||
*/
|
||||
export async function requireThisTree(base, repo, label) {
|
||||
export async function requireThisTree(base, repo, label, { assets = [] } = {}) {
|
||||
let origin = String(base || '')
|
||||
// A gate that loads file:// URLs is reading THIS checkout by construction — there is no server to
|
||||
// be wrong about, so the guard stays silent rather than inventing a precondition.
|
||||
|
|
@ -116,7 +136,7 @@ export async function requireThisTree(base, repo, label) {
|
|||
if (!/^(localhost|127\.|0\.0\.0\.0$|\[?::1\]?$)/.test(host)) return { ok: true, why: 'remote target, not a worktree port' }
|
||||
} catch { /* unparseable — fall through to the content check */ }
|
||||
try { origin = new URL(origin).origin } catch { /* already an origin, or unusable — pass through */ }
|
||||
const verdict = await servesThisTree(origin, repo)
|
||||
const verdict = await servesThisTree(origin, repo, { assets })
|
||||
if (/DIFFERENT build/.test(verdict.why)) {
|
||||
console.log(`${label}: NOT MEASURED — ${verdict.why}`)
|
||||
console.log(' Point this gate at a server for THIS checkout (bun run serve) and re-run.')
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
# Usage: internal/browser-slot.sh <command> [args...]
|
||||
# Env: BROWSER_SLOTS (default 2), BROWSER_SLOT_DIR (default /tmp),
|
||||
# BROWSER_MEM_FLOOR_MB (default 2500), BROWSER_LOCK_WAIT (default 900),
|
||||
# BROWSER_MEM_WAIT (default 300), BROWSER_MAX_HOLD (default 900 s)
|
||||
# BROWSER_MEM_WAIT (default 300), BROWSER_MAX_HOLD (default 900 s),
|
||||
# BROWSER_DISK_FLOOR_MB (default 1500; checked on / and on $TMPDIR or /tmp)
|
||||
set -u
|
||||
|
||||
SLOTS="${BROWSER_SLOTS:-2}"
|
||||
|
|
@ -33,6 +34,7 @@ MEM_WAIT_S="${BROWSER_MEM_WAIT:-300}"
|
|||
BUSY=75
|
||||
MAX_HOLD="${BROWSER_MAX_HOLD:-900}"
|
||||
KILL_AFTER="${BROWSER_KILL_AFTER:-15}"
|
||||
DISK_FLOOR_MB="${BROWSER_DISK_FLOOR_MB:-1500}"
|
||||
|
||||
# ★ A GATE THAT IS LEGITIMATELY LONG DECLARES ITS OWN LIMIT, in its header: @browser-slot-max-hold 2700
|
||||
# check:host-locale-contract loads every leaf twice and hit the 900 s default three times on full
|
||||
|
|
@ -68,21 +70,67 @@ fi
|
|||
# the command named so its owner can find it. A long gate that legitimately needs more says so:
|
||||
# BROWSER_MAX_HOLD=1800 internal/browser-slot.sh node internal/<long-gate>.mjs
|
||||
run_held() {
|
||||
timeout --kill-after="$KILL_AFTER" "$MAX_HOLD" "$@" 9>&-
|
||||
local rc=$?
|
||||
# The gate's output still flows to the caller untouched (stdout and stderr kept apart); a COPY goes to
|
||||
# RAM (/dev/shm, never the disk that may be full) so a failure can be classified afterwards.
|
||||
local log="" rc
|
||||
if [ -d /dev/shm ] && [ -w /dev/shm ]; then log=$(mktemp /dev/shm/browser-slot.XXXXXX 2>/dev/null) || log=""; fi
|
||||
if [ -n "$log" ]; then
|
||||
{ timeout --kill-after="$KILL_AFTER" "$MAX_HOLD" "$@" 9>&- 2> >(tee -a "$log" >&2); } | tee -a "$log"
|
||||
rc=${PIPESTATUS[0]}
|
||||
sleep 0.2 # let the stderr tee finish writing its copy
|
||||
else
|
||||
timeout --kill-after="$KILL_AFTER" "$MAX_HOLD" "$@" 9>&-
|
||||
rc=$?
|
||||
fi
|
||||
if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then
|
||||
echo "browser-slot: '$*' held a browser slot past BROWSER_MAX_HOLD=${MAX_HOLD}s and was killed (timeout rc $rc) — NOT MEASURED. Give it a timeout of its own, or set BROWSER_MAX_HOLD if it is legitimately that long." >&2
|
||||
exit "$BUSY"
|
||||
rm -f "$log"; exit "$BUSY"
|
||||
fi
|
||||
# ★ AN INFRASTRUCTURE FAILURE IS NOT A PRODUCT VERDICT (measured 2026-09-25: with the disk at 48 MB free,
|
||||
# three browser gates went RED with Chromium "Page crashed" — an out-of-disk box wearing a product
|
||||
# gate's exit code). ENOSPC is always infrastructure. A browser crash is classified as such ONLY when
|
||||
# the box is below a floor at that moment: a renderer crash on a healthy box can be a real product
|
||||
# bug (a leak, a runaway loop), and blanket-excusing it would hide exactly that.
|
||||
if [ "$rc" -ne 0 ] && [ "$rc" -ne "$BUSY" ] && [ -n "$log" ]; then
|
||||
local infra=""
|
||||
if grep -qiE 'ENOSPC|No space left on device' "$log"; then
|
||||
infra="the disk is full (ENOSPC)"
|
||||
elif grep -qE 'Page crashed|Target (page, context or browser )?has been closed|Target closed|Browser (has been )?closed|browser has disconnected' "$log"; then
|
||||
local d m; d=$(disk_available_mb); m=$(mem_available_mb)
|
||||
if [ "$d" -lt "$DISK_FLOOR_MB" ] || [ "$m" -lt "$FLOOR_MB" ]; then
|
||||
infra="the browser crashed while the box was below a floor (disk ${d}MB / floor ${DISK_FLOOR_MB}MB, memory ${m}MB / floor ${FLOOR_MB}MB)"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$infra" ]; then
|
||||
echo "browser-slot: '$*' failed from INFRASTRUCTURE, not the product — ${infra}. NOT MEASURED (the command exited $rc)." >&2
|
||||
rm -f "$log"; exit "$BUSY"
|
||||
fi
|
||||
fi
|
||||
rm -f "$log"
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
slot_path() { [ "$1" = "1" ] && echo "$SLOT_BASE" || echo "$SLOT_BASE$1"; }
|
||||
|
||||
# Free disk, the lower of / and the temp dir (they may be different filesystems). BROWSER_FAKE_DISK_FILE is
|
||||
# a TEST HOOK: when set, the reading comes from that file, so a test can "fill the disk" mid-run.
|
||||
disk_available_mb() {
|
||||
if [ -n "${BROWSER_FAKE_DISK_FILE:-}" ] && [ -f "$BROWSER_FAKE_DISK_FILE" ]; then cat "$BROWSER_FAKE_DISK_FILE"; return; fi
|
||||
local a b; a=$(df --output=avail -m / 2>/dev/null | tail -1 | tr -d ' '); b=$(df --output=avail -m "${TMPDIR:-/tmp}" 2>/dev/null | tail -1 | tr -d ' ')
|
||||
a=${a:-999999}; b=${b:-999999}; [ "$a" -lt "$b" ] && echo "$a" || echo "$b"
|
||||
}
|
||||
|
||||
mem_available_mb() {
|
||||
awk '/^MemAvailable:/ { printf "%d", $2 / 1024 }' /proc/meminfo 2>/dev/null || echo 999999
|
||||
}
|
||||
|
||||
# ★ REFUSE ON A FULL DISK, before taking a slot: Chromium needs disk for its profile and cache, and a
|
||||
# measurement started with none ends as a "Page crashed" that reads like a product failure.
|
||||
if [ "$(disk_available_mb)" -lt "$DISK_FLOOR_MB" ]; then
|
||||
echo "browser-slot: only $(disk_available_mb)MB free on disk (floor ${DISK_FLOOR_MB}MB) — not measuring" >&2
|
||||
exit "$BUSY"
|
||||
fi
|
||||
|
||||
# Hold off while the box is below the floor. Proceeding anyway is not an option here the way it is
|
||||
# for a load average: load makes a measurement slow, memory makes it get killed halfway through.
|
||||
waited=0
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@
|
|||
* CENSUS IS BLIND and exits 75. No preset or ad-hoc run is accepted without one.
|
||||
*
|
||||
* PRESETS: internal/element-census/<name>.json — { owner, pages:[{name, leaf, live}], neg:{leaf, text},
|
||||
* sanctioned:[{match, flags, what, driver, date, card}] }. An allowlist entry without the Driver's words,
|
||||
* sanctioned:[{match, flags, what, driver, date, card, theirs?, theirsFlags?}] }. `theirs` makes the entry a
|
||||
* SANCTIONED PAIR: their string for the same control (e.g. our "Hide Filters" / their "Show Filters"),
|
||||
* excused as an omission only while our half is on the page. An allowlist entry without the Driver's words,
|
||||
* a date and a card is REFUSED (exit 2): it is evidence, not an ignore list.
|
||||
*
|
||||
* USAGE node internal/check-element-census-vs-live.mjs every preset
|
||||
|
|
@ -64,7 +66,12 @@ function validate(p, from) {
|
|||
for (const k of ['match', 'driver', 'date', 'card']) if (!s[k]) bad(`${from}: sanctioned[${i}] has no "${k}" — every allowance carries the Driver's words, the date and the card`)
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(s.date)) bad(`${from}: sanctioned[${i}].date must be YYYY-MM-DD`)
|
||||
let re; try { re = new RegExp(s.match, s.flags || '') } catch (e) { bad(`${from}: sanctioned[${i}].match is not a regex: ${e.message}`) }
|
||||
return { ...s, re }
|
||||
// A SANCTIONED PAIR: `theirs` names THEIR string for the same control in its other state (their
|
||||
// "Show Filters" vs our sanctioned "Hide Filters") under the same citation, so a sanctioned
|
||||
// difference does not leave a phantom omission behind.
|
||||
let theirsRe = null
|
||||
if (s.theirs !== undefined) { try { theirsRe = new RegExp(s.theirs, s.theirsFlags ?? s.flags ?? '') } catch (e) { bad(`${from}: sanctioned[${i}].theirs is not a regex: ${e.message}`) } }
|
||||
return { ...s, re, theirsRe }
|
||||
})
|
||||
return p
|
||||
}
|
||||
|
|
@ -171,6 +178,13 @@ for (const [name, p] of presets) {
|
|||
}
|
||||
const driftShapes = new Set(drift.map(([s]) => shape(s)))
|
||||
theirsOnly = theirsOnly.filter((t) => !driftShapes.has(shape(t)))
|
||||
// Pairs: THEIR half is excused only when OUR half of the same entry is actually on this page. If
|
||||
// our control vanished, their string is a real omission again — a pair can never hide a gap.
|
||||
const pairedTheirs = []
|
||||
for (const x of p.sanctioned.filter((x) => x.theirsRe)) {
|
||||
if (!ours.list.some((t) => x.re.test(t))) continue
|
||||
theirsOnly = theirsOnly.filter((t) => { if (x.theirsRe.test(t)) { pairedTheirs.push([t, x]); return false } return true })
|
||||
}
|
||||
console.log(` OURS-ONLY: ${allowed.length} sanctioned · ${invented.length} unaccounted`)
|
||||
for (const [s, why] of allowed) console.log(` ok "${s.slice(0, 60)}" — ${why.slice(0, 140)}`)
|
||||
for (const s of invented) console.log(` ?? "${s.slice(0, 80)}"`)
|
||||
|
|
@ -179,6 +193,7 @@ for (const [name, p] of presets) {
|
|||
console.log(` DATA DRIFT (same label, different figure — their page is an SSR snapshot, ours is live): ${drift.length}`)
|
||||
for (const [o, t] of drift.slice(0, 10)) console.log(` ~ ours ${JSON.stringify(o.slice(0, 44))} vs theirs ${JSON.stringify(t.slice(0, 44))}`)
|
||||
}
|
||||
for (const [t, x] of pairedTheirs) console.log(` ok their "${t.slice(0, 50)}" — the other half of a sanctioned pair (${x.what || x.card}, Driver ${x.date})`)
|
||||
console.log(` THEIRS-ONLY (ported nowhere on our side): ${theirsOnly.length}`)
|
||||
for (const s of theirsOnly.slice(0, 25)) console.log(` !! "${s.slice(0, 80)}"`)
|
||||
if (theirsOnly.length > 25) console.log(` … and ${theirsOnly.length - 25} more`)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
* DOXA_SKIP_FRESHNESS=1 skips (4) — for a DRY check only; main-capture scan never sets it.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { readdirSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
|
@ -42,8 +41,11 @@ if (dirty.stdout.trim()) refuse('the working tree differs from the commit being
|
|||
console.log('LANDING-PRECONDITIONS: 1 clean — the working tree is the commit being landed')
|
||||
|
||||
// 2. ONE FOLDER, and it is the configured one
|
||||
const pub = resolve(ROOT, 'public')
|
||||
const entries = existsSync(pub) ? readdirSync(pub).sort() : []
|
||||
// What LANDS is the commit, so read public/ from HEAD, not from the disk: a gitignored, untracked leftover
|
||||
// (a pre-rename public/assets/ in an older worktree) refused a clean README-only landing (repo, 2026-09-25).
|
||||
const tree = run('git', ['ls-tree', '--name-only', 'HEAD', 'public/'])
|
||||
if (tree.status !== 0) refuse('could not read public/ from HEAD')
|
||||
const entries = tree.stdout.split('\n').filter(Boolean).map((l) => l.replace(/^public\//, '')).sort()
|
||||
const want = ['README.md', TREE_NAME].sort()
|
||||
if (JSON.stringify(entries) !== JSON.stringify(want)) {
|
||||
const extra = entries.filter((e) => !want.includes(e)), missing = want.filter((e) => !entries.includes(e))
|
||||
|
|
@ -62,7 +64,13 @@ if (process.env.DOXA_SKIP_FRESHNESS === '1') {
|
|||
console.log('LANDING-PRECONDITIONS: 4 fresh — SKIPPED (DOXA_SKIP_FRESHNESS=1; dry check only, never set by main-capture scan)')
|
||||
} else {
|
||||
const fresh = run(process.execPath, ['internal/check-composite-freshness.mjs'])
|
||||
if (fresh.status !== 0) refuse('a committed composite is not what its source builds — rebuild it (BUNDLE=<name> bun x vite build, bun run tree, bun run fix:sri) and commit', (fresh.stdout + fresh.stderr).trim().split('\n').filter((l) => /✗|STALE|FAIL/.test(l)).slice(0, 8).join('\n'))
|
||||
if (fresh.status !== 0) refuse(
|
||||
'a committed composite is not what its source builds (stale consumers).\n' +
|
||||
'README rule 5: ONE agent owns the full build. If your change touches library/ or anything compiled into\n' +
|
||||
'other plugins, do not rebuild everyone yourself: commit it on your branch and hand the landing to studio\n' +
|
||||
'(msg-agent studio "please land <branch>@<sha>: library change, consumers need rebuilding"). If only YOUR\n' +
|
||||
'own plugin is stale, rebuild just it: BUNDLE=<name> bun x vite build && bun run tree && bun run fix:sri.',
|
||||
(fresh.stdout + fresh.stderr).trim().split('\n').filter((l) => /✗|STALE|FAIL/.test(l)).slice(0, 8).join('\n'))
|
||||
console.log('LANDING-PRECONDITIONS: 4 fresh — every composite rebuilds to its committed bytes')
|
||||
}
|
||||
console.log('LANDING-PRECONDITIONS: PASS — this commit may land.')
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import { resolve, dirname } from 'node:path'
|
|||
import { fileURLToPath } from 'node:url'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
// The data files this gate judges; declared to servesThisTree so their served bytes must equal disk.
|
||||
const GOSPEL_DATA = ['assets/tables/people-group-pins.json', 'assets/tables/doxa-gospel-access.json']
|
||||
const BASE = (process.env.BASE_URL || process.argv[2] || 'http://127.0.0.1:4173').replace(/\/+$/, '')
|
||||
const GL = ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--enable-webgl', '--no-sandbox']
|
||||
// The screened publishers. Anything else is a doctrine failure, not a typo.
|
||||
|
|
@ -79,13 +81,22 @@ let server = null
|
|||
const alive = async (b) => {
|
||||
const r = await fetch(`${b}/${TREE_NAME}/gospel-resources/resources-map/index.html`).catch(() => null)
|
||||
if (!(r && r.ok)) return false
|
||||
const tree = await servesThisTree(b, REPO)
|
||||
// The tables this gate judges are declared, so a server whose manifest matches but whose DATA does
|
||||
// not (another worktree) is not adopted — the gate starts its own server for THIS checkout instead.
|
||||
const tree = await servesThisTree(b, REPO, { assets: GOSPEL_DATA })
|
||||
if (tree.ok) return true
|
||||
console.log(`GOSPEL-MAP: not adopting ${b} — ${tree.why}`)
|
||||
return false
|
||||
return 'different' // reachable, but not THIS tree — distinct from "nothing there"
|
||||
}
|
||||
|
||||
if (!(await alive(base))) {
|
||||
const adopted = await alive(base)
|
||||
if (adopted === 'different' && EXPLICIT) {
|
||||
// You NAMED this server and it is another tree: that is NOT MEASURED, never a skip (exit 0 read as
|
||||
// green while the gate had measured nothing of this checkout).
|
||||
console.log(`GOSPEL-MAP: NOT MEASURED — ${base} is not serving THIS checkout (see above).`)
|
||||
process.exit(75)
|
||||
}
|
||||
if (adopted !== true) {
|
||||
if (EXPLICIT) {
|
||||
console.log(`GOSPEL-MAP: SKIP — nothing served at ${base} (you named it explicitly, so none was started).`)
|
||||
process.exit(0)
|
||||
|
|
@ -175,22 +186,15 @@ try {
|
|||
* The general weakness belongs to _serves-this-tree.mjs and every gate that relies on it;
|
||||
* this only closes it for the two files this gate actually measures.
|
||||
*/
|
||||
const sameAsDisk = async (rel) => {
|
||||
const res = await fetch(`${base}/${TREE_NAME}/${rel}`)
|
||||
if (!res.ok) return `not served (${res.status})`
|
||||
const served = await res.text()
|
||||
const disk = readFileSync(resolve(REPO, `public/${TREE_NAME}/${rel}`), 'utf8')
|
||||
return served === disk ? null : 'differs from this checkout — the server is another tree'
|
||||
}
|
||||
for (const rel of ['assets/tables/people-group-pins.json', 'assets/tables/doxa-gospel-access.json']) {
|
||||
const why = await sameAsDisk(rel)
|
||||
if (why) {
|
||||
console.error(`GOSPEL-MAP: NOT MEASURED — ${rel} ${why}.`)
|
||||
console.error(` Serving ${base}. Point BASE_URL at a server for THIS checkout, or stop the`)
|
||||
console.error(' one holding the port; a PASS here would be about somebody else\'s data.')
|
||||
if (server) server.kill()
|
||||
process.exit(2)
|
||||
}
|
||||
// (The per-gate copy of this check that used to live here is now the shared helper's `assets`
|
||||
// option — the weakness was in _serves-this-tree.mjs, so the fix is there, for every gate.)
|
||||
const ident = await servesThisTree(base, REPO, { assets: GOSPEL_DATA })
|
||||
if (!ident.ok) {
|
||||
console.error(`GOSPEL-MAP: NOT MEASURED — ${ident.why}.`)
|
||||
console.error(` Serving ${base}. Point BASE_URL at a server for THIS checkout, or stop the`)
|
||||
console.error(' one holding the port; a PASS here would be about somebody else\'s data.')
|
||||
if (server) server.kill()
|
||||
process.exit(75)
|
||||
}
|
||||
|
||||
const idxRes = await fetch(`${base}/${TREE_NAME}/assets/tables/people-group-pins.json`)
|
||||
|
|
|
|||
|
|
@ -223,6 +223,13 @@ const PS_SHADOW_CSS = `
|
|||
.ps-logo { display:inline-flex; align-items:center; }
|
||||
.ps-logo svg { display:block; }
|
||||
.ps-count { color:#6d766e; }
|
||||
/* THE HEADER'S MENUS SIT ABOVE EVERY PLUGIN (Driver, 2026-09-25: "the Mapbox token menu is not on top …
|
||||
it is underlapping the plugins"). The header row and the pane area are siblings with no stacking context
|
||||
of their own, so a menu at z-index 30 competed with each plugin's own chrome (a filter bar at 55, a drawer
|
||||
at 1000) and lost. The header now stacks above the content, and the pane area is ISOLATED: whatever
|
||||
z-index a plugin uses stays inside its pane. */
|
||||
.vtl-top { position:relative; z-index:3; }
|
||||
.vtl-body { isolation:isolate; }
|
||||
.ps-token { position:relative; display:inline-flex; }
|
||||
.ps-token-dot { width:8px; height:8px; border-radius:50%; background:#b8bdb4; }
|
||||
.ps-token-dot.is-yours, .ps-token-dot.is-url { background:#92b195; }
|
||||
|
|
@ -1058,6 +1065,13 @@ export default defineComponent({
|
|||
.ps-logo { display: inline-flex; align-items: center; }
|
||||
.ps-logo svg { display: block; }
|
||||
.ps-count { color: #6d766e; }
|
||||
/* THE HEADER'S MENUS SIT ABOVE EVERY PLUGIN (Driver, 2026-09-25: "the Mapbox token menu is not on top …
|
||||
it is underlapping the plugins"). The header row and the pane area are siblings with no stacking context
|
||||
of their own, so a menu at z-index 30 competed with each plugin's own chrome (a filter bar at 55, a drawer
|
||||
at 1000) and lost. The header now stacks above the content, and the pane area is ISOLATED: whatever
|
||||
z-index a plugin uses stays inside its pane. */
|
||||
.vtl-top { position:relative; z-index:3; }
|
||||
.vtl-body { isolation:isolate; }
|
||||
.ps-token { position:relative; display:inline-flex; }
|
||||
.ps-token-dot { width:8px; height:8px; border-radius:50%; background:#b8bdb4; }
|
||||
.ps-token-dot.is-yours, .ps-token-dot.is-url { background:#92b195; }
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-rRF96zozigk0kwJ6SwT1E2hyz91Mdu82TdfNYZP2pMIl86JsFV1byXVeuInovv9Y",
|
||||
"integrity": "sha384-Mx03ys3XIk0Ud2XgCBsjIUXS3K39uYVoTzyL0aLM4v6fK5Z96qM/1RvPRYLFX+dm",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/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=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-rRF96zozigk0kwJ6SwT1E2hyz91Mdu82TdfNYZP2pMIl86JsFV1byXVeuInovv9Y\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
"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=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-Mx03ys3XIk0Ud2XgCBsjIUXS3K39uYVoTzyL0aLM4v6fK5Z96qM/1RvPRYLFX+dm\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -782,7 +782,7 @@
|
|||
"bundle": "plugin-studio",
|
||||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"output": "dx-maps/plugin-studio/plugin-studio.js",
|
||||
"integrity": "sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb",
|
||||
"integrity": "sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d",
|
||||
"elements": [
|
||||
"plugin-studio"
|
||||
],
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -945,7 +945,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "dx-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-GsBGCy2M1ZnE7Xximx1HpGih2povZWx98661bjzLdaH6pnrTEhZj/PoPDuQ2gfJ5",
|
||||
"integrity": "sha384-QtpHA6LFYa0Ig1rB2KZtX32+djysHFL251q7PYRCpkuyCts2Zn4JA3KQtqNS2Gak",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -961,7 +961,7 @@
|
|||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/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=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-GsBGCy2M1ZnE7Xximx1HpGih2povZWx98661bjzLdaH6pnrTEhZj/PoPDuQ2gfJ5\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
"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=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-QtpHA6LFYa0Ig1rB2KZtX32+djysHFL251q7PYRCpkuyCts2Zn4JA3KQtqNS2Gak\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
},
|
||||
"bundle": "research-dashboard"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<plugin-studio tree-base="."></plugin-studio>
|
||||
<script src="./plugin-studio/plugin-studio.js" integrity="sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb" crossorigin="anonymous"></script>
|
||||
<script src="./plugin-studio/plugin-studio.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@
|
|||
"bundle": "plugin-studio",
|
||||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"output": "dx-maps/plugin-studio/plugin-studio.js",
|
||||
"integrity": "sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb",
|
||||
"integrity": "sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d",
|
||||
"elements": [
|
||||
"plugin-studio"
|
||||
],
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../plugin-studio.js" integrity="sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb" crossorigin="anonymous"></script>
|
||||
<script src="../plugin-studio.js" integrity="sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../../plugin-studio.js" integrity="sha384-diiEvybWLwF+XuXh9aAkhzgJCZN9bPezKiAd6ANxLVjR1kHXSg4T+XFI7XHrX0Zb" crossorigin="anonymous"></script>
|
||||
<script src="../../plugin-studio.js" integrity="sha384-lYyH0iKEqJ0Ps32Bbzbe3XVdfJZbYAZ6SaeP7DURDmt6yVbxMY1bYrJNUdLDcf4d" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||