{ "name": "doxa-maps", "version": "0.1.0", "license": "Apache-2.0", "private": true, "description": "A portable bundler that generates embeddable JAMstack maps — one library, many map bundles that embed on any site, compose into a lazy-loaded SPA, or wrap a Capacitor Android/iOS app.", "type": "module", "scripts": { "dev": "bun x vite", "build": "bash internal/build-lock.sh", "build:watch": "bun x vite build --watch", "preview": "bun x vite preview", "clean": "node internal/clean.mjs", "build:bundle": "bun x vite build", "tree": "node internal/generate-tree.mjs", "gallery": "node internal/generate-gallery.mjs", "check:drop": "node internal/drop-portability-check.mjs", "check:host-locale-contract": "node internal/check-host-locale-contract.mjs", "check:parse": "node internal/inline-parse-check.mjs", "verify": "node library/scripts/research/verify-all.mjs", "verify:full": "node library/scripts/research/verify-all.mjs --full", "serve": "node internal/serve-strict.mjs", "serve:shell": "python3 -m http.server 4174 --bind 127.0.0.1 --directory library/layouts/app-wrapper-material", "cap:sync": "./node_modules/.bin/cap sync android", "cap:verify": "test -f deployment/capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)", "check:tauri": "node internal/check-tauri-config.mjs", "tauri:build": "./node_modules/.bin/tauri build", "apk:debug": "bun run build && bun run cap:sync && bun run app:start-page && bun run cap:verify && cd deployment/capacitor/android && ./gradlew assembleDebug", "apk:install": "adb install -r deployment/capacitor/android/app/build/outputs/apk/debug/app-debug.apk", "phone": "bun run apk:debug && bun run apk:install", "shell": "node internal/install-shell.mjs --no-token", "posters": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", "posters:one": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs", "posters:shoot": "cd library/scripts/posters && DSF=2 node shoot.mjs", "posters:gallery": "cd library/scripts/posters && node build-gallery.mjs && node build-hd-gallery.mjs", "posters:export": "cd library/scripts/posters && node export-public.mjs", "posters:previews": "cd library/scripts/posters && python3 make-previews.py", "packets": "cd library/scripts/posters && VITE_API_BASE_URL=https://pray.doxa.life node packet.mjs", "packet-fixture": "node internal/generate-packet-fixture.mjs", "check:packet-fixture": "node internal/generate-packet-fixture.mjs --check", "packet:ci": "node internal/build-packet-fixtures.mjs", "token": "node internal/write-token.mjs", "smoke": "node internal/smoke.mjs", "smoke:embeds": "node internal/embed-smoke.mjs", "smoke:gallery": "node internal/plugin-gallery-smoke.mjs", "smoke:playground": "bun run smoke:gallery", "smoke:share": "node internal/share-roundtrip-smoke.mjs", "probe:legend-columns": "node internal/legend-columns-probe.mjs", "sync:dropin": "node internal/sync-dropin.mjs", "deploy:dev": "bun run build && node internal/sync-dropin.mjs --from-config --prune", "check:public-hygiene": "node internal/public-hygiene-check.mjs", "check:label-joins": "node internal/check-label-joins.mjs", "check:dxlife": "node internal/check-dxlife-untouched.mjs", "check:facet-cascade": "node internal/check-facet-cascade.mjs", "check:facet-cascade:self-test": "node internal/check-facet-cascade.mjs --self-test", "check:facet-one-field": "node internal/check-facet-one-field.mjs", "check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test", "check:facet-changes-map": "node internal/check-facet-changes-map.mjs", "check:facet-changes-map:built": "sh -c 'BASE_URL=${BASE_URL:-http://127.0.0.1:4173} PROBE_PATH=${PROBE_PATH:-/doxa-maps/research-dashboard/research-dashboard/index.html} exec node internal/check-facet-changes-map.mjs'", "check:facet-changes-map:self-test": "node internal/check-facet-changes-map.mjs --self-test", "check:heartbeat": "node internal/check-heartbeat-threshold.mjs", "check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test", "build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery", "check:card-deck": "node internal/check-card-deck.mjs", "check:poster-raster": "node internal/check-poster-raster.mjs", "check:deck-click": "node internal/check-deck-click.mjs", "check:i18n-parity": "node internal/check-i18n-parity.mjs", "check:locale-reachable": "node internal/check-locale-reachable.mjs", "check:poster-prints": "node internal/check-poster-prints.mjs", "check:provider-switch": "node internal/check-provider-switch.mjs", "check:legend-export-reachable": "node internal/check-legend-export-reachable.mjs", "check:poster-card-fits": "node internal/check-poster-card-fits.mjs", "check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs", "check:share-policy": "node internal/check-share-policy.mjs", "check:zone-census": "node internal/check-zone-census.mjs", "check:print-geometry": "node internal/check-print-geometry.mjs", "check:packet-browser": "node internal/check-packet-browser.mjs", "check:packet-render": "node internal/check-packet-render.mjs", "check:template-literal": "node internal/template-literal-check.mjs", "verify:map-paint": "node internal/verify-map-paint.mjs", "verify:gospel-map": "node internal/verify-gospel-map.mjs", "check:sri": "node internal/sri-consistency-check.mjs", "fix:sri": "node internal/fix-sri.mjs", "check:composite": "node internal/check-composite-freshness.mjs", "check:shots": "node internal/check-shot-freshness.mjs", "check:library-index": "node internal/check-library-index.mjs", "check:quoted-scripts": "node internal/check-quoted-scripts.mjs", "capability-exemplars": "node internal/generate-capability-exemplars.mjs", "check:a11y-button-names": "node internal/check-a11y-button-names.mjs", "check:catalog-identity": "node internal/check-catalog-identity.mjs", "check:catalog-currency": "node internal/check-catalog-currency.mjs", "check:capability-exemplars": "node internal/check-capability-exemplars.mjs", "check:capability-citations": "node internal/check-capability-citations.mjs", "check:shot-distinctness": "node internal/check-shot-distinctness.mjs", "check:shot-fidelity": "node internal/check-shot-fidelity.mjs", "shots": "node internal/readme-shots.mjs", "library-index": "node internal/generate-library-index.mjs", "check:done-evidence": "node internal/check-done-evidence.mjs", "check:wp-embed": "php deployment/wordpress/tests/wp-embed-contract-test.php", "check:mobile": "bun run cap:sync && bun run cap:verify", "check:settled": "node internal/settled-tree-gate.mjs", "verify:5-caps": "node internal/verify-5-capabilities.mjs", "verify:gate1": "node internal/acceptance/big-picture-acceptance.mjs", "verify:interop": "node internal/acceptance/interop-harness.mjs", "verify:interop:self-test": "node internal/acceptance/interop-harness.mjs --self-test", "verify:interop:self-test:camera-freeze": "node internal/acceptance/interop-harness.mjs --self-test --self-test-mode=camera-freeze", "check:descriptions": "node internal/check-profile-descriptions.mjs", "check:no-abs-paths": "node internal/check-no-absolute-paths.mjs", "check:no-codename": "node internal/check-no-internal-codename.mjs", "check:dep-names": "node internal/check-dependency-names.mjs", "check:embed-targets": "node internal/check-embed-targets-exist.mjs", "check:composite-srcs": "node internal/check-composite-srcs.mjs", "check:nested-token": "node internal/check-nested-token-delivery.mjs", "check:nav-reachable": "node internal/check-nav-reachable-mobile.mjs", "check:fits-screen": "node internal/check-fits-the-screen.mjs", "check:fits-screen:self-test": "node internal/check-fits-the-screen.mjs --self-test", "check:fits-screen:self-test:b": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=b", "check:fits-screen:self-test:c": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=c", "check:fits-screen:self-test:d": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=d", "check:fits-screen:self-test:e": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=e", "verify:browser-all": "node internal/verify-browser-all.mjs", "check:shot-fonts": "node internal/check-shot-fonts.mjs", "smoke:share-all": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/share-roundtrip-smoke.mjs --url \"$B/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/all-people-groups/all-people-groups/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/country-posters-mfe/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/demo-map/demo-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-research-map/research-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-simple-map/doxa-simple-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/gospel-resources/resources-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-dashboard/research-dashboard/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/my-upg-100-list/upg-100-mfe/index.html?tk=$MAPBOX_TOKEN\" --browser chromium'", "smoke:legend-info": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-legend-info-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", "smoke:aria-labels": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/check-aria-labels-localized.mjs \"$B/doxa-maps/my-upg-100-list/upg-100-list/index.html\" es,ar,zh'", "check:sfc-parse": "node internal/check-sfc-script-parses.mjs", "check:url-resolver": "node internal/check-url-resolver.mjs", "check:studio-all": "node internal/check-studio-every-plugin.mjs", "check:basemap-labels": "node internal/check-basemap-labels-localized.mjs", "check:vision-card-numbers": "node internal/check-vision-card-numbers.mjs", "check:map-embed-cells": "node internal/check-map-embed-cells.mjs", "check:map-unmount-race": "node internal/check-map-unmount-guard.mjs", "check:map-unmount-mid-init": "node internal/check-map-unmount-mid-init.mjs", "smoke:studio": "node internal/smoke-studio.mjs", "check:manifest-covers": "node internal/check-manifest-covers-discovery.mjs", "check:csv-shape": "node internal/check-csv-export-shape.mjs", "check:csv-shape:self-test": "node internal/check-csv-export-shape.mjs --self-test", "check:verification-tags": "node internal/check-verification-tags.mjs", "check:fixed-anchoring": "node internal/check-fixed-anchoring.mjs", "check:scrim-viewport": "node internal/check-scrim-covers-viewport.mjs", "check:scrim-viewport:self-test": "node internal/check-scrim-covers-viewport.mjs --self-test", "check:no-public-binds": "node internal/check-no-public-binds.mjs", "check:facet-cascade:offline": "node internal/check-facet-cascade.mjs --offline", "check:shot-fidelity:strict": "node internal/check-shot-fidelity.mjs --strict", "check:embed-targets:self-test": "node internal/check-embed-targets-exist.mjs --self-test", "verify:browser-all:plugin-gallery": "node internal/verify-browser-all.mjs --plugin-gallery", "check:script-flags": "node internal/check-script-flags-reachable.mjs", "check:shell-scroll": "node internal/verify-shell-scroll.mjs", "check:no-orphan-gates": "node internal/check-no-orphan-gates.mjs", "check:one-palette": "node internal/check-one-palette.mjs", "check:kpi-unit": "node internal/check-kpi-unit-once.mjs", "check:tab-radius": "node internal/check-tab-radius-token.mjs", "check:tab-radius:self-test": "node internal/check-tab-radius-token.mjs --self-test", "check:synced-css": "node internal/check-synced-css-copies.mjs", "check:composite-reg": "node internal/check-composite-registration.mjs", "check:filter-width": "node internal/check-filter-width-stable.mjs", "check:gallery-embed": "node internal/check-research-gallery-embed.mjs", "check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js", "check:gallery-embed:iframe": "node internal/check-research-gallery-embed.mjs --mode iframe", "check:gallery-embed:maplibre": "node internal/check-research-gallery-embed.mjs --provider maplibre", "check:gallery-embed:live-cors": "node internal/check-research-gallery-embed.mjs --live-cors", "check:prayer-walk-planner": "node internal/check-prayer-walk-planner.mjs", "check:spa-tab-order": "node internal/check-spa-tab-order.mjs", "check:backlog-status": "node internal/check-backlog-status-enum.mjs", "check:backlog-refs": "node internal/check-backlog-refs.mjs", "check:mjs-parse": "node internal/check-mjs-parses.mjs", "check:snapshot-guard": "node internal/check-snapshot-guard.mjs", "check:snapshot-guard:poison": "node internal/check-snapshot-guard.mjs --poison", "check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs", "check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison", "check:click-sheet": "node plugin-designs/gospel-resources/scripts/verify-gospel-click-sheet.mjs", "check:staging-lang": "node internal/check-staging-forwards-lang.mjs", "check:stale-sweep-claim": "node internal/check-no-stale-sweep-claim.mjs # a caveat that has stopped being true is a wrong number. The gospel surfaces carry a paragraph saying which pools were searched; while the GRN sweep ran they truthfully said part of it was unread, the sweep finished, and the sentence stayed for four more commits. The DATA decides here: undecided gap codes mean the claim is allowed, all-decided means it must go, so it re-arms itself for the next slow pool. Does NOT flag a claim rendered under a v-if on the pending count — that form expires on its own. Self-test: `the check:stale-sweep-claim:poison entry`. Static, sub-second.", "check:surfaces-read-the-tier": "node internal/check-surfaces-read-the-tier.mjs", "check:surfaces-read-the-tier:poison": "node internal/check-surfaces-read-the-tier.mjs --poison", "check:link-rot": "node internal/check-link-rot-sample.mjs", "check:embed-basemap": "node internal/check-embed-basemap-paints.mjs", "check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test", "serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public", "check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs", "check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test", "check:legend-source": "node internal/check-legend-matches-source.mjs", "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts", "gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source check:backlog-refs check:composite-embed-knobs check:linked-vocabulary", "build:bundles": "node internal/run-parallel.mjs --bundles", "check:composite-embed-knobs": "node internal/check-composite-embed-knobs.mjs", "check:linked-vocabulary": "node plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs", "check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs", "check:tabs-localized": "node internal/check-tabs-localized.mjs", "check:packets-all": "node internal/check-packets-all.mjs", "check:poster-print-fallback": "node internal/check-poster-print-fallback.mjs", "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", "check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs", "check:upg-map-data": "node internal/check-upg-map-data-painted.mjs", "check:published": "node internal/check-published-matches-build.mjs", "check:shared-reached": "node internal/check-shared-component-reached-bundles.mjs", "check:tree-weight": "node internal/check-published-tree-weight.mjs", "check:i18n-never-translated": "node internal/check-i18n-never-translated.mjs", "check:embed-base": "node internal/check-embed-base-resolution.mjs", "check:composed-filters": "node internal/check-gospel-composed-filters.mjs", "app:start-page": "node internal/set-app-start-page.mjs", "check:app-start-page": "node internal/set-app-start-page.mjs --check", "check:leaves-mount": "node internal/check-gospel-leaves-mount.mjs", "check:leaves-mount:poison": "node internal/check-gospel-leaves-mount.mjs --poison", "check:gospel-all": "node internal/check-gospel-all.mjs", "check:no-conflict-markers": "node internal/check-no-conflict-markers.mjs", "check:no-conflict-markers:poison": "node internal/check-no-conflict-markers.mjs --poison", "check:english-leaks": "node internal/check-gospel-english-leaks.mjs", "check:poster-wall-localised": "node internal/check-poster-wall-localised.mjs", "check: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.", "gallery": "writes public/__gallery/index.html — the ARCHIVED bundle-index gallery, from public/doxa-maps/manifest.json. It is NO LONGER the root page: as of 2026-09-22 `/` mounts in BOTH trees (the built root page is written by `bun run tree`), and the gallery is the opt-in index at /__gallery. Archived, not deleted — the dev server imports renderGallery()/readBuildConfig() from this module.", "serve": "serves public/ the way PRODUCTION does: a DIRECTORY request returns 404, no index fallback. This was python3 -m http.server, which DID resolve a directory to index.html while the live static layer does not — so gates passed on URLs that 404 live (measured 2026-09-23; it cost a false \"publish failed\" reading and hid a bad URL in verify-shell-scroll for a whole flight). Loopback only, same as before. Override port/host with PORT/HOST.", "serve:shell": "LOOPBACK ONLY, same reason as `serve` above — a local preview server has no business answering on a public interface.", "cap:sync": "NOT `bunx` — bunx is not on PATH in this environment (command not found, exit 127), which broke cap:sync -> apk:debug -> phone at step one. The CLI IS installed locally (@capacitor/cli 7.6.9); invoke the local bin directly. The rest of package.json already uses the spaced `bun x` form.", "shell": "OPTIONAL, not part of build: overlays a layouts/ wrapper onto public/ for local use (the served root is the generated index.html); --no-token so the local credential is never copied into the served root", "posters:one": "ONLY= to build one", "packets": "the prayer packets. READS out/posters/.html for page 1's map, which `bun run posters` writes — without it the packet still builds and page 1 is simply BLANK, so the script now warns per-zone AND in its summary. PACKET_LANG= for one language, ONLY= for one zone, NOPHOTOS=1 to skip the photo fetch.", "packet-fixture": "regenerate the committed records subset (smallest zone, image_url stripped) that lets CI build a packet at all — out/ is gitignored, so a clean checkout has neither the records cache nor any packet.", "check:packet-fixture": "the committed fixture must still match clusters.json and must never carry image_url (CI would fetch 43 photos from someone else's CDN).", "packet:ci": "build EVERY committed fixture, no network, each renamed to its own output so they coexist. Was one fixture (tibet); packet.mjs names its output for the ZONE not the fixture, so sequential builds overwrote each other and check:packets-all saw a single SHAPE while reporting success. The fixtures are chosen at the PER_PAGE=20 page boundary — exactly 20, 21, and 1 group — because laos (95) and tibet (43) are both mid-range remainder cases and the boundary itself had never rendered.", "deploy:dev": "destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST= overrides the destination", "check:dxlife": "⚠ NOT WAVE-SAFE, deliberately unwired: exit codes 0/1/2 are a DOCUMENTED contract (0 unchanged · 1 drift · 2 clone not found), and run-parallel grades any non-zero as FAILURE — so a checkout without the sibling /root-dx-life clone goes red on a precondition. Changing the contract could break another consumer; hand-run it, or give it a recognised SKIP first.", "check:facet-cascade": "a child facet selection must not outlive its parent: clearing a parent facet must cascade-clear its descendants on EVERY clear path. Asserts on RESULT COUNTS, not DOM — an orphaned child silently ANDs the map to empty. run check:facet-cascade:self-test for the NEGATIVE test (reverts the cascade and requires this gate to FAIL).", "check:facet-one-field": "ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r. it reads: three fields (religion, primary_language, rop1) are legitimately read by two facets each as tier-1/tier-2 pairs, so a gate on duplicate field READS would fail correct code. Scoped to facets a user can actually select (rail + dropdowns + charts), because a search-only projection sharing a partition is fine until it becomes a control. run check:facet-one-field:self-test for the NEGATIVE test.", "check:facet-changes-map": "a facet selection must CHANGE THE DOTS — and the dots must SAY what they mean. Asserts (1) the rd-bubbles feature set changes by count AND identity, (2) every surviving dot satisfies the selected level, (3) while a prayer filter is active circle-color is a DATA-DRIVEN EXPRESSION reading peoplePraying, not a flat literal. Assertion 3 is the one that matters: the prayer filter ALREADY narrowed 2107->689 correctly while the Driver reported it broken, because every dot painted the same orange. Browser gate (needs `bun run dev` on 5173, or BASE_URL/PROBE_PATH at a served tree), so NOT in build:unlocked. run the `check:facet-changes-map:self-test` script for the NEGATIVE test.", "check:facet-changes-map:built": "the SAME gate against the BUILT tree instead of the dev server. Measured 2026-09-24: the dev default is 1 PASS / 3 FAIL in four runs on a busy box (88 unbundled module requests vs the built leaf's 3), while the built tree passes deterministically. Use this when you need a trustworthy answer; use the dev default when checking uncommitted source.", "check:facet-changes-map:self-test": "reverts the fix IN THE LIVE PAGE and requires the gate to FAIL. SELF_TEST_MODE=colour (default, isolates the colour assertion — the reported bug) | props | both | frozen (blocks the source update, isolating the set assertion). All four modes verified caught.", "check:heartbeat": "the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config AND the shipped config must still render today's ramp. Two sides: (B) a configured threshold must MOVE the edge — catches re-hard-coding; (A2) dashboard.config.js's own map.* must still resolve to 5/20/60/150/Infinity — catches the shipped default silently changing. A2 was MISSING at first and heartbeatThreshold:999 shipped past this gate printing 'default ramp unchanged'. A grep for '150' cannot do either job: it false-positives on the legitimate default and false-negatives on a re-hard-coding at 200. run check:heartbeat:self-test for the NEGATIVE tests (3 cases).", "check:heartbeat:self-test": "THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.", "check:host-locale-contract": "a HOST's profile-config.locale reaches every leaf: each leaf page is loaded with ONLY \"locale\":\"ar\" added to its profile-config (no ?lang=, the page keeps lang=\"en\") and must draw at least half the Arabic text the same page draws at ?lang=ar. regions-dashboard/regions-list is the positive control (NOT MEASURED if it does not enter Arabic). Guards ProfileLoader's inject('appI18n'): a bundle that never provides appI18n silently ignores the host. --dir for a before/after, --only a/b,c/d to narrow. Through browser-slot.sh.", "check:provider-switch": "the provider switch must actually SWITCH: counts requests BY HOST (not window globals, which stay defined after a swap) and asserts the maplibre run stays within the leaf HTML's own declared api.mapbox.com preloads. Defaults to the upg-100 leaf; pass a URL or set BASE_URL for another. CONDITIONAL: SKIPs when nothing is served.", "check:legend-export-reachable": "the Export affordance must be reachable at BOTH widths. Selects a leaf row first (the control is gated on selectedNode) and opens the mobile sheet first (it starts closed). Asserts a REAL BOX, not CSS visibility — on mobile the elements report visibility:visible at 0x0. PASSES at both widths since the mobile sheet was given :export-enabled and :tabs. SKIPs when nothing is served.", "check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.", "check:zone-census": "every zone a USER CAN SELECT must have a poster (asks the LEGEND, not clusters.json — two collapsed deaf lists are unreachable and not a gap). CONDITIONAL: SKIPs without out/posters/, which is gitignored — run `bun run posters` first. It cannot be fixtured like check:packets-all, because it asserts COVERAGE of real generated output and a fixture would only assert the fixture is complete.", "check:packet-browser": "the packet the READER gets — the legend's Export > Prayer packet path, proving the shared renderer runs in a BROWSER. Asserts the same 12 points / 12 wheel labels / page-1 geography / print layer as check:packet-render, which it did not until 2026-09-24. CONDITIONAL: SKIPs without a server over the repo root (python3 -m http.server 4898) and a reachable records API.", "verify:gospel-map": "the gospel-resources MAP: pins render (not merely load), a tap opens a named group, EVERY link in the index is on a screened publisher, the table deep-link carries the group's ISO, and the gap filter leaves only tier 0. Needs a server (bun run serve); SKIPS loudly without one.", "check:composite": "a COMPOSITE is a COPY taken at its own build time: fails if a nested child's source is newer than the parent's bundle. NOT in build:unlocked on purpose — there everything was just built, so it could never catch the real case (a PARTIAL build). Run it before PUBLISH.", "check:shots": "a README screenshot that predates the bundle it shows is a picture telling a lie — and it looks IDENTICAL to a current one. Compares each shot's PNG against the BUILT BUNDLE it renders (not raw sources: an unbuilt edit is not yet in any leaf). Fix: node internal/readme-shots.mjs", "check:library-index": "the DERIVED library menu must not DRIFT from library/. REGENERATES the index into a throwaway tree (the generator, unmodified, over a symlinked library/) and diffs the BYTES — it used to compare mtimes, so `touch` or a rebase turned it red on a byte-identical doc. Fix: bun run library-index", "check:quoted-scripts": "documentation/README-SECTION-3-cli-commands.md QUOTES the scripts block verbatim; it had silently drifted TWICE (a stale bunx cap:sync, and build:unlocked missing check:public-hygiene + check:label-joins). package.json is what RUNS — update the DOC.", "capability-exemplars": "regenerate documentation/reference-capability-exemplars.md — the DERIVED answer to \"which bundle is the capability-N example?\", a question answered WRONGLY in three separate artifacts (a gate, the README, the /plugin-designer skill).", "check:a11y-button-names": "every VISIBLE button must have an accessible name (aria-label / aria-labelledby / visible text — a `title` is a TOOLTIP and does NOT count). Measures the RENDERED DOM through shadow roots: a source grep cannot answer this (132 `