From 12d1fee1332feee4924f8c9f3c8fd7d9db933eba Mon Sep 17 00:00:00 2001 From: swift-river542 Date: Wed, 16 Sep 2026 18:23:14 -0500 Subject: [PATCH] Public build folder is three entries: index.html, assets/, doxa-maps/ - index.html is generated by internal/generate-spa.mjs from doxa-maps/manifest.json: one self-contained page that lists every bundle, profile and instance, mounts the chosen one, and offers Open page / Copy embed; build ends with `bun run spa` (the material shell is no longer installed; nav.js, icons.js, languages.js, pane-wrapper/, external/ and _headers leave public/) - The website drop-in follows the same contract: dropin.include in the build config, sync-dropin --from-config --prune, public-hygiene --strict fails on any other root entry - Production bundles no longer carry CSS comments: a build-only Vite plugin strips them from every shadow-style block (about 140 per map bundle, 5% smaller); remaining comments reworded - Share pages and the pin renderer drop their note-style comments --- data/README.md | 2 +- docs/reference.md | 11 +- internal/generate-spa.mjs | 327 ++++ internal/generate-tree.mjs | 37 +- internal/public-hygiene-check.mjs | 20 +- internal/sync-dropin.mjs | 104 +- .../vite-plugin-strip-shadow-css-comments.mjs | 155 ++ .../components/map-controls/ShareButton.vue | 2 +- library/composables/useSelectedPin.js | 6 +- package.json | 7 +- public/_headers | 14 - .../all-people-groups/index.html | 2 +- .../cities-internet/cities-internet.js | 83 +- .../connectivity-list/index.html | 4 +- .../connectivity-map/index.html | 4 +- .../cities-internet/data-tables/index.html | 4 +- public/doxa-maps/cities-internet/index.html | 2 +- .../country-posters/countries-map/index.html | 4 +- .../country-poster-wall/index.html | 4 +- .../country-posters/country-posters.js | 545 +------ public/doxa-maps/country-posters/index.html | 2 +- public/doxa-maps/demo-map/demo-map.js | 96 +- public/doxa-maps/demo-map/demo-map/index.html | 4 +- .../demo-map/demo-map/maplibre/index.html | 4 +- public/doxa-maps/demo-map/index.html | 2 +- .../doxa-research-map/doxa-research-map.js | 583 +------ public/doxa-maps/doxa-research-map/index.html | 2 +- .../doxa-research-map/research-map/index.html | 4 +- .../doxa-simple-map/doxa-simple-map.js | 498 +----- .../doxa-simple-map/adoption/index.html | 4 +- .../doxa-simple-map/engagement/index.html | 4 +- .../doxa-simple-map/index.html | 4 +- .../doxa-simple-map/prayer/index.html | 4 +- public/doxa-maps/doxa-simple-map/index.html | 2 +- .../gospel-resources/charts/index.html | 4 +- .../gospel-resources/data-tables/index.html | 4 +- .../gospel-resources/gospel-resources.js | 51 +- public/doxa-maps/gospel-resources/index.html | 2 +- .../resources-list/index.html | 4 +- .../gospel-resources/resources-map/index.html | 4 +- public/doxa-maps/index.html | 2 +- public/doxa-maps/manifest.json | 48 +- public/doxa-maps/my-upg-100-list/index.html | 2 +- .../my-upg-100-list/my-upg-100-list.js | 346 +---- .../my-upg-100-list/poster-wall/index.html | 4 +- .../my-upg-100-list/upg-100-list/index.html | 4 +- .../my-upg-100-list/upg-100-mfe/index.html | 4 +- .../doxa-maps/research-dashboard/index.html | 2 +- .../research-dashboard/research-dashboard.js | 72 +- .../research-dashboard/index.html | 4 +- .../template-bundle/template-map-a/index.html | 2 +- .../template-bundle/template-map-b/index.html | 2 +- public/external/research-dashboard-map.js | 337 ---- public/external/upg-neighbor.js | 548 ------- public/icons.js | 62 - public/index.html | 1382 +++-------------- public/languages.js | 62 - public/nav.js | 128 -- public/pane-wrapper/pane-wrapper.js | 176 --- vite.config.js | 6 +- vite.doxa-maps-build-config.json | 9 +- 61 files changed, 1167 insertions(+), 4654 deletions(-) create mode 100644 internal/generate-spa.mjs create mode 100644 internal/vite-plugin-strip-shadow-css-comments.mjs delete mode 100644 public/_headers delete mode 100644 public/external/research-dashboard-map.js delete mode 100644 public/external/upg-neighbor.js delete mode 100644 public/icons.js delete mode 100644 public/languages.js delete mode 100644 public/nav.js delete mode 100644 public/pane-wrapper/pane-wrapper.js diff --git a/data/README.md b/data/README.md index b9a075a3a..06c958653 100644 --- a/data/README.md +++ b/data/README.md @@ -10,7 +10,7 @@ served web-root by the build (`internal/generate-tree.mjs`), exactly like the pe | `vendor/` | `/assets/vendor/` | Third-party runtime assets (e.g. table libraries) | | `posters/` | `/assets/posters/` | Rendered poster webp set (heavy pipeline output; poster walls fetch these) | | `tables/` | `/assets/tables/` | The MERGED served table set (its index.json is the merge; per-profile `app-profiles/*/data/tables` are build-time sources) | -| `external-bundles/` | `/external/` | Prebuilt map IIFEs vendored from sibling projects (not built here) | +| `external-bundles/` | not served | Prebuilt map IIFEs vendored from sibling projects (not built here); only the optional `layouts/` shell mounted them, so the build no longer copies them into `public/` | Don't edit anything under `public/` by hand — change it here (or in `app-profiles/*/data/`, `layouts/*/assets/`) and rebuild. diff --git a/docs/reference.md b/docs/reference.md index 256a92b5e..2fdf4f727 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -153,9 +153,9 @@ each bundle registers a **distinct** tag. | `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`, or the next free port — read the terminal). `/` is the **auto-generated SPA** (playground) of every element, built from `internal/playground/{discover,render,dev-plugin}.mjs`; every bundle is also served at its own html path. Dev-only routes: `/__playground/registry.json` (the registry), `/assets/*` (served from `public/assets/`, else `app-profiles/*/data/`). **Writes nothing to `public/`** — no HMR across that boundary. | | `bun run smoke:playground` | Opens every element of the auto-generated SPA by deep link in Firefox and proves it boots: element defined, shadow DOM populated, no error card, a WebGL canvas for map profiles (checked through nested shadow roots), no HTML-for-JSON data responses. `--json ` writes the evidence; exit 1 on any failure. The pre-handoff gate. | | `bun run smoke:share` | The share round-trip gate (Firefox): opens a host page (`--playground` = every canvas element of the dev SPA; `--url --tag ` = the website or any CDN page), clicks Share, reads the URL, fetches it, reopens it in a fresh page and asserts the SAME custom element mounts with a WebGL canvas. `--json `; exit 1 on any failure — proves REQ-1 ("links work wherever the tree is hosted"). | -| `bun run check:public-hygiene [] [--strict]` | Proves a served folder is fit to publish: scans for local paths, internal folder names, dev-server URLs, JWT-shaped tokens, `.env`/token files, source maps, documentation and staging/preview pages, and prints an inventory (bundles, leaf pages, assets by folder). Exit 1 on a finding (`--strict` also fails on warnings). Run it on the website's drop-in before any public push. | -| `bun run sync:dropin ` | Mirrors the built web-root `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`): adds, updates and deletes to match; **never copies credentials** (`api/maps/token.json`, `token.local.js`) **nor documentation** (`*.md`, `*.txt` — the README and docs stay private with the bundler; a stray doc in the destination is removed); `--protect a,b` keeps host-owned files; `--dry-run` first. The only sanctioned way a map changes on the site: change the bundler → `bun run build` → sync → commit in the host. | -| `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse`. | +| `bun run check:public-hygiene [] [--strict]` | Proves a served folder is fit to publish: scans for local paths, internal folder names, dev-server URLs, JWT-shaped tokens, `.env`/token files, source maps, documentation and staging/preview pages, and prints an inventory (bundles, leaf pages, assets by folder). Under `--strict` any root entry other than `index.html`, `assets/` and `doxa-maps/` is a finding ("outside the drop-in contract"). Exit 1 on a finding. Run it on the website's drop-in before any public push. | +| `bun run sync:dropin [--from-config \| --only a,b] [--prune]` / `bun run deploy:dev` | Syncs the built `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`). **The drop-in contract** lives in ONE place — `vite.doxa-maps-build-config.json` → `dropin.include`, exactly three root entries: `index.html` (the root page generated by `internal/generate-spa.mjs`, listing every bundle/profile/instance and mounting the chosen one), `assets/` (data) and `doxa-maps/` (the bundle folders + `manifest.json`). `--from-config` copies only those, `--prune` removes everything else in the destination, so a host carries exactly the served web-root and nothing else at its root. Without an allowlist it is a plain full mirror. Never copies credentials, docs or staging pages; `--protect a,b` keeps host-owned files; `--dry-run` first; prints a copied/unchanged/deleted/pruned summary and the resulting top-level tree. `deploy:dev` = `build` → sync `--from-config --prune` into the website (`DOXA_DROPIN_DEST` overrides the path) — the only sanctioned way a map changes on the site; then commit in both repos. | +| `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse` → `verify` → `spa` (writes `public/index.html` from the manifest). | | `bun run build:bundle` | Builds ONE bundle (needs `BUNDLE=`). | | `bun run build:watch` | Rebuilds into `public/` on every change (integration loop). | | `bun run clean` | Wipes + recreates the `public//` output tree. | @@ -175,6 +175,11 @@ CSS is injected by JS (`cssCodeSplit: false`), so there is no separate `.css`. T name comes from `vite.doxa-maps-build-config.json` `name` (default `doxa-maps`). `emptyOutDir: false` keeps sibling bundles intact when you rebuild one. +**Shadow-style comments are stripped from production bundles.** Component CSS is injected +from `useShadowStyles(\`…\`)` template literals that ship verbatim, so `internal/vite-plugin-strip-shadow-css-comments.mjs` +(build only, `enforce: 'pre'`) removes the `/* … */` blocks inside those literals and collapses the blank lines +they leave; a literal with `${…}` is left untouched with a warning, and dev mode keeps every comment. + **What a built bundle folder contains.** Beyond the IIFE, `bun run tree` generates an `index.html` **per bundle** (staging/showcase), **per profile**, and **per instance** — each profile/instance page is a standalone, directly linkable/iframe-able map — plus diff --git a/internal/generate-spa.mjs b/internal/generate-spa.mjs new file mode 100644 index 000000000..08bc2c359 --- /dev/null +++ b/internal/generate-spa.mjs @@ -0,0 +1,327 @@ +#!/usr/bin/env node +/** + * generate-spa.mjs — write public/index.html, the root page of the served web-root. + * + * public/ holds exactly three things: assets/ (data), / (the bundle folders + manifest.json) + * and this one page, which lists every bundle → profile → instance the manifest declares and + * mounts the chosen one. It is GENERATED — never hand-edited — as a single static file: inline + * CSS, inline vanilla JS, every path relative, so the same file works from any static host, + * under a subpath (the website serves it at /js/doxa-maps-build/index.html) and from file://. + * + * At runtime the page fetches /manifest.json so it stays correct when bundles change; a + * compact copy of the manifest is baked in as the fallback (file://, or the fetch fails). Embed + * snippets are re-derived from the manifest fields with the template generate-tree uses; this + * script checks the derivation against every manifest snippet and bakes any that differ, so the + * fallback is exact without carrying the snippets twice. + * + * The chosen map mounts exactly like its leaf page: same peer engine (Mapbox, or MapLibre for a + * provider:'maplibre' instance), same profile-config (assetsBase, dataSource, instanceId or the + * instance props), window.MAP_APP_API_URL, and the same token ladder — profile-config tk → + * ?tk= on this page's URL → the paste-once localStorage token → baked token → window.MAP_TOKEN_URL + * → the configured tokenUrl → same-origin /api/maps/token → ask once. + * + * Deep link: #//[/]. Actions: Open page (the leaf page), Copy embed + * (the manifest's embed.iframe / embed.script). + * + * Usage: node internal/generate-spa.mjs (last step of `bun run build`) + */ +import { readFileSync, writeFileSync, existsSync } from 'node:fs' +import { join, resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +// Same fields the tree generator bakes into every leaf page (vite.doxa-maps-build-config.json). +function readBuildConfig() { + let cfg = {} + try { cfg = JSON.parse(readFileSync(join(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8')) } catch { /* defaults */ } + const str = (k) => (typeof cfg[k] === 'string' ? cfg[k].trim() : '') + let tokenUrl = str('tokenUrl') + if (!tokenUrl) { + try { + const src = JSON.parse(readFileSync(join(ROOT, 'library/api/sources.json'), 'utf8')) + const v = src?._meta?.mapboxTokenUrl || src?.mapboxTokenUrl + if (typeof v === 'string' && v.trim()) tokenUrl = v.trim() + } catch { /* fall through */ } + } + return { + name: str('name') || 'doxa-maps', + title: str('displayTitle') || 'doxa-maps', + tokenUrl: tokenUrl || 'window.MAP_TOKEN_URL', + token: str('token'), + apiBaseUrl: str('apiBaseUrl').replace(/\/+$/, ''), + assetsBaseUrl: str('assetsBaseUrl').replace(/\/+$/, ''), + qaWidget: cfg.qaWidget !== false, + } +} + +// ── Shared with the page (injected by source, so Node and the browser run the same code) ── +// The embed snippets exactly as generate-tree writes them into the manifest: profile → iframe + +// script, instance → iframe only. b/p/x are the compact records below. +function snip(b, p, x) { + var u = (x || p).u, tag = (x && x.e) || p.e || b.e[0] || b.b + var r = { iframe: '' } + if (!x) r.script = ' + + +` + +const out = join(ROOT, 'public', 'index.html') +writeFileSync(out, html) +console.log(`[generate-spa] public/index.html — ${(html.length / 1024).toFixed(1)} KB · ${baked.length} bundle(s) · ${nProfiles} profile(s) · ${nInstances} instance(s) · ${nOverrides} embed snippet(s) baked verbatim (the rest re-derive)`) diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs index 86792102d..58c717d58 100644 --- a/internal/generate-tree.mjs +++ b/internal/generate-tree.mjs @@ -409,7 +409,7 @@ function mapPageHtml(opts) { ${peerBlock} - -
- -
- Doxa Maps -
-
-
- Select ViewView -
-
- -
- - -
-
- - - -
-
- - -
- -
-
- -
- -
-

Research Map

-

-
- top-left - top-right - bottom-left - bottom-right -
-
- - diff --git a/public/languages.js b/public/languages.js deleted file mode 100644 index 6d38c6d09..000000000 --- a/public/languages.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * languages.js — the language list for the app wrapper's locale dropdown. - * - * PORTED from dx-life's single source of truth: `config/languages.ts` - * (LANGUAGES / ENABLED_LANGUAGES / hideFromSwitcher). Kept DELIBERATELY MINIMAL: - * dx-life's version also carries DeepL codes, Bible translation ids, glossary ids - * and font flags — all of which belong to the CMS/translation pipeline, not to a - * map wrapper. Only the four fields the switcher actually renders are kept, plus - * `dir` (RTL) because the wrapper has to flip layout direction. - * - * WHY NOT A LIBRARY: the switcher is ~30 lines of state over this array. Adding a - * dependency to render a