Main moved 20+ commits while the regions work was on its own branch. Every
conflict was a GENERATED file — manifest.json, the root page, and the
regions-dashboard bundle's .js and leaf pages. Zero source conflicts: all six
of my source files (the two regions .vue, the new map child, fix-sri.mjs,
sri-consistency-check.mjs, package.json) auto-merged.
Generated files are not hand-merged. They were resolved to main's side and then
REGENERATED from the merged source — BUNDLE=regions-dashboard vite build, then
bun run tree, then bun run fix:sri. `bun run tree` rewrote every bundle's pages
and produced byte-identical output to main's committed ones for all twenty
other bundles, so the only files this commit changes beyond the merge are the
six that had to be rebuilt. That agreement is also evidence main's committed
tree matches main's source.
The pinHint:false regression I saw before is gone: main's 566ad46 committed the
two metas that were previously only on disk, which was the root cause — a tree
regeneration now preserves them instead of dropping them.
Re-verified against the merged library, which is the part that mattered: main
changed useMapInstance.js, StackLayout.vue and ViewSelect.vue, all of which the
regions element sits on. All three suites still pass — the map draws 934 pins /
51 outline features for a region and 61 / 5 for a country, the pin mode still
follows the subject, the country page still renders CountryDetail's shape, and
every width still matches DX-life's container formula at six viewports.
23 of 25 gates green. The two that fail are pre-existing and not in this diff:
check:library-index is an mtime artifact (regenerating the index produces
byte-identical content), and check:backlog-status reports 6 of 546 non-enum
statuses in the shared _qa-cycle board, which lives outside every repo.
158 lines
29 KiB
JSON
158 lines
29 KiB
JSON
{
|
||
"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 # 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 <plugin-studio> 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.",
|
||
"check:drop": "node internal/drop-portability-check.mjs",
|
||
"check:parse": "node internal/inline-parse-check.mjs",
|
||
"verify": "node library/scripts/research/verify-all.mjs",
|
||
"verify:full": "node library/scripts/research/verify-all.mjs --full",
|
||
"serve": "node internal/serve-strict.mjs # 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": "python3 -m http.server 4174 --bind 127.0.0.1 --directory library/layouts/app-wrapper-material # LOOPBACK ONLY, same reason as `serve` above — a local preview server has no business answering on a public interface.",
|
||
"cap:sync": "./node_modules/.bin/cap sync android # 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.",
|
||
"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 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 # 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": "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 # ONLY=<zoneKey> to build one",
|
||
"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",
|
||
"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 # destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST=<dir> overrides the destination",
|
||
"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 # 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-cascade:self-test": "node internal/check-facet-cascade.mjs --self-test",
|
||
"check:facet-one-field": "node internal/check-facet-one-field.mjs # ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r.<field> 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-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test",
|
||
"check:facet-changes-map": "node internal/check-facet-changes-map.mjs # 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:self-test": "node internal/check-facet-changes-map.mjs --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": "node internal/check-heartbeat-threshold.mjs # 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": "node internal/check-heartbeat-threshold.mjs --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.",
|
||
"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:template-literal": "node internal/template-literal-check.mjs",
|
||
"verify:map-paint": "node internal/verify-map-paint.mjs",
|
||
"check:sri": "node internal/sri-consistency-check.mjs",
|
||
"fix:sri": "node internal/fix-sri.mjs",
|
||
"check:composite": "node internal/check-composite-freshness.mjs # 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": "node internal/check-shot-freshness.mjs # 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": "node internal/check-library-index.mjs # the DERIVED library menu must not fall behind library/. Compares the index against the newest library SOURCE (not a bundle — this generator scans library/ directly, with no build step between). Fix: bun run library-index",
|
||
"check:quoted-scripts": "node internal/check-quoted-scripts.mjs # 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": "node internal/generate-capability-exemplars.mjs # 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": "node internal/check-a11y-button-names.mjs # 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 `<button>` hits, most with visible text). Needs a browser, so NOT in build:unlocked and NOT in CI. ALL=1 sweeps every profile.",
|
||
"check:catalog-identity": "node internal/check-catalog-identity.mjs # proves the LIVE server serves the CATALOG’s exact bytes, by hashing each live bundle and comparing sha384 against the manifest’s own integrity field. An HTTP 200 cannot do this: /app/doxa-maps/ returns 200 with a 2872-byte HTML SIGN-IN PAGE. Network-dependent, so NOT in build:unlocked and NOT in CI; SKIPs cleanly when unreachable. CATALOG=<dir> also requires the live manifest to match a catalog checkout.",
|
||
"check:catalog-currency": "node internal/check-catalog-currency.mjs",
|
||
"check:capability-exemplars": "node internal/check-capability-exemplars.mjs # REGENERATES and content-compares (not mtimes: a full build rewrites manifest.json every time, so an mtime rule would cry wolf after every build and get switched off).",
|
||
"check:capability-citations": "node internal/check-capability-citations.mjs # a doc that names a bundle as \"capability N\" must match manifest.json, which is what the BUILD computed. README captioned doxa-vision-card as cap-1; it ships 2 instances so it is parameterized (cap-2). The SAME error was fixed once in verify-5-capabilities.mjs and never reached the README — two copies of one fact, nothing forcing agreement.",
|
||
"check:shot-distinctness": "node internal/check-shot-distinctness.mjs # two README slots must not show the SAME view. README shipped \"List — page 1\" and \"page 2\" as the same picture: the shot chased a Next control and a scroll that DO NOT EXIST, every attempt inside try/catch so the miss was silent, and the PNGs differed by 0.16% render noise so no checksum caught them. Keys on url+VIEWPORT — same url at a different vp (deploy2 mobile) is intended.",
|
||
"check:shot-fidelity": "node internal/check-shot-fidelity.mjs # warns when THIS machine cannot RENDER what the shots must SHOW. `bun run shots` printed 13x OK while every emoji rendered as a tofu box — a glyph fallback loads perfectly (0 console errors, 0 HTTP>=400), so the generator cannot see it. Reports by default; the `check:shot-fidelity:strict` script to block once an emoji font is installed.",
|
||
"shots": "node internal/readme-shots.mjs # regenerate documentation/readme-images/*.png (fetches its own Mapbox token SERVER-SIDE, so the browser CORS block does not apply). Was UNREACHABLE — present but invoked by no script, which is how the 12 PNGs silently rotted a day behind the UI.",
|
||
"library-index": "node internal/generate-library-index.mjs # regenerate documentation/reference-library-index.md — the DERIVED menu of the 84 reusable library pieces an author can import instead of rebuilding. Was UNREACHABLE, and the index did not exist at all: its header promised 'can't drift from the code' while nothing ever ran it.",
|
||
"check:done-evidence": "node internal/check-done-evidence.mjs # a card may not reach status:done with NO bluf AND NO fields — the Doxa team inherits this board and an evidence-less 'done' cannot be reconciled against the code. 10 pre-existing are baselined by id; the list should only ever SHRINK.",
|
||
"check:wp-embed": "php deployment/wordpress/tests/wp-embed-contract-test.php",
|
||
"check:mobile": "bun run cap:sync && bun run cap:verify # deploy-2 CONTRACT check: needs NO JDK/SDK/device. cap sync is a file copy + plugin wiring, so it runs anywhere; cap:verify then asserts assets/public/index.html exists. Guards the SOFT FAILURE capacitor.config.json warns about: a wrong webDir yields an EMPTY assets/public and a BLANK WHITE SCREEN at launch, not an error.",
|
||
"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 # the Gate-1 chain end-to-end against LIVE data: header emits research-filter -> composite passes props down -> the REAL cap-2 grid re-filters -> count reflected back up. Asserts the NEGATIVE (a filter that must return 0 does) and RESET. Was an ORPHAN — present in internal/acceptance/ but invoked by NO script, so it could never report anything.",
|
||
"verify:interop": "node internal/acceptance/interop-harness.mjs # the CAP-4 INTEROPERABILITY GATE (real browser, real shadow DOM, real live data) — closes the gap verify:gate1 leaves open. Drives the ACTUAL compiled composites: (1) DESKTOP dashboard-plus-grid map->grid (a facet-rail click must narrow the grid's live `Showing N` count line — card count is a proven FALSE-NEGATIVE trap, always 24, never asserted on), (2) DESKTOP grid->map (selecting table rows must MOVE THE CAMERA, read back off window.__rdashMap.getCenter()/getZoom() — useSelectionCamera.js's contract), (3) MOBILE research-map-plus-grid, where source-reading FIRST established there is NO cross-wire between the map and grid children by design (StackLayout composes two independent elements, no provide/inject/prop crosses the boundary) — so this pair asserts each half narrows on its OWN control, not a link that does not exist. Prints ONE line per combination, pass/fail only, plus a combination table. SKIPs cleanly (exit 0) when the tree is mid-build (internal/settled-tree-gate.mjs) or pray.doxa.life is unreachable — never reports a false defect from a torn tree. Spawns its own internal/serve-strict.mjs on PORT (default 4180) so it does not depend on another process's server. run `verify:interop:self-test` for the NEGATIVE test.",
|
||
"verify:interop:self-test": "node internal/acceptance/interop-harness.mjs --self-test # TWO regression modes, each required to fire ITS OWN combination-named failure: grid-prop-drop (default — freezes the grid child's filter props/attrs on the live dashboard-plus-grid page, reproducing the exact historical bug \"selecting anything in the dashboard above did not filter the grid below\") and --self-test-mode=camera-freeze (pins fitBounds/flyTo to no-ops on window.__rdashMap, reproducing \"the grid->map link stops moving the camera\"). A harness that has only ever reported PASS is unproven.",
|
||
"check:descriptions": "node internal/check-profile-descriptions.mjs # every shipped profile must say what it is (reads the BUILT manifest)",
|
||
"check:no-abs-paths": "node internal/check-no-absolute-paths.mjs # no /home/… or /Users/… in any TRACKED file (a clone must work on any machine)",
|
||
"check:no-codename": "node internal/check-no-internal-codename.mjs # the development-era INTERNAL CODE NAME may not ship in any TRACKED file. Same hand-off family as check:no-abs-paths: meaningless to the Doxa team who inherit this, and invisible to every other gate (none read for vocabulary). Deliberately does NOT fail on the same letters appearing as the unix USERNAME inside a /home/… path — that is a portability defect check:no-abs-paths already reports better, and two gates disagreeing about one string teaches everyone to ignore both.",
|
||
"check:dep-names": "node internal/check-dependency-names.mjs # every declared dep is a REAL package name (a rename once rewrote @capacitor/* and broke bun install in a clone)",
|
||
"check:embed-targets": "node internal/check-embed-targets-exist.mjs # every embed.iframe src in manifest.json must resolve to a file that EXISTS. Those snippets ARE deployment-method 1 (iframe) — a partner pastes them verbatim — and NOTHING else asserts their targets are still emitted, because every other gate walks only files that exist and is blind to ABSENCE. `the `check:embed-targets:self-test` script` poisons a copy of the manifest and asserts the gate NOTICES: a gate that cannot fail is worse than no gate.",
|
||
"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 # a cap-5 shell must have ONE on-screen control that opens its tab rail at phone width (needs `bun run serve` on 4173; browser gate, not in build:unlocked)",
|
||
"check:fits-screen": "node internal/check-fits-the-screen.mjs # THE FITNESS-FOR-USE GATE (LL-the-user-found-three-bugs-my-gates-could-not-see): a green STATE suite (mounts/fetches/counts/paints) said nothing about whether the product is USABLE, and the Driver found five real defects in one sitting that no gate saw. Asserts 5 clauses on the built dashboard-plus-grid composite + research-dashboard leaf, real browser, live data: (A) the dashboard band fits ONE 1440x900 viewport and is shorter than its own tree-legend's scrollHeight; (B) the people-groups grid is still reachable by scrolling underneath it (a fix for A must not re-break this — AP-fixing-reachability-by-making-the-thing-taller); (C) a wheel over the map changes ZOOM and does not scroll the pane, a wheel below it scrolls and does not zoom — instrumented on the DISPATCHED EVENT (composedPath), not geometry, per LL-four-wrong-diagnoses-instrument-the-event-not-the-geometry; (D) the grid's live count ([data-table-count], the composite's documented single source of truth — show-total=false on the grid child is deliberate) is visible and CHANGES when a facet narrows it; (E) a 1440/1280/768/390 responsive sweep has no horizontal document overflow and no element clipped to zero height (.rd-td/.rd-th ellipsis excluded — expected). Spawns its own serve-strict on PORT (default 4391); SKIPs (exit 0) if the tree is not settled at START OR re-settled at END, or pray.doxa.life is unreachable — never reports a defect from a torn tree. Also best-effort probes any cap-5 shell the manifest declares (type nested-mfe-spa) by CONTENT-MATCHING a tab, never by hardcoded structure, so it survives the shell's own in-flight restructure without depending on it. Needs a built tree (`bun run build`). run `check:fits-screen:self-test` for the NEGATIVE test.",
|
||
"check:fits-screen:self-test": "node internal/check-fits-the-screen.mjs --self-test # FIVE regression modes, one per clause, each required to fire ITS OWN [A]/[B]/[C]/[D]/[E]-named failure: --self-test-clause=a (default — forces the dashboard section past the viewport, the AP-fixing-reachability shape), =b (n/a here — B is proven by A's own poison already trapping the grid below the fold; kept as a mode for a future independent poison), =c (freezes map.scrollZoom enabled, the BUG-MAP-EATS-THE-WHEEL regression), =d (blanks the live count element), =e (forces the shell wider than the viewport at 1440). A gate that has only ever printed PASS is unproven.",
|
||
"verify:browser-all": "node internal/verify-browser-all.mjs # runs EVERY browser gate in the GATES array over the built tree in one command (needs `bun run serve` on 4173 + MAPBOX_TOKEN). Deliberately does NOT state a count: GATES lives in the .mjs and grows, so a number here rots on the next append — it said \"ALL 7\" while the array held 9. The script prints its own GATES.length at runtime; trust that. Every gate listed was RUN before being listed. The `verify:browser-all:plugin-gallery` mode is separate because it needs the vite dev server.",
|
||
"check:shot-fonts": "node internal/check-shot-fonts.mjs # can THIS machine render the emoji the UI uses? Run BEFORE `bun run shots` — no font = tofu boxes in every committed PNG, which every other shot gate passes. Deliberately NOT in build:unlocked: it reports an ENVIRONMENT condition (this box has no emoji font), and a gate that always fails in the build makes the build useless.",
|
||
"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/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/countries-map/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/my-upg-100-list/upg-100-list/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'",
|
||
"check:sfc-parse": "node internal/check-sfc-script-parses.mjs # every .vue script block must parse as the BUILD parses it — catches the LIVE-CSS backtick that check:template-literal cannot see (it reads CSS comments only). 0 false positives on 83 files, where 3 lookahead heuristics scored 98 and 136.",
|
||
"check:url-resolver": "node internal/check-url-resolver.mjs # cap-5 resolve() must not prefix a rooted URL (the dev-surface 404 class)",
|
||
"smoke:studio": "node internal/smoke-studio.mjs # 5s DEV-surface check: / mounts the studio, tabs>0, 0 4xx (exit 2 = dev server down)",
|
||
"check:manifest-covers": "node internal/check-manifest-covers-discovery.mjs # every DISCOVERED (non-gated) bundle must be in the built manifest",
|
||
"check:csv-shape": "node internal/check-csv-export-shape.mjs # the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) — the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.",
|
||
"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 # a full-screen SCRIM must cover the VIEWPORT, not just the content pane. A position:fixed;inset:0 scrim is PANE-relative wherever an ancestor sets contain/transform — VerticalTabsLayout sets contain:layout on .vtl-content — so a scrim dimmed 1024x793 @ (256,107) at a 1280x900 viewport and left the 256px rail CLICKABLE behind a modal. A source grep cannot see it: the declaration is correct, the ANCESTOR is the variable. Needs `bun run dev`; browser gate, not in build:unlocked.",
|
||
"check:scrim-viewport:self-test": "node internal/check-scrim-covers-viewport.mjs --self-test # reverts each scrim to plain inset:0 and requires EVERY case to FAIL — a gate that cannot fail is worse than no gate, and this one measures a property that looks fine in a screenshot either way.",
|
||
"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:spa-tab-order": "node internal/check-spa-tab-order.mjs",
|
||
"check:backlog-status": "node internal/check-backlog-status-enum.mjs",
|
||
"check:mjs-parse": "node internal/check-mjs-parses.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 # LOOPBACK ONLY. This was --bind 0.0.0.0, which published the built tree on every interface of whatever machine ran a gate; measured on the build VPS it answered HTTP 200 on the box's PUBLIC IP while a browser gate was running. Every consumer (verify-browser-all, check:a11y-button-names, check:nav-reachable, smoke:share-all) already defaults to 127.0.0.1, so nothing needed the wider bind. Override with BASE_URL/BASE if you deliberately want to reach it from another host.",
|
||
"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 # the numbers the maps SHOW must match the numbers the live SOURCE has. Every other gate checks INTERNAL consistency — a page can agree with itself perfectly while its legend has stopped describing reality. Offline SKIPs (exit 0); only a reachable endpoint that disagrees fails.",
|
||
"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",
|
||
"build:bundles": "node internal/run-parallel.mjs --bundles"
|
||
},
|
||
"devDependencies": {
|
||
"@capacitor/android": "^7.6.9",
|
||
"@capacitor/cli": "^7.6.9",
|
||
"@capacitor/core": "^7.6.9",
|
||
"@tauri-apps/cli": "^2.11.5",
|
||
"@vitejs/plugin-vue": "^5.0.4",
|
||
"playwright": "1.61.1",
|
||
"terser": "^5.46.0",
|
||
"vite": "^5.4.21",
|
||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||
},
|
||
"dependencies": {
|
||
"@maplibre/maplibre-gl-geocoder": "^1.7.0",
|
||
"jspdf": "^4.2.1",
|
||
"maplibre-gl": "^5.6.0",
|
||
"pinia": "^2.3.1",
|
||
"topojson-client": "^3.1.0",
|
||
"vue": "^3.5.20",
|
||
"vue-i18n": "^9.14.5",
|
||
"world-atlas": "^2.0.2"
|
||
},
|
||
"engines": {
|
||
"node": ">=18.0.0"
|
||
}
|
||
}
|