Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing)

This commit is contained in:
doxa-maps-88655c70 2026-09-17 18:18:19 +00:00
parent 1a3925c6a6
commit 2d64e21fbf
136 changed files with 393 additions and 641 deletions

View file

@ -7,7 +7,7 @@ lookups in [docs/reference.md](./docs/reference.md), depth in
## The mental model — three zones
```
library/ (@map) app-profiles/<map>/ internal/
library/ (@map) plugin-designs/<map>/ internal/
shared, parameterized ──► your map: its screens ──► the build machine
legend · map · controls (.vue) + private colors, (scripts, staging)
REUSE, never customize api wiring, components NEVER touch
@ -56,6 +56,6 @@ it (script tag or the generated per-map page). Same-origin vs external-CDN modes
## One-line recap
Copy `app-profiles/template-bundle/`, change exactly four things (folder name, tag in
Copy `plugin-designs/template-bundle/`, change exactly four things (folder name, tag in
`index.js`, tag/profile strings in `index.html`, `.vue` filenames), build everything inside
your folder from `@map` blocks, `bun run build`, embed the snippet. That's the whole job.

View file

@ -110,7 +110,7 @@ New to this? Read this table first. You do **not** need to memorize it — just
## What doxa-maps gives you
- **One repository for all your maps.** Each map is an *application profile* in `app-profiles/<your-map>/`.
- **One repository for all your maps.** Each map is an *application profile* in `plugin-designs/<your-map>/`.
- **A shared library of reusable, parameterized components** (`library/` — the map, the legend, the
controls, the search bar) so your maps stop duplicating code.
- **Room for each map's own custom code** — because real maps always have something unique.
@ -197,9 +197,9 @@ JavaScript.* They were right. So here's the honest deal before you start:
- `bun run dev` — the dev server (live preview)
- `bun run build` — the build step (ship it)
- what `node_modules` are (the downloaded dependencies)
3. **You do *not* need to write the code.** Give your agent access to the example maps in `app-profiles/` plus
3. **You do *not* need to write the code.** Give your agent access to the example maps in `plugin-designs/` plus
the shared `library/`, describe your map in plain English, and it builds a new map that reuses the library
instead of duplicating spaghetti. The library is for reusable components while the app profiles is for storing custom components that are not reusable, and are specific to that app-profile or map-app. Each map app has custom components that are not reusable and they are not appropriate in the shared library, but if you look at other app-profiles you will see they store their custom components, as well as their main.vue file for the map in the app-profiles folder.
instead of duplicating spaghetti. The library is for reusable components while the app profiles is for storing custom components that are not reusable, and are specific to that app-profile or map-app. Each map app has custom components that are not reusable and they are not appropriate in the shared library, but if you look at other plugin-designs you will see they store their custom components, as well as their main.vue file for the map in the plugin-designs folder.
That's the wall. It's small, and everything past it is the maps of your dreams.
@ -216,7 +216,7 @@ bun run dev # the plugin gallery: http://localhost:5173 (or the next free
# an auto-generated SPA of every map, grouped by bundle and composition tier,
# each deep-linkable; `bun run smoke:gallery` proves every one of them boots
bun run build # the build step: every app-profiles/<map>/ becomes
bun run build # the build step: every plugin-designs/<map>/ becomes
# public/doxa-maps/<map>/<map>.js + a standalone index.html per map
bun run serve # preview the BUILT tree (the SPA shell + every embed page)

View file

@ -34,7 +34,7 @@ Use this when the color is specific to your map — you want a different shade t
shared one, or a brand-new coloring nobody else has. This keeps the shared library clean
("local by default").
1. In your bundle folder, create `app-profiles/<your-map>/src/colors/<name>.js`.
1. In your bundle folder, create `plugin-designs/<your-map>/src/colors/<name>.js`.
(No such folder yet? Just make it. `template-bundle/src/colors/example-mode.js`
is a copy-me starting point.)
2. Follow the same contract as a library strategy
@ -80,7 +80,7 @@ loader, the store) references them, so they correctly stay in the library:
`adoption`, `engagement`, `language-family`, `prayer-progress`, `religion`.
Three more are **research-only** and live in the research bundle's own folder,
`app-profiles/doxa-research-map/src/colors/`: `affinity-block`, `doxa-region`,
`plugin-designs/doxa-research-map/src/colors/`: `affinity-block`, `doxa-region`,
`resource`.
If you want *your* map to color one of these differently, don't edit the library — drop

View file

@ -9,7 +9,7 @@ being duplicated.
> **Parameterized + genuinely reused by more than one map → the shared library**
> (`library/`, alias `@map`).
> **Not reused → it lives in your app-profile folder** (`app-profiles/<your-map>/`).
> **Not reused → it lives in your app-profile folder** (`plugin-designs/<your-map>/`).
"**Local by default, promote deliberately.**" When in doubt, keep your change local to your
own map; something graduates into the shared library only once a *second* map genuinely
@ -31,6 +31,6 @@ needs it. Full reasoning — the promotion / orphan-audit rule — is in
[`COLORS.md`](./COLORS.md) is the beginner deep-dive on the **two places a color strategy
can live** — the shared `library/colors/` set vs your bundle's private
`app-profiles/<bundle>/src/colors/` — and how the filename decides override-vs-add. The
`plugin-designs/<bundle>/src/colors/` — and how the filename decides override-vs-add. The
strategy **contract** (the required `export default` shape) is in
[`../library/colors/README.md`](../library/colors/README.md).

View file

@ -79,7 +79,7 @@
- #stage/#showcase: live gem-framed map tiles, desktop/mobile viewport toggle,
lazy-mount + WebGL guard, collapsing header on scroll (mobile)
- loads ./staging-main.js
Tabs auto-detect bundles from /app-profiles/*/. -->
Tabs auto-detect bundles from /plugin-designs/*/. -->
<script type="module" src="./staging-main.js"></script>
</body>
</html>

View file

@ -2,15 +2,15 @@
**The law: data lives here; `public/` is generated.** Every folder in `data/` is copied into the
served web-root by the build (`internal/generate-tree.mjs`), exactly like the per-profile data in
`app-profiles/*/data/`. You should be able to `rm -rf public/` and get it all back with `bun run build`.
`plugin-designs/*/data/`. You should be able to `rm -rf public/` and get it all back with `bun run build`.
| Folder | Served at | What it is |
|---|---|---|
| `people-groups/` | `/assets/people-groups/` | People-group datasets (CSV/JSON) the maps read at runtime |
| `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) |
| `tables/` | `/assets/tables/` | The MERGED served table set (its index.json is the merge; per-profile `plugin-designs/*/data/tables` are build-time sources) |
| `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/`,
Don't edit anything under `public/` by hand — change it here (or in `plugin-designs/*/data/`,
`layouts/*/assets/`) and rebuild.

View file

@ -3,7 +3,7 @@
> The simple overview is in `layouts/README.md`. This is the full contract for building and wiring
> an app wrapper.
Each folder in `layouts/` is an **app wrapper**: a host shell that composes doxa-maps app-profiles
Each folder in `layouts/` is an **app wrapper**: a host shell that composes doxa-maps plugin-designs
(the independently-built custom elements) into one application.
## Terminology (deliberate — don't drift)
@ -11,7 +11,7 @@ Each folder in `layouts/` is an **app wrapper**: a host shell that composes doxa
| Thing | What it is |
|---|---|
| **app-profile** | An independently-built, self-contained custom element (a map, a dashboard, a list). **These are the micro-frontends.** |
| **app wrapper** (this folder) | The **host/shell** that mounts app-profiles, provides nav/chrome/routing, and bounds resources. Not itself a micro-frontend — it's the container. |
| **app wrapper** (this folder) | The **host/shell** that mounts plugin-designs, provides nav/chrome/routing, and bounds resources. Not itself a micro-frontend — it's the container. |
A wrapper is a *host page*, **not** a regular app-profile. Keeping it a host (rather than a bundle
nested inside a bundle) avoids the relative-path/base-URL resolution problem that nesting one

View file

@ -53,7 +53,7 @@ operator (`'match'`, `'case'`, `'interpolate'`, …). It feeds `circle-color` in
## Profile-local strategies (override / add without touching the library)
`library/colors/` is the **shared** set. A single map that wants its own coloring drops a `<name>.js`
in that bundle's `app-profiles/<bundle>/src/colors/`. At init, `index.js` calls
in that bundle's `plugin-designs/<bundle>/src/colors/`. At init, `index.js` calls
`registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true }))`, merging local files
**over** the shared set:
@ -62,7 +62,7 @@ in that bundle's `app-profiles/<bundle>/src/colors/`. At init, `index.js` calls
Each bundle is its own IIFE, so a local strategy can't leak to another map. This is the "local by
default, promote deliberately" seam. See `contributing/COLORS.md` and
`app-profiles/template-bundle/src/colors/` for the copy-me example.
`plugin-designs/template-bundle/src/colors/` for the copy-me example.
## Status / custom modes

View file

@ -167,48 +167,48 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
---
## app-profiles/ — one folder per bundle
## plugin-designs/ — one folder per bundle
### demo-map/
| File | What it is | Agents should know |
|---|---|---|
| `app-profiles/demo-map/index.js` | Bundle entry: defines/registers the `<demo-map>` custom element wrapping @map/ProfileLoader. | import.meta.glob calls must live HERE (build-time, relative): `./*.vue` → 'profileModules', `./src/colors/*.js` → registerStrategies. i18n provided as 'appI18n' (custom-element root can't useI18n()). |
| `app-profiles/demo-map/index.html` | Dev/staging page: loads Mapbox GL peer, mounts `<demo-map>` with a profile-config attribute, pulls ./index.js as Vite entry. | %VITE_MAPBOX_TOKEN% replaced by Vite from gitignored .env; sets window.DOXA_TOKEN + MAP_APP_BASE_URL; Mapbox GL must load before the bundle script. |
| `app-profiles/demo-map/demo-map.vue` | Demo profile: two-tab map (religion coloring vs all-purple pins) composed from @map composables + a local single-color strategy. | Relies on ProfileLoader injections; tab colorStrategy IDs must exist in the registry; layer name 'language-family-pins' is hardcoded for tab repaints; legend duck-types the strategy shape. |
| `app-profiles/demo-map/src/colors/colors.json` | Static hex palette (pins, polygons, fallback) — code-free edit surface. | Edit hex strings only; per-value logic goes in a strategy .js in the same folder. |
| `app-profiles/demo-map/src/colors/single-color.js` | Local strategy painting every pin one constant purple. | Auto-registered via the src/colors glob — filename becomes the mode key (single-color → singleColor); keep the buildColorExpression alias and default `name`; applyColor must return a Mapbox expression array. |
| `plugin-designs/demo-map/index.js` | Bundle entry: defines/registers the `<demo-map>` custom element wrapping @map/ProfileLoader. | import.meta.glob calls must live HERE (build-time, relative): `./*.vue` → 'profileModules', `./src/colors/*.js` → registerStrategies. i18n provided as 'appI18n' (custom-element root can't useI18n()). |
| `plugin-designs/demo-map/index.html` | Dev/staging page: loads Mapbox GL peer, mounts `<demo-map>` with a profile-config attribute, pulls ./index.js as Vite entry. | %VITE_MAPBOX_TOKEN% replaced by Vite from gitignored .env; sets window.DOXA_TOKEN + MAP_APP_BASE_URL; Mapbox GL must load before the bundle script. |
| `plugin-designs/demo-map/demo-map.vue` | Demo profile: two-tab map (religion coloring vs all-purple pins) composed from @map composables + a local single-color strategy. | Relies on ProfileLoader injections; tab colorStrategy IDs must exist in the registry; layer name 'language-family-pins' is hardcoded for tab repaints; legend duck-types the strategy shape. |
| `plugin-designs/demo-map/src/colors/colors.json` | Static hex palette (pins, polygons, fallback) — code-free edit surface. | Edit hex strings only; per-value logic goes in a strategy .js in the same folder. |
| `plugin-designs/demo-map/src/colors/single-color.js` | Local strategy painting every pin one constant purple. | Auto-registered via the src/colors glob — filename becomes the mode key (single-color → singleColor); keep the buildColorExpression alias and default `name`; applyColor must return a Mapbox expression array. |
### doxa-research-map/
| File | What it is | Agents should know |
|---|---|---|
| `app-profiles/doxa-research-map/index.js` | Bundle entry: globs profiles + colors, registers strategies, sets up the RTL plugin, defines `<doxa-research-map>`. | Globs are relative to THIS file — profile .vue at bundle root, strategies in src/colors/, or they silently don't register. Deliberately does NOT register legacy `<doxa-map>` (owned by doxa-simple-map; one-shot customElements.define). |
| `app-profiles/doxa-research-map/index.html` | Staging showcase: gem-framed instances from a flat DOXA_SLOTS array, profile tabs, overview grid. | Slot `profile` must match a .vue filename at bundle root; profile + tk (+ ?locale=) auto-injected into config; ./index.js script tag rewritten to the IIFE at build. |
| `app-profiles/doxa-research-map/research-map.vue` | The research-map profile (~2100 lines): 7-tab map (prayer/adoption/engagement/people-groups/regions/languages/religions) with SemanticTreeLegend, geocoder, filters, detail panel, lazy poster/packet export. | Tabs are hardcoded in ALL_TABS/FEATURES (NOT profile-config); config arrives only via injections; maxZoom pinned to 22 deliberately; heavy deps (poster, packet, clustering, detail panel, mobile legend) are lazy-loaded — keep them off the boot path. |
| `app-profiles/doxa-research-map/src/colors/affinity-block.js` | Strategy mapping ROP1 affinity-block codes (A001–A017) to names and pin colors. | ROP1 code is the primary key (name lookups are lossy legacy); reads properties.affinityBlock falling back to `_raw.ROP1`; keep NAMES and PALETTE keys in sync; A016 intentionally absent. |
| `app-profiles/doxa-research-map/src/colors/doxa-region.js` | Strategy coloring pins by DOXA/WAGF region; SLUG_PALETTE is authoritative. | Color by slug, never localized display labels (break in non-EN locales); uses @map/utils/geoUtils generateColorFromString as fallback. |
| `app-profiles/doxa-research-map/src/colors/resource.js` | Binary green/black coloring by gospel-resource availability + per-resource palette for the legend. | Default coloring is intentionally binary despite the per-resource PALETTE; resource values must literally equal 'Available' (properties or _raw CSV columns via getCSVColumn); legacy aliases kept for compat. |
| `plugin-designs/doxa-research-map/index.js` | Bundle entry: globs profiles + colors, registers strategies, sets up the RTL plugin, defines `<doxa-research-map>`. | Globs are relative to THIS file — profile .vue at bundle root, strategies in src/colors/, or they silently don't register. Deliberately does NOT register legacy `<doxa-map>` (owned by doxa-simple-map; one-shot customElements.define). |
| `plugin-designs/doxa-research-map/index.html` | Staging showcase: gem-framed instances from a flat DOXA_SLOTS array, profile tabs, overview grid. | Slot `profile` must match a .vue filename at bundle root; profile + tk (+ ?locale=) auto-injected into config; ./index.js script tag rewritten to the IIFE at build. |
| `plugin-designs/doxa-research-map/research-map.vue` | The research-map profile (~2100 lines): 7-tab map (prayer/adoption/engagement/people-groups/regions/languages/religions) with SemanticTreeLegend, geocoder, filters, detail panel, lazy poster/packet export. | Tabs are hardcoded in ALL_TABS/FEATURES (NOT profile-config); config arrives only via injections; maxZoom pinned to 22 deliberately; heavy deps (poster, packet, clustering, detail panel, mobile legend) are lazy-loaded — keep them off the boot path. |
| `plugin-designs/doxa-research-map/src/colors/affinity-block.js` | Strategy mapping ROP1 affinity-block codes (A001–A017) to names and pin colors. | ROP1 code is the primary key (name lookups are lossy legacy); reads properties.affinityBlock falling back to `_raw.ROP1`; keep NAMES and PALETTE keys in sync; A016 intentionally absent. |
| `plugin-designs/doxa-research-map/src/colors/doxa-region.js` | Strategy coloring pins by DOXA/WAGF region; SLUG_PALETTE is authoritative. | Color by slug, never localized display labels (break in non-EN locales); uses @map/utils/geoUtils generateColorFromString as fallback. |
| `plugin-designs/doxa-research-map/src/colors/resource.js` | Binary green/black coloring by gospel-resource availability + per-resource palette for the legend. | Default coloring is intentionally binary despite the per-resource PALETTE; resource values must literally equal 'Available' (properties or _raw CSV columns via getCSVColumn); legacy aliases kept for compat. |
### doxa-simple-map/
| File | What it is | Agents should know |
|---|---|---|
| `app-profiles/doxa-simple-map/index.js` | Bundle entry registering `<doxa-map>` (legacy) AND `<doxa-simple-map>` mounting ProfileLoader with per-instance Pinia/i18n. | Q3 contract: the bundle runs import.meta.glob('./*.vue') itself and provides 'profileModules' (ProfileLoader can't glob across @map). defineCustomElement called twice on purpose (one class per tag); RTL plugin registered once. |
| `app-profiles/doxa-simple-map/index.html` | Staging showcase: gem-framed `<doxa-map>` slots (Engagement/Prayer/Adoption) with profile tabs and overview grid. | Edit only DOXA_SLOTS; `profile` must match a bundle-root .vue; ?locale= forwarded into every config; build rewrites the module script to ../../app/doxa-simple-map.iife.js. |
| `app-profiles/doxa-simple-map/doxa-simple-map.vue` | Tabbed (prayer/engagement/adoption) single-map profile wiring library composables, legend, geocoder, and strategies into one Shadow-DOM-safe embed. | Behavior gated by in-file FEATURES flags (geocoder/hamburger/overlapDensity/activeOverlayEmphasis OFF here); tabs come from injected profileConfig; SHARE_EMBED_BY_ID hardcodes instance-page URLs — renaming instances in simple-map.instances.json breaks share links. |
| `app-profiles/doxa-simple-map/simple-map.instances.json` | Instance manifest: build emits N preset standalone embed pages (prayer/engagement/adoption) from the one parameterized profile. | Build-consumed data, not runtime code: each instance becomes `doxa-simple-map/<id>/index.html`; instances SPLIT one profile into many render targets; colorStrategy/legend/popup values must match registered strategy names. |
| `plugin-designs/doxa-simple-map/index.js` | Bundle entry registering `<doxa-map>` (legacy) AND `<doxa-simple-map>` mounting ProfileLoader with per-instance Pinia/i18n. | Q3 contract: the bundle runs import.meta.glob('./*.vue') itself and provides 'profileModules' (ProfileLoader can't glob across @map). defineCustomElement called twice on purpose (one class per tag); RTL plugin registered once. |
| `plugin-designs/doxa-simple-map/index.html` | Staging showcase: gem-framed `<doxa-map>` slots (Engagement/Prayer/Adoption) with profile tabs and overview grid. | Edit only DOXA_SLOTS; `profile` must match a bundle-root .vue; ?locale= forwarded into every config; build rewrites the module script to ../../app/doxa-simple-map.iife.js. |
| `plugin-designs/doxa-simple-map/doxa-simple-map.vue` | Tabbed (prayer/engagement/adoption) single-map profile wiring library composables, legend, geocoder, and strategies into one Shadow-DOM-safe embed. | Behavior gated by in-file FEATURES flags (geocoder/hamburger/overlapDensity/activeOverlayEmphasis OFF here); tabs come from injected profileConfig; SHARE_EMBED_BY_ID hardcodes instance-page URLs — renaming instances in simple-map.instances.json breaks share links. |
| `plugin-designs/doxa-simple-map/simple-map.instances.json` | Instance manifest: build emits N preset standalone embed pages (prayer/engagement/adoption) from the one parameterized profile. | Build-consumed data, not runtime code: each instance becomes `doxa-simple-map/<id>/index.html`; instances SPLIT one profile into many render targets; colorStrategy/legend/popup values must match registered strategy names. |
### template-bundle/ (copy-me starter)
| File | What it is | Agents should know |
|---|---|---|
| `app-profiles/template-bundle/index.js` | Copy-me entry: globs flat .vue profiles, registers private strategies, defines `<template-bundle>`. | Globs are position-bound: `./*.vue` flat (filename = profile name) and `./src/colors/*.js` (colors.json ignored by the glob); tag name = bundle folder name. |
| `app-profiles/template-bundle/index.html` | Staging page rendering both template profiles side-by-side. | The profile-config `"profile"` field selects which bundle-root .vue renders; copying this bundle means updating tag names and profile strings. |
| `app-profiles/template-bundle/template-map-a.vue` | Minimal example profile "A" — colored placeholder, no Mapbox/data, demonstrates profile-config injection + sandbox color JSON. | Filename minus .vue IS the profile name; must sit at bundle root; sandbox imports are relative (./src/...) — @map alias is library-only. |
| `app-profiles/template-bundle/template-map-b.vue` | Minimal example profile "B" — shows multi-profile bundles; both profiles compile into one bundle JS. | Host selects it via profile-config `{"profile":"template-map-b"}`. |
| `app-profiles/template-bundle/src/colors/colors.json` | Plain-values hex palette a non-programmer edits. | Values-only; logic goes in a strategy .js beside it; imported relatively from the bundle-root .vue. |
| `app-profiles/template-bundle/src/colors/example-mode.js` | Copy-me template for a bundle-private strategy (two-bucket example on peoplePraying). | Filename IS the mode key (kebab → camelCase); matching a library mode overrides it for this bundle only; `name` in the default export is required or the tab/legend label is blank. |
| `plugin-designs/template-bundle/index.js` | Copy-me entry: globs flat .vue profiles, registers private strategies, defines `<template-bundle>`. | Globs are position-bound: `./*.vue` flat (filename = profile name) and `./src/colors/*.js` (colors.json ignored by the glob); tag name = bundle folder name. |
| `plugin-designs/template-bundle/index.html` | Staging page rendering both template profiles side-by-side. | The profile-config `"profile"` field selects which bundle-root .vue renders; copying this bundle means updating tag names and profile strings. |
| `plugin-designs/template-bundle/template-map-a.vue` | Minimal example profile "A" — colored placeholder, no Mapbox/data, demonstrates profile-config injection + sandbox color JSON. | Filename minus .vue IS the profile name; must sit at bundle root; sandbox imports are relative (./src/...) — @map alias is library-only. |
| `plugin-designs/template-bundle/template-map-b.vue` | Minimal example profile "B" — shows multi-profile bundles; both profiles compile into one bundle JS. | Host selects it via profile-config `{"profile":"template-map-b"}`. |
| `plugin-designs/template-bundle/src/colors/colors.json` | Plain-values hex palette a non-programmer edits. | Values-only; logic goes in a strategy .js beside it; imported relatively from the bundle-root .vue. |
| `plugin-designs/template-bundle/src/colors/example-mode.js` | Copy-me template for a bundle-private strategy (two-bucket example on peoplePraying). | Filename IS the mode key (kebab → camelCase); matching a library mode overrides it for this bundle only; `name` in the default export is required or the tab/legend label is blank. |
---
@ -217,7 +217,7 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
| File | What it is | Agents should know |
|---|---|---|
| `internal/clean.mjs` | Pre-build script that deletes/recreates the output tree `public/<name>/`. | Reads the tree name from vite.doxa-maps-build-config.json (fallback "doxa-maps"); must run BEFORE per-bundle vite builds. |
| `internal/generate-tree.mjs` | Post-vite step that scans app-profiles/ and writes the self-contained doxa-maps/ output tree (manifest.json, staging pages, standalone profile/instance pages) and copies to configured destinations. | Runs AFTER per-bundle builds (expects the IIFE at `public/<name>/<bundle>/<bundle>.js`); discovery is convention-driven (bundle = non-underscore dir with .vue at ROOT — profiles moved into src/ silently vanish); all URLs stay relative unless embedBaseUrl is set; inlines staging-forge.{css,js}. |
| `internal/generate-tree.mjs` | Post-vite step that scans plugin-designs/ and writes the self-contained doxa-maps/ output tree (manifest.json, staging pages, standalone profile/instance pages) and copies to configured destinations. | Runs AFTER per-bundle builds (expects the IIFE at `public/<name>/<bundle>/<bundle>.js`); discovery is convention-driven (bundle = non-underscore dir with .vue at ROOT — profiles moved into src/ silently vanish); all URLs stay relative unless embedBaseUrl is set; inlines staging-forge.{css,js}. |
| `internal/generate-library-index.mjs` | Scans live library/ code and regenerates docs/reference-library-index.md (one-line import catalog). | Output is DERIVED — never hand-edit the index; rerun after any library/ change; descriptions come from each file's first header-comment line. |
| `internal/drop-portability-check.mjs` | CLI gate (`check:drop`): scans built .html/.json for root-relative refs and fails the build if any would break subpath/Drop hosting. | Defaults treeDir to `public/<name>` from the build config — build first; only checks .html/.json and a fixed manifest key list (iframeUrl\|scriptUrl\|url\|embedUrl\|output) — new URL keys need adding here. |
| `internal/inline-parse-check.mjs` | Post-build verifier (`check:parse`): syntax-checks every inline classic `<script>` in the built HTML via `node --check`. | Only matches bare `<script>` (no attributes), checked as classic scripts — import.meta or a literal `</script>` in snippets is exactly the bug class it catches. |
@ -227,7 +227,7 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
| `internal/staging-reference-v2.html` | Hand-authored reference mockup of the staging-root tree page (nested details, lazy iframes) that generate-tree.mjs's template replicates. | Not served or built — spec-by-example; rules encoded: inline manifest (file:// safe), lazy iframe src on toggle, tk never baked, normal-flow header. |
| `library/scripts/research/` | Table + resource builders shared by cities-internet, gospel-resources and key-cities. In `library/` because more than one profile consumes the output. |
| `library/scripts/posters/` | Poster and preview generator shared by country-posters and my-upg-100-list. Its `out/` tree is render output and is gitignored; what ships is the exported subset in `public/assets/`. |
| `app-profiles/gospel-resources/scripts/` | Link and click-sheet verifiers. Single-profile, so they live with the profile rather than in the library. |
| `plugin-designs/gospel-resources/scripts/` | Link and click-sheet verifiers. Single-profile, so they live with the profile rather than in the library. |
| `internal/write-token.mjs` | Writes `public/api/maps/token.json` from the `MAPBOX_TOKEN` environment variable. Refuses an `sk.*`; writes nothing when unset. |
---
@ -236,9 +236,9 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
| File | What it is | Agents should know |
|---|---|---|
| `vite.config.js` | Vite config building one IIFE bundle per invocation (BUNDLE env var) from auto-discovered app-profiles/; serves all bundles in dev. | Build requires `BUNDLE=<name>` (`npm run build` loops all); bundle discovery needs BOTH index.html AND index.js at profile root (`_`/`.` dirs skipped); `@map` alias → library/; jspdf externalized to an esm.sh CDN URL in builds; output name from vite.doxa-maps-build-config.json. |
| `vite.config.js` | Vite config building one IIFE bundle per invocation (BUNDLE env var) from auto-discovered plugin-designs/; serves all bundles in dev. | Build requires `BUNDLE=<name>` (`npm run build` loops all); bundle discovery needs BOTH index.html AND index.js at profile root (`_`/`.` dirs skipped); `@map` alias → library/; jspdf externalized to an esm.sh CDN URL in builds; output name from vite.doxa-maps-build-config.json. |
| `vite.doxa-maps-build-config.json` | The single committed build-config: output tree name ("doxa-maps"), extra copy destinations (paths), embedBaseUrl, Mapbox tokenUrl/token. | Edit this ONE file then `bun run build`; empty embedBaseUrl keeps manifest URLs tree-relative (drop-portability gate); token ladder: per-map prop > ?tk= > baked token > window.MAP_TOKEN_URL > tokenUrl > same-origin /api/maps/token. tokenUrl is a URL, not a secret. |
| `package.json` | Root npm/bun manifest: scripts (dev, build, build:bundle, clean, tree, check:drop, check:parse) + deps (vue 3, pinia, vue-i18n, topojson-client, world-atlas, jspdf; vite 5 toolchain). | `build` iterates app-profiles/*/ setting BUNDLE per invocation — adding a folder auto-builds; internal/ scripts run as part of build (check:drop currently invoked twice, likely unintentional). Bun-first toolchain. |
| `package.json` | Root npm/bun manifest: scripts (dev, build, build:bundle, clean, tree, check:drop, check:parse) + deps (vue 3, pinia, vue-i18n, topojson-client, world-atlas, jspdf; vite 5 toolchain). | `build` iterates plugin-designs/*/ setting BUNDLE per invocation — adding a folder auto-builds; internal/ scripts run as part of build (check:drop currently invoked twice, likely unintentional). Bun-first toolchain. |
| `index.html` | Root staging page ("Embeddable JAMstack Maps") listing every bundle as tabs and lazy-mounting each bundle's maps in live frames; logic in /internal/staging.js. | Loads mapbox-gl v3.24.0 + geocoder globally ONCE so custom elements share window.mapboxgl in the same realm; pinned CDN versions must match library expectations. |

View file

@ -61,7 +61,7 @@ i18n/
## How it integrates with the component library
The app-profile entry (`app-profiles/<name>/index.js`) does `app.use(createAppI18n())` alongside
The app-profile entry (`plugin-designs/<name>/index.js`) does `app.use(createAppI18n())` alongside
`createPinia()`, so every component reaches strings through the standard `$t('area.key')` /
`useI18n()`. Initial locale comes from `detectLocale()` (the host page's `<html lang>`); the
fallback chain is `<detected> → en`. Because area files are namespaced by filename, a component

View file

@ -29,7 +29,7 @@ A single bundle can present **several different maps**. There are **two ways** t
do it, and they solve different problems. Neither is "more correct" — pick the one
that matches *why* your maps differ.
> **Vocabulary.** A **bundle** is one folder under `app-profiles/<name>/` → one
> **Vocabulary.** A **bundle** is one folder under `plugin-designs/<name>/` → one
> `<name>.js` script. A **profile** is a `.vue` file at that folder's root,
> discovered by `import.meta.glob('./*.vue')` in the bundle's `index.js`. The host
> picks which profile to mount with the `profile` field of `profile-config`; the

View file

@ -28,7 +28,7 @@ The full pipeline, in order:
posters -> posters:shoot -> (convert to WebP previews) -> bun run build
```
The WebP step feeds `app-profiles/my-upg-100-list/assets/previews/`, which is what the poster wall
The WebP step feeds `plugin-designs/my-upg-100-list/assets/previews/`, which is what the poster wall
shows in the app. **A template change is not visible in the app until previews are regenerated and
the app is rebuilt.**

View file

@ -1,6 +1,6 @@
# What doxa-maps is
A portable bundler for embeddable maps. One shared `library/`, many `app-profiles/<map>/`
A portable bundler for embeddable maps. One shared `library/`, many `plugin-designs/<map>/`
folders; one build turns each map into a self-contained web component.
**The one idea worth knowing:** each map builds to a standard web component, so it drops into

View file

@ -5,7 +5,7 @@ first, then use this page to look up a task. For contracts and field tables, see
**[reference.md](./reference.md)**.
The three zones govern everything: **reuse `library/` (`@map`), build in
`app-profiles/<your-map>/`, never touch `internal/`.**
`plugin-designs/<your-map>/`, never touch `internal/`.**
**Pick your task:**
[embed on a site](#embed-a-map-on-a-partner-site) · [link/iframe directly](#link-or-iframe-a-map-directly-zero-effort-embed) ·
@ -68,14 +68,14 @@ copy-paste ready. Design details: [architecture/embeddability.md](./architecture
## Add a new map profile
The fastest path is **copy `template-bundle` and rename it.** A bundle is one folder under
`app-profiles/`; its name becomes the bundle name and the output filename. Don't prefix it
`plugin-designs/`; its name becomes the bundle name and the output filename. Don't prefix it
with `_` or `.` — those are skipped by the build.
1. **Copy the template.**
```bash
cp -r app-profiles/template-bundle app-profiles/my-new-map
cp -r plugin-designs/template-bundle plugin-designs/my-new-map
```
2. **Rename the element tag.** In `app-profiles/my-new-map/index.js`, change the
2. **Rename the element tag.** In `plugin-designs/my-new-map/index.js`, change the
`customElements.define('…', …)` tag to your map's tag. The entry must glob the bundle's
flat profiles and hand them to the loader — it already does this; keep the shape:
```js
@ -186,7 +186,7 @@ auto-discovers every `<name>.js` in that folder, so there's no registration step
deliberate — this changes *every* map using that mode.
**Path B — a color private to your map.** Create
`app-profiles/<your-map>/src/colors/<name>.js` (copy `template-bundle/src/colors/example-mode.js`
`plugin-designs/<your-map>/src/colors/<name>.js` (copy `template-bundle/src/colors/example-mode.js`
as a starter). Your bundle's `index.js` runs `registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true }))`
at startup, merging your local files **over** the shared set. The **filename** derives the
mode key (`language-family.js` → `languageFamily`):
@ -328,10 +328,10 @@ URL restrictions at Mapbox, not by hiding it.
If you point an AI agent at this repo, paste this first, before it reads any code:
> This is the doxa-maps bundler. Three zones: `library/` (`@map`) = reusable shared blocks —
> **reuse via the `@map/…` alias, never edit**. `app-profiles/<map>/` = the sandbox for **one**
> **reuse via the `@map/…` alias, never edit**. `plugin-designs/<map>/` = the sandbox for **one**
> map — **build everything here**. `internal/` = the build machine — **never touch it**.
> To build a map: copy `app-profiles/template-bundle/`, read its README, pick reusable pieces
> from `docs/reference.md`, then build the map's own code inside `app-profiles/<the-map>/`.
> To build a map: copy `plugin-designs/template-bundle/`, read its README, pick reusable pieces
> from `docs/reference.md`, then build the map's own code inside `plugin-designs/<the-map>/`.
> Change colors by editing **values** in that map's `src/colors/` folder — not logic in
> `library/`. Then run `bun run build`. If a change seems to belong in `library/`, stop and
> ask — that's an expert move that can break every other map.

View file

@ -15,18 +15,18 @@ bundle is a single self-contained `.js` file that registers a custom HTML elemen
| Zone | Path | Alias | License | You edit it… |
|---|---|---|---|---|
| **Shared library** | `library/` | `@map` | Apache-2.0 | …once; the fix reaches every bundle on next build |
| **Your app-profile** | `app-profiles/<name>/` | — | yours | …freely; nothing here leaks to other maps |
| **Your app-profile** | `plugin-designs/<name>/` | — | yours | …freely; nothing here leaks to other maps |
| **The build machine** | `internal/` | — | Apache-2.0 | …never — it's the bundler's own scripts |
**Terminology.** "Profile" is used at two levels: a **bundle / app-profile** is a folder under
`app-profiles/`; a **profile screen** is one `.vue` file sitting **flat** in that folder (no
`plugin-designs/`; a **profile screen** is one `.vue` file sitting **flat** in that folder (no
`profiles/` subfolder — the entry globs `./*.vue`). A bundle can hold several screens; the host
picks which to render with the `profile` field of `profile-config`.
**Two words first.** An **element** is a custom HTML tag this repo invents, such as `<doxa-map>`: the browser
learns it once, then you can paste it into any page as often as you like; each pasted tag is one live copy, an
*instance* (people also say "web component"). An **app profile** (this repo also says *mounting file*) is one
flat `.vue` in `app-profiles/<bundle>/` that defines a whole map app — its main component; the settings on the
flat `.vue` in `plugin-designs/<bundle>/` that defines a whole map app — its main component; the settings on the
tag (`profile-config`) name which app profile to load and the shared `ProfileLoader` picks it. In class-and-object
terms: the element class is the class, each tag is an instance, the app profile is the screen that instance loads.
Most repos need one such file per app; doxa-maps builds hundreds of maps from one shared `library/`, so every map
@ -70,7 +70,7 @@ doxa-maps/
│ ├── stores/ ← Pinia: dataStore, mapStore, uiStore (per-instance)
│ └── utils/ ← pure helpers (geoUtils, clustering)
│
├── app-profiles/ ← ALL bundles live here (each subfolder = 1 bundle)
├── plugin-designs/ ← ALL bundles live here (each subfolder = 1 bundle)
│ ├── <bundle>/
│ │ ├── index.js ← bundle ENTRY — globs ./*.vue, registers the web component
│ │ ├── index.html ← Vite html entry = the dev STAGING page
@ -86,7 +86,7 @@ doxa-maps/
│ ├── index.html the app shell
│ ├── doxa-maps/<bundle>/ built bundles + manifest.json + staging HTML
│ └── assets/ posters & data the maps fetch at runtime
├── vite.config.js ← multi-entry: discovers app-profiles/<name>/ folders
├── vite.config.js ← multi-entry: discovers plugin-designs/<name>/ folders
└── vite.doxa-maps-build-config.json ← output tree name + copy destinations + token settings
```
@ -151,12 +151,12 @@ each bundle registers a **distinct** tag.
| Script | What it does |
|---|---|
| `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`, or the next free port — read the terminal). `/` is the **plugin gallery** of every element — `internal/generate-gallery.mjs` in mode `dev` over `internal/plugin-gallery/{discover,dev-plugin}.mjs`; every bundle is also served at its own html path. Dev-only routes: `/__plugin-gallery/manifest.json` (the manifest-shaped model the page fetches), `/__plugin-gallery/registry.json` (the discovery registry the smoke tools read), `/assets/*` (served from `public/assets/`, else `app-profiles/*/data/`). **Writes nothing to `public/`** — no HMR across that boundary. |
| `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`, or the next free port — read the terminal). `/` is the **plugin gallery** of every element — `internal/generate-gallery.mjs` in mode `dev` over `internal/plugin-gallery/{discover,dev-plugin}.mjs`; every bundle is also served at its own html path. Dev-only routes: `/__plugin-gallery/manifest.json` (the manifest-shaped model the page fetches), `/__plugin-gallery/registry.json` (the discovery registry the smoke tools read), `/assets/*` (served from `public/assets/`, else `plugin-designs/*/data/`). **Writes nothing to `public/`** — no HMR across that boundary. |
| `bun run smoke:gallery` | Opens every element of the plugin gallery 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 <file>` 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 (`--plugin-gallery` = every canvas element of the dev plugin gallery; `--url <page> --tag <element>` = 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 <file>`; exit 1 on any failure — proves REQ-1 ("links work wherever the tree is hosted"). |
| `bun run check:public-hygiene [<dir>] [--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 <dest> [--from-config \| --only a,b] [--prune]` / `bun run deploy:dev` | Syncs the built `public/` into a host checkout (e.g. the website's `public/embed/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 built plugin gallery — the root page generated by `internal/generate-gallery.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. `dropin.exclude` (same file) lists built files inside the allowlist that no page fetches — the full people-groups CSV, the tables only internal scripts read, the table index; they stay in `public/` for the scripts, are skipped by `--from-config` (counted as "excluded (not shipped)") and removed from the destination if a copy is there. 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/excluded 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` → `gallery` (writes `public/index.html`, the built plugin gallery, from the manifest). |
| `bun run build` | `clean` → one `vite build` per `plugin-designs/*/` folder → `tree` → `check:drop` → `check:parse` → `verify` → `gallery` (writes `public/index.html`, the built plugin gallery, from the manifest). |
| `bun run build:bundle` | Builds ONE bundle (needs `BUNDLE=<name>`). |
| `bun run build:watch` | Rebuilds into `public/` on every change (integration loop). |
| `bun run clean` | Wipes + recreates the `public/<name>/` output tree. |
@ -172,13 +172,13 @@ the phone layout and the baked fallback. Mode `build` (`bun run gallery`) render
`public/doxa-maps/manifest.json` into `public/index.html` — the root page of the served web-root and,
through the drop-in, the website's `/doxa-maps` page. Mode `dev` is what `bun run dev` serves at `/`: the
same page over `galleryModel(discoverSpaRegistry())` (`internal/plugin-gallery/dev-plugin.mjs`), differing
only in the loader (it imports `/app-profiles/<bundle>/index.js` instead of adding the built IIFE), the live
only in the loader (it imports `/plugin-designs/<bundle>/index.js` instead of adding the built IIFE), the live
model route (`/__plugin-gallery/manifest.json`), the `/assets/*` dev route and the composability ladder's
"?" popovers in the header — kept out of the built page to keep it lean. Nothing about the gallery is
written twice.
**How a build works.** `vite.config.js` is multi-entry by discovery: at build time it
registers every `app-profiles/` subfolder that (a) doesn't start with `_`/`.` and (b) has
registers every `plugin-designs/` subfolder that (a) doesn't start with `_`/`.` and (b) has
**both** `index.html` and `index.js`. One `vite build` builds **exactly one** bundle, selected
by `BUNDLE=<name>`, so the output is a self-contained **IIFE** (`inlineDynamicImports: true`).
`bun run build` loops every folder. Each bundle only bundles what its screens import, so sizes
@ -304,13 +304,13 @@ The **Semantic Tree Legend** component (a reusable hierarchical legend) is docum
| I want to… | Do this |
|---|---|
| Add a new map | copy `app-profiles/template-bundle/` → rename folder + tag in `index.js` |
| Add a new map | copy `plugin-designs/template-bundle/` → rename folder + tag in `index.js` |
| Add a screen to an existing bundle | drop a `.vue` flat in that bundle's folder, request it via `profile-config.profile` |
| Fix a legend / control for ALL maps | edit it once in `library/` (`@map`); rebuild bundles |
| Add a color strategy | new file in `@map/colors/` (shared) or the bundle's `src/colors/` (map-private) |
| Build everything | `bun run build` → `public/doxa-maps/<bundle>/<bundle>.js` |
| Build just one | `BUNDLE=<name> bun run build:bundle` |
| Preview in isolation | `bun run dev` → `http://localhost:5173/app-profiles/<name>/index.html` |
| Preview in isolation | `bun run dev` → `http://localhost:5173/plugin-designs/<name>/index.html` |
| See a change on the served site | `bun run build`, then refresh (NOT `dev`) |
| Repoint the API without a rebuild | set `window.MAP_APP_API_URL` before the bundle loads |
| Repoint the Mapbox token | pass `tk` in `profile-config`, or set `window.MAP_TOKEN_URL` / build-config `tokenUrl` |

View file

@ -72,7 +72,7 @@ and the ocean is already sand. No reload, no rebuild. That's HMR, and that's the
### Why staging is genuinely magic
- **It finds every map by itself.** Staging auto-detects your maps by looking in the
`app-profiles/` folder. Add a folder or a `.vue` file and it appears. There is no roster to
`plugin-designs/` folder. Add a folder or a `.vue` file and it appears. There is no roster to
maintain, so you can never "forget to add a map to the index" — the folder *is* the list.
- **Your changes appear instantly (HMR).** Save, and the page updates in place. The feeling is
less like programming and more like sculpting: nudge, watch, nudge again.
@ -89,10 +89,10 @@ safe from every other map.
| Zone | What it is | Can you edit it? |
|---|---|---|
| **`library/`** (`@map`) | Reusable, shared building blocks — map, legends, toolbars, colors, data. You **reuse** these via the `@map/…` import alias. | **No — reuse only.** A change here can affect every map. |
| **`app-profiles/<your-map>/`** | Your **sandbox**. Your one map lives here: its screens, colors, API wiring. | **Yes — build anything.** Nothing here leaks to another map. |
| **`plugin-designs/<your-map>/`** | Your **sandbox**. Your one map lives here: its screens, colors, API wiring. | **Yes — build anything.** Nothing here leaks to another map. |
| **`internal/`** | The bundler's own build machinery. | **No — never.** It's the machine that builds the bundles. |
One line: **reuse `library/`, build in `app-profiles/<your-map>/`, never touch `internal/`.**
One line: **reuse `library/`, build in `plugin-designs/<your-map>/`, never touch `internal/`.**
---
@ -101,14 +101,14 @@ One line: **reuse `library/`, build in `app-profiles/<your-map>/`, never touch `
A new map is a **copy of a great starter, renamed** — you never start from a blank page.
> **One map = one file.** A `.vue` file living **flat** in its own folder under
> `app-profiles/`. The folder name is your map's name.
> `plugin-designs/`. The folder name is your map's name.
Inside `app-profiles/` there's a folder called **`template-bundle/`** — a complete, working
Inside `plugin-designs/` there's a folder called **`template-bundle/`** — a complete, working
example built to be copied. You don't study it; you copy it.
**The plain-English way (what you'll actually do)** — tell your agent:
> *"Copy `app-profiles/template-bundle/` to a new folder called `app-profiles/harvest-map/`,
> *"Copy `plugin-designs/template-bundle/` to a new folder called `plugin-designs/harvest-map/`,
> rename the inside files, then make it a map of Southeast Asia shaded by population, with a
> blue-to-orange legend on the right and labels in English and Thai."*
@ -118,10 +118,10 @@ change. Conversation in, map out.
**The by-hand way (if you want to see what happened):**
```bash
cp -r app-profiles/template-bundle app-profiles/harvest-map # 1. copy the starter
cp -r plugin-designs/template-bundle plugin-designs/harvest-map # 1. copy the starter
# 2. change exactly four things: the folder name, the tag in index.js, the tag/profile
# strings in index.html, and the .vue filenames (delete the second one if you want one
# screen). Full checklist: app-profiles/template-bundle/README.md
# screen). Full checklist: plugin-designs/template-bundle/README.md
# 3. it shows up in staging automatically — no list to touch
bun run build # 4. when ready, publish
```
@ -135,7 +135,7 @@ Arabic, Hindi, Chinese). The library is **Apache-2.0** — shared freely — but
is entirely yours**.
> **Change colors by editing values, not logic.** Your map's colors live in your own folder,
> `app-profiles/<your-map>/src/colors/`. Copy `example-mode.js`, rename it, change the hex
> `plugin-designs/<your-map>/src/colors/`. Copy `example-mode.js`, rename it, change the hex
> values. Your colors merge *over* the shared set for your map only, so they can never break
> another map. Never edit `library/colors/` to recolor your own map.

View file

@ -121,7 +121,7 @@
<main id="showcase"></main>
<div class="foot">
Live proof of the bundler · bundles auto-detected from <code>app-profiles/*/</code> · maps lazy-mounted, WebGL-context-safe.
Live proof of the bundler · bundles auto-detected from <code>plugin-designs/*/</code> · maps lazy-mounted, WebGL-context-safe.
<span class="scr">“Write the vision, and make it plain … that he may run that readeth it.” — Habakkuk 2:2</span>
</div>

View file

@ -7,7 +7,7 @@
* web-root, written by `bun run gallery` (last step of
* `bun run build`) from public/<tree>/manifest.json.
* renderGallery(model, { mode: 'dev' }) what `bun run dev` serves at / — the same page over a
* manifest-shaped model of app-profiles/ (see
* manifest-shaped model of plugin-designs/ (see
* internal/plugin-gallery/dev-plugin.mjs).
*
* public/ holds exactly three things: assets/ (data), <tree>/ (the bundle folders + manifest.json)
@ -32,7 +32,7 @@
* (the manifest's embed.iframe / embed.script).
*
* What mode 'dev' changes — and nothing else: the loader imports the bundle's dev entry
* (scriptUrl = /app-profiles/<bundle>/index.js, an ES module Vite serves) instead of adding an
* (scriptUrl = /plugin-designs/<bundle>/index.js, an ES module Vite serves) instead of adding an
* IIFE script tag; the live model is fetched from the dev route (<tree> = __plugin-gallery); and
* the header carries the composability ladder with its "?" popovers (kind explanations + the
* "Start here" primer). The popovers stay out of the built page on purpose: they add ~8 KB of
@ -134,9 +134,9 @@ const LADDER_JS = String.raw`<script>
how: 'The bundle index.js turns the shared ProfileLoader into an element class with defineCustomElement and registers the tag once with customElements.define.',
ex: '&lt;doxa-map profile-config=&#39;{&quot;profile&quot;:&quot;doxa-simple-map&quot;}&#39;&gt;&lt;/doxa-map&gt;' },
{ title: 'App profile (mounting file)',
def: 'One flat .vue file in app-profiles/&lt;bundle&gt;/ that defines a whole map app — the main component of that map. The settings written on the tag (profile-config) name which app profile to load, and the shared ProfileLoader picks it. In class-and-object terms: the element class is the class, each tag on a page is an instance, and the app profile is the screen that instance loads.',
how: 'Most repos need one such main file per app. doxa-maps builds hundreds of maps from one shared library, so every map gets its own app profile — the folder app-profiles/ is where they sit. The five kinds on this ladder are the five shapes those files come in.',
ex: 'app-profiles/doxa-simple-map/doxa-simple-map.vue' }
def: 'One flat .vue file in plugin-designs/&lt;bundle&gt;/ that defines a whole map app — the main component of that map. The settings written on the tag (profile-config) name which app profile to load, and the shared ProfileLoader picks it. In class-and-object terms: the element class is the class, each tag on a page is an instance, and the app profile is the screen that instance loads.',
how: 'Most repos need one such main file per app. doxa-maps builds hundreds of maps from one shared library, so every map gets its own app profile — the folder plugin-designs/ is where they sit. The five kinds on this ladder are the five shapes those files come in.',
ex: 'plugin-designs/doxa-simple-map/doxa-simple-map.vue' }
];
var LADDER = [
{ label: 'single-element', tier: 4, title: 'Single element', aka: 'single',

View file

@ -87,8 +87,8 @@ alias; **build your own custom code in your app-profile's \`src/\` folder.** Do
anything listed here unless you know what you're doing — these are shared across every map,
and a change here can break other maps.
- **Three zones:** \`library/\` (this menu — reuse, don't edit) · \`app-profiles/<map>/\` (your sandbox — build anything) · \`internal/\` (the build machine — never touch).
- **To make a new map:** copy \`app-profiles/template-bundle/\`, read its README, list your requirements, pick from below.
- **Three zones:** \`library/\` (this menu — reuse, don't edit) · \`plugin-designs/<map>/\` (your sandbox — build anything) · \`internal/\` (the build machine — never touch).
- **To make a new map:** copy \`plugin-designs/template-bundle/\`, read its README, list your requirements, pick from below.
`;
md += section('Components (reusable UI)', 'components', { recursive: true, ext: '.vue' }, '@map/components');

View file

@ -3,7 +3,7 @@
* generate-tree.mjs — build the self-contained doxa-maps/ output tree.
*
* Runs AFTER the per-bundle vite builds (which emit app/doxa-maps/<bundle>/<bundle>.js).
* It scans app-profiles/<bundle>/*.vue, pulls the OPTIONAL JSDoc header tags from the
* It scans plugin-designs/<bundle>/*.vue, pulls the OPTIONAL JSDoc header tags from the
* top of each profile, reads the custom-element tag(s) each bundle registers in its
* index.js, then WRITES, into the doxa-maps/ tree:
*
@ -628,7 +628,7 @@ function readInstances(dir, profileName, bundle, tag) {
})
}
// ── Scan app-profiles → bundle records ───────────────────────────────────────
// ── Scan plugin-designs → bundle records ───────────────────────────────────────
function scanBundles() {
if (!existsSync(APP_PROFILES)) {
console.error(`[generate-tree] no plugin-designs/ at ${APP_PROFILES}`)

View file

@ -11,7 +11,7 @@
* Phone widths (< 600) tap the sheet's drag strip first so the rows are visible.
* Site: --url http://localhost:3001/pray --tag doxa-map
* --url http://localhost:3001/research/ --tag doxa-research-map
* Plugin gallery dev server: --url http://localhost:5174/app-profiles/doxa-simple-map/index.html
* Plugin gallery dev server: --url http://localhost:5174/plugin-designs/doxa-simple-map/index.html
*/
import { launch, TELEMETRY } from './lib/browser.mjs'
const args = process.argv.slice(2); const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }

View file

@ -30,7 +30,7 @@
* shadow roots). Every observation is bucketed by the phase it STARTED in.
* --profile (Chromium): CDP Profiler sampled at 0.5 ms during the idle phase, aggregated
* to self/total ms per function@file:line; the top frames are printed (all, and app-only:
* library/ app-profiles/ internal/) and saved in the report. Line numbers are those of
* library/ plugin-designs/ internal/) and saved in the report. Line numbers are those of
* the Vite-served module (.js files keep their lines; .vue compiled output does not).
* Browser: Chromium by DEFAULT here (Playwright's bundled build — never Google Chrome) —
* LoAF, the Violation log and the Profiler are Chromium-only; --browser firefox still
@ -80,7 +80,7 @@ const JSON_OUT = argOf('--json', '')
const [VW, VH] = String(argOf('--viewport', '1440x900')).split('x').map(Number)
const VIEWPORT = { width: VW || 1440, height: VH || 900 }
const VIOLATION_RE = /requestAnimationFrame/i // applied to Violation-source log entries + console text
const APP_URL_RE = /\/(library|app-profiles|internal)\//
const APP_URL_RE = /\/(library|plugin-designs|internal)\//
// ── targets ──────────────────────────────────────────────────────────────────
if (TAGS.length > 1 && TAGS.length !== URLS.length) { console.error(`[${TOOL}] ${TAGS.length} --tag for ${URLS.length} --url — give one per --url, one for all, or none`); process.exit(2) }
@ -199,7 +199,7 @@ function aggregateProfile(profile) {
const meta = /^\((program|idle|garbage collector|root)\)$/
const byGroup = {}
for (const [k, ms] of self) {
const g = meta.test(k) ? k : /mapbox-gl|maplibre-gl/.test(k) ? 'mapbox-gl' : APP_URL_RE.test(k) ? 'app (library/app-profiles/internal)' : /\/node_modules\/|\/@vite\/|\/@fs\//.test(k) ? 'other node_modules' : 'other'
const g = meta.test(k) ? k : /mapbox-gl|maplibre-gl/.test(k) ? 'mapbox-gl' : APP_URL_RE.test(k) ? 'app (library/plugin-designs/internal)' : /\/node_modules\/|\/@vite\/|\/@fs\//.test(k) ? 'other node_modules' : 'other'
byGroup[g] = Math.round((byGroup[g] || 0) + ms)
}
return {

View file

@ -48,7 +48,7 @@
* [--no-select] skip the people-group selection and check the LEGEND-TAB
* free-drag contract instead (sheet stays where released,
* pull-to-bottom collapses, strip tap re-opens) + overlap audit
* Defaults: --url http://localhost:5174/app-profiles/doxa-simple-map/index.html
* Defaults: --url http://localhost:5174/plugin-designs/doxa-simple-map/index.html
* --tag doxa-map --nth 0. NOTE the standalone dev page ignores ?profile= (its tabs
* are per-bundle, read from localStorage) and renders three stacked <doxa-map>
* slots (engagement / prayer / adoption): pick one with --instance dev-prayer.
@ -65,7 +65,7 @@ import { launch, TELEMETRY } from './lib/browser.mjs'
const TOOL = 'mobile-sheet-probe'
const args = process.argv.slice(2)
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
const URL = String(argOf('--url', 'http://localhost:5174/app-profiles/doxa-simple-map/index.html'))
const URL = String(argOf('--url', 'http://localhost:5174/plugin-designs/doxa-simple-map/index.html'))
const TAG = argOf('--tag', 'doxa-map')
const NTH = Number(argOf('--nth', 0))
const INSTANCE = argOf('--instance', '')

View file

@ -17,7 +17,7 @@ import { readBuildConfig, renderGallery } from '../generate-gallery.mjs'
// ─── DEV plugin gallery ──────────────────────────────────────────────────────
// `bun x vite` (dev) serves the plugin gallery at `/` — the same page `bun run build`
// writes to public/index.html — over every profile DISCOVERED across app-profiles/*/.
// writes to public/index.html — over every profile DISCOVERED across plugin-designs/*/.
// Choosing a map lazy-imports that bundle's dev entry (registering its custom element)
// and mounts it, with the dev Mapbox token (VITE_MAPBOX_TOKEN) as the baked rung of
// the token ladder so maps just work. Dev-only (apply:'serve'); the build is untouched.
@ -27,7 +27,7 @@ import { readBuildConfig, renderGallery } from '../generate-gallery.mjs'
// and `'/boundaries/*.geojson'`. publicDir is false ON PURPOSE (a bundle build must never
// inline public/), so dev has to serve the asset tree explicitly, the way the built site
// does: public/assets first (exactly what generate-tree ships), else the per-bundle source
// of truth app-profiles/<bundle>/data/<rel> when public/ has been cleaned.
// of truth plugin-designs/<bundle>/data/<rel> when public/ has been cleaned.
const DEV_ASSET_MIME = {
'.json': 'application/json; charset=utf-8', '.geojson': 'application/geo+json; charset=utf-8',
'.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.csv': 'text/csv; charset=utf-8',
@ -39,7 +39,7 @@ export function resolveDevAsset(rel, root = REPO_ROOT) {
const isFile = (p) => existsSync(p) && statSync(p).isFile()
const shipped = resolve(root, 'public', 'assets', rel)
if (isFile(shipped)) return shipped
const profiles = resolve(root, 'app-profiles')
const profiles = resolve(root, 'plugin-designs')
for (const name of readdirSync(profiles)) {
if (name.startsWith('_') || name.startsWith('.')) continue
const cand = resolve(profiles, name, 'data', rel)
@ -62,7 +62,7 @@ export function galleryModel(reg) {
const iframe = (u) => `<iframe src="${u}?utm_source=YOUR_SOURCE" width="100%" height="600" style="border:none;border-radius:12px;" allowfullscreen></iframe>`
const link = (...parts) => '/#/' + parts.map(encodeURIComponent).join('/')
const bundles = [...reg].sort((a, b) => a.bundle.localeCompare(b.bundle)).map((b) => {
const scriptUrl = `/app-profiles/${b.bundle}/index.js`
const scriptUrl = `/plugin-designs/${b.bundle}/index.js`
const presets = new Map() // profile → its preset instances (empty for every other kind)
for (const it of b.items) {
if (!presets.has(it.profile)) presets.set(it.profile, [])
@ -89,7 +89,7 @@ export function galleryModel(reg) {
return { bundle: b.bundle, scriptUrl, integrity: '', elements: [b.tag], profiles }
})
return {
schema: 'doxa-maps-manifest@1', generatedFrom: 'app-profiles/*/*.vue (dev discovery)', embedBase: '.',
schema: 'doxa-maps-manifest@1', generatedFrom: 'plugin-designs/*/*.vue (dev discovery)', embedBase: '.',
bundleCount: bundles.length, profileCount: bundles.reduce((n, b) => n + b.profiles.length, 0), bundles,
}
}
@ -126,7 +126,7 @@ export function autoSpaDevPlugin(token = '') {
try { rel = decodeURIComponent(path.slice('/assets/'.length)) } catch { rel = '' }
if (!rel || rel.split('/').includes('..')) { res.statusCode = 400; return res.end('bad asset path') }
const file = resolveDevAsset(rel)
if (!file) return json(res, { error: 'asset not found', asset: rel, looked: ['public/assets/', 'app-profiles/*/data/'] }, 404)
if (!file) return json(res, { error: 'asset not found', asset: rel, looked: ['public/assets/', 'plugin-designs/*/data/'] }, 404)
res.statusCode = 200
res.setHeader('Content-Type', DEV_ASSET_MIME[extname(file).toLowerCase()] || 'application/octet-stream')
res.setHeader('Cache-Control', 'no-cache')

View file

@ -1,5 +1,5 @@
/**
* discover.mjs — the auto-generated SPA's registry: every bundle under app-profiles/,
* discover.mjs — the auto-generated SPA's registry: every bundle under plugin-designs/,
* every profile inside it, classified into a composition tier.
*
* single → parameterized → monolithic → nested-elements-mfe → nested-mfe-spa
@ -58,7 +58,7 @@ export function detectProfileType(dir, profile, ownTag, allTags, hasInstances) {
}
export function discoverSpaRegistry(root = REPO_ROOT) {
const APP_DIR = resolve(root, 'app-profiles')
const APP_DIR = resolve(root, 'plugin-designs')
const bundles = []
if (!existsSync(APP_DIR)) return bundles
const dirs = readdirSync(APP_DIR).sort().filter(n => {

View file

@ -10,7 +10,7 @@
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
* (paste once, stored locally in the browser, never in files/links unless you
* use ?tk= on purpose). Maps must Just Work after one paste.
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
* - Auto-generated: whatever plugin-designs exist, the tree detects and shows them.
*
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
@ -96,7 +96,7 @@ function buildFlat() {
kind: kind === 'nested' ? 'nested' : 'profile',
label: p.profile,
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
edit: 'plugin-designs/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})

View file

@ -21,13 +21,13 @@
// language on every map as the `locale` PROP (profile-config.locale) and re-mounts.
// ── Discovery ───────────────────────────────────────────────────────────────────────────────
const indexRaw = import.meta.glob('/app-profiles/*/index.js', { query: '?raw', import: 'default', eager: true })
const htmlRaw = import.meta.glob('/app-profiles/*/index.html', { query: '?raw', import: 'default', eager: true })
const bundleLoaders = import.meta.glob('/app-profiles/*/index.js') // lazy: importing registers the element
const profileFiles = import.meta.glob('/app-profiles/*/*.vue')
const indexRaw = import.meta.glob('/plugin-designs/*/index.js', { query: '?raw', import: 'default', eager: true })
const htmlRaw = import.meta.glob('/plugin-designs/*/index.html', { query: '?raw', import: 'default', eager: true })
const bundleLoaders = import.meta.glob('/plugin-designs/*/index.js') // lazy: importing registers the element
const profileFiles = import.meta.glob('/plugin-designs/*/*.vue')
const TOKEN = import.meta.env.VITE_MAPBOX_TOKEN || ''
const nameOf = (p) => p.split('/app-profiles/')[1].split('/')[0]
const nameOf = (p) => p.split('/plugin-designs/')[1].split('/')[0]
function tagsOf(src) {
const re = /customElements\.define\(\s*['"]([^'"]+)['"]/g
@ -102,7 +102,7 @@ function slotsFor(b) {
const imported = {}
function loadBundle(name) {
if (!imported[name]) {
const key = Object.keys(bundleLoaders).find((k) => k.includes('/app-profiles/' + name + '/index.js'))
const key = Object.keys(bundleLoaders).find((k) => k.includes('/plugin-designs/' + name + '/index.js'))
imported[name] = key ? bundleLoaders[key]() : Promise.reject(new Error('bundle loader not found: ' + name))
}
return imported[name]

View file

@ -4,7 +4,7 @@
// useShadowStyles(`…`). Those literals ship verbatim inside the built IIFE, so any
// CSS block comment written in them would reach the public drop-in as dead weight.
// This build-only plugin rewrites the source of every .vue and .js file under
// library/ and app-profiles/ before the Vue compiler sees it (enforce: 'pre'):
// library/ and plugin-designs/ before the Vue compiler sees it (enforce: 'pre'):
// inside each such literal it removes the block comments and collapses the blank
// lines they leave behind. Nothing outside the literal changes, and dev mode is
// untouched so the comments stay readable while working.
@ -17,7 +17,7 @@ import { resolve, dirname, sep } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const SOURCE_ROOTS = ['library', 'app-profiles'].map(d => resolve(ROOT, d) + sep)
const SOURCE_ROOTS = ['library', 'plugin-designs'].map(d => resolve(ROOT, d) + sep)
const TAG = '[strip-shadow-css-comments]'
// The call site: `useShadowStyles(` then optional whitespace then the opening backtick.

View file

@ -1,6 +1,6 @@
# layouts/ — app wrappers
Each folder here is an **app wrapper**: a host shell that composes doxa-maps app-profiles (the
Each folder here is an **app wrapper**: a host shell that composes doxa-maps plugin-designs (the
independently-built custom elements) into one application.
```

View file

@ -770,7 +770,7 @@ function label(id){
}
/* ══════════════════════════════════════════════════════════════════════
PROFILE REGISTRY — which app-profiles are wired to a real bundle.
PROFILE REGISTRY — which plugin-designs are wired to a real bundle.
`tag` the custom element the bundle registers
`src` the built bundle (self-contained, relative → drop-portable)
`config` the profile-config JSON the element takes
@ -782,7 +782,7 @@ const PROFILES = {
// which was architecturally wrong — the research map is a 6-tab research tool
// and belongs under the Research section, not Prayer Mobilization.
// simple-map ships three named single-tab instances (prayer / engagement /
// adoption) declared in app-profiles/doxa-simple-map/simple-map.instances.json.
// adoption) declared in plugin-designs/doxa-simple-map/simple-map.instances.json.
// The 'prayer' one is titled "Prayer Map" and is the right map for this slot.
// NOTE its element tag is <doxa-map>, NOT <doxa-simple-map> — the bundle
// registers both, and the generated instance pages use <doxa-map>. Config is
@ -796,7 +796,7 @@ const PROFILES = {
// ⚠️ colorStrategy MUST be 'prayerProgress', NOT 'prayer'.
// The strategy registry derives its keys from FILENAMES via import.meta.glob:
// library/colors/prayer-progress.js -> 'prayerProgress'. There is no prayer.js
// anywhere (nor a profile-local one in app-profiles/doxa-simple-map/src/colors/),
// anywhere (nor a profile-local one in plugin-designs/doxa-simple-map/src/colors/),
// so 'prayer' does not resolve — and getColorStrategy() SILENTLY FALLS BACK to
// languageFamily. No error, just a completely different palette, which is why
// this map lost the white-ringed "glowing" prayer pins.
@ -838,7 +838,7 @@ const PROFILES = {
// is what turns it into a real app.
//
// Tag + profile names come from the bundle itself, NOT guessed:
// folder app-profiles/country-posters/ -> src below
// folder plugin-designs/country-posters/ -> src below
// element <country-posters-map> (index.js customElements.define)
// profiles countries-map.vue / country-poster-wall.vue
// The profile value is the .vue FILENAME, which is why 'countries-map' is right

View file

@ -108,7 +108,7 @@ const instanceId = computed(() =>
)
// ─── Profile Registry — provided by bundle entry ────────────────────────────
// The bundle entry (app-profiles/<bundle>/index.js) MUST run
// The bundle entry (plugin-designs/<bundle>/index.js) MUST run
// app.provide('profileModules', import.meta.glob('./*.vue'))
// because import.meta.glob cannot cross package boundaries — it must be
// evaluated by the bundle that owns the profile .vue files.

View file

@ -1,7 +1,7 @@
# library/ (`@map`) — the shared map template
**The other half of the architecture** (see [`../app-profiles/README.md`](../app-profiles/README.md)):
every map in `app-profiles/<your-map>/` composes the reusable, parameterized pieces that live here.
**The other half of the architecture** (see [`../plugin-designs/README.md`](../plugin-designs/README.md)):
every map in `plugin-designs/<your-map>/` composes the reusable, parameterized pieces that live here.
Fix something here and **every map benefits** on the next build; break something here and every map
breaks — read [`components/WARNING.md`](./components/WARNING.md) before touching shared pieces.

View file

@ -12,7 +12,7 @@ progress, religion, …). Drop a `<name>.js` in and it's auto-discovered; no reg
│ _registry.js (the scanner)│ └───────────────────────┘
└──────────────────────────┘
shared set a single map can add/override its own in
app-profiles/<map>/src/colors/ (that map only)
plugin-designs/<map>/src/colors/ (that map only)
```
- **Shared here** = reused by every map. **Your map's own** color → put it in your bundle's

View file

@ -3,7 +3,7 @@
*
* Each strategy lives in its own file in this folder (one strategy = one file).
* Strategies are discovered automatically at build time via Vite's
* `import.meta.glob` — the SAME pattern app-profiles use to auto-discover
* `import.meta.glob` — the SAME pattern plugin-designs use to auto-discover
* bundles. There is NO manual registration step.
*
* Add a new strategy:

View file

@ -1,11 +1,11 @@
# ⚠️ Read before you touch these components
**These are the SHARED, REUSABLE components — every map in `app-profiles/` depends on them.**
**These are the SHARED, REUSABLE components — every map in `plugin-designs/` depends on them.**
A change here does not affect one map. It ripples to **all** of them. So:
- **Prototyping or experimenting? Don't edit these.** Put your custom, one-off, not-yet-proven component
inside your own map's folder (`app-profiles/<your-map>/`), where it can be as messy as you need and
inside your own map's folder (`plugin-designs/<your-map>/`), where it can be as messy as you need and
breaks nothing else.
- **Only proven, parameterized, reusable components graduate into this library.** If a component has hard-coded
values, assumptions about one specific map, or hasn't been used by more than one map yet — it is **not**

View file

@ -17,7 +17,7 @@ import { getColorStrategy, COLOR_MODES } from '../../colors/_registry.js'
// (inside the builder, at call time) so registration has already run — a
// module-load-time read could see an empty palette.
const doxaRegionColors = () => getColorStrategy(COLOR_MODES.DOXA_REGION)?.palette ?? {}
// resource is likewise research-only (app-profiles/doxa-research-map/src/colors/
// resource is likewise research-only (plugin-designs/doxa-research-map/src/colors/
// resource.js), registry-resolved. Lazy + GUARDED on propertyKey — getColorStrategy
// falls back to language-family for unregistered modes, and that palette must not
// masquerade as resource colors.

View file

@ -42,7 +42,7 @@ import { computed } from 'vue'
// Single source of truth — pins paint with this same palette, keyed by ROP1 code.
// Resolving the SAME strategy the pins use keeps legend ↔ map in lock-step (was a
// separate name-keyed palette causing pin/legend drift). affinity-block is a per-map
// (research-only) strategy that lives in app-profiles/doxa-research-map/
// (research-only) strategy that lives in plugin-designs/doxa-research-map/
// src/colors/affinity-block.js and is resolved via the registry (registered by
// the research bundle's local src/colors glob). Read LAZILY (at call time
// inside blocColor — this composable is only invoked by research surfaces) so

View file

@ -29,7 +29,7 @@ const COLUMNS_SIMPLE = [] // e.g. affinity-blocs — no numeric columns
// ─── Resource palette/names (research-only strategy) ──────────────────────────
// resource is a per-map (research-only) strategy that lives in
// app-profiles/doxa-research-map/src/colors/resource.js and is resolved via
// plugin-designs/doxa-research-map/src/colors/resource.js and is resolved via
// the registry (registered by the research bundle's local src/colors glob).
// Read LAZILY (inside the items computed, at call time) so registration has already
// run, and GUARDED on propertyKey — getColorStrategy silently falls back to
@ -42,7 +42,7 @@ function resourceStrategy() {
// ─── Region / Bloc palettes (registry strategies — single source of truth) ───
// doxa-region and affinity-block are PER-MAP (research-only) strategies that
// live in app-profiles/doxa-research-map/src/colors/ and register via the
// live in plugin-designs/doxa-research-map/src/colors/ and register via the
// research bundle's local src/colors glob. Read LAZILY (inside the items
// computed, at call time) so registration has already run, and GUARDED on
// propertyKey — getColorStrategy silently falls back to language-family when a

View file

@ -2,7 +2,7 @@ import { nextTick, unref } from 'vue'
/**
* Shared geocoder→map search logic (kind-switch + Mapbox filter builder + the
* common legend-tab tail). One copy for all map app-profiles AND the future
* common legend-tab tail). One copy for all map plugin-designs AND the future
* dashboard — the kind-switch / per-kind property map / filter-expression
* builder live HERE and nowhere else.
*

View file

@ -5,7 +5,7 @@
// bbox, and which sheet size it takes.
//
// node emit-countries.mjs
// -> ../../app-profiles/country-posters/data/countries.json
// -> ../../plugin-designs/country-posters/data/countries.json
//
// SHEET LADDER (design note 2026-08-01) — the count selects the SHEET SIZE, it does not
// exclude a country:

View file

@ -26,14 +26,14 @@ import { dirname, join, resolve } from 'node:path';
import { fetchGrid, bestZoom } from './osm-basemap.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
// The profile data lives in THIS repo — library/scripts/posters -> app-profiles.
// The profile data lives in THIS repo — library/scripts/posters -> plugin-designs.
// This generator used to sit outside doxa-maps entirely and reach in via
// UPG100_DATA_PATH; forgetting that env var silently rendered a STALE COPY of the
// data (that is how Nepal vanished from a poster). Now the default path is the
// repo's own profile, so the generator cannot read anyone else's data by accident.
const DATA = process.env.UPG100_DATA_PATH
? resolve(process.env.UPG100_DATA_PATH)
: resolve(__dirname, '..', '..', '..', 'app-profiles', 'my-upg-100-list', 'data');
: resolve(__dirname, '..', '..', '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });
@ -2520,7 +2520,7 @@ if (CITIES) {
console.log(`[generate] CITY MODE — ${zones.length} named ASCII population centres` +
(unresolved ? ` (${unresolved} group names did not resolve to a slug)` : ''));
} else if (COUNTRIES) {
const cpath = resolve(__dirname, '..', '..', '..', 'app-profiles', 'country-posters', 'data', 'countries.json');
const cpath = resolve(__dirname, '..', '..', '..', 'plugin-designs', 'country-posters', 'data', 'countries.json');
const cdata = JSON.parse(readFileSync(cpath, 'utf8'));
const want = process.env.SHEET || '3x3'; // which tier to build
zones = (cdata.countries || [])

View file

@ -3,7 +3,7 @@
the app ships.
python3 make-previews.py
-> app-profiles/my-upg-100-list/assets/previews/<zone>.webp 560px card thumbnails
-> plugin-designs/my-upg-100-list/assets/previews/<zone>.webp 560px card thumbnails
-> public/assets/posters/<zone>.webp 1500px the download
WHY TWO SIZES
@ -62,5 +62,5 @@ for p in pngs:
fkb += os.path.getsize(fp) / 1024
print(f'[make-previews] {len(pngs)} posters')
print(f' thumbnails -> app-profiles/.../assets/previews {tkb/1024:5.1f} MB (inlined in the bundle)')
print(f' thumbnails -> plugin-designs/.../assets/previews {tkb/1024:5.1f} MB (inlined in the bundle)')
print(f' full size -> public/assets/posters {fkb/1024:5.1f} MB (served + shipped in the APK)')

View file

@ -24,14 +24,14 @@ import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// The profile data lives in THIS repo — library/scripts/posters -> app-profiles.
// The profile data lives in THIS repo — library/scripts/posters -> plugin-designs.
// This generator used to sit outside doxa-maps entirely and reach in via
// UPG100_DATA_PATH; forgetting that env var silently rendered a STALE COPY of the
// data (that is how Nepal vanished from a poster). Now the default path is the
// repo's own profile, so the generator cannot read anyone else's data by accident.
const DATA = process.env.UPG100_DATA_PATH
? resolve(process.env.UPG100_DATA_PATH)
: resolve(__dirname, '..', '..', 'app-profiles', 'my-upg-100-list', 'data');
: resolve(__dirname, '..', '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });

View file

@ -24,7 +24,7 @@ import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'app-profiles', 'my-upg-100-list', 'data');
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });

View file

@ -70,11 +70,11 @@ cp snapshots/v2-3x3-full-vision/generate.mjs generate.mjs
```bash
ONLY=asia-india-uttarakhand \
UPG100_DATA_PATH=<repo-root>/app-profiles/my-upg-100-list/data \
UPG100_DATA_PATH=<repo-root>/plugin-designs/my-upg-100-list/data \
OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 \
node generate.mjs
```
`UPG100_DATA_PATH` is **required** — the generator's default points at a stale duplicate of
the profile data in `app-profiles/`, which is missing Nepal (58 slugs, no
the profile data in `plugin-designs/`, which is missing Nepal (58 slugs, no
`adm0`). Without it Nepal silently disappears from the sheet.

View file

@ -12,7 +12,7 @@ cd <repo-root>/library/scripts/posters
# ONE poster
SPARSE=1 SERIES_INDEX=1 ONLY=asia-india-uttarakhand \
UPG100_DATA_PATH=<repo-root>/app-profiles/my-upg-100-list/data \
UPG100_DATA_PATH=<repo-root>/plugin-designs/my-upg-100-list/data \
OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 \
node generate.mjs
@ -26,7 +26,7 @@ SPARSE=1 UPG100_DATA_PATH=... OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life
|---|---|
| `SPARSE=1` | **Selects THIS template.** Without it you get the v2 full-vision layout. |
| `FORMAT=3x3` | 1500×1500 square sheet. |
| `UPG100_DATA_PATH` | **Required.** The generator's default points at a STALE DUPLICATE of the profile data in `app-profiles/` (58 slugs, no `adm0`) — without this, Nepal silently vanishes and the sheet renders 58 pins instead of 92. |
| `UPG100_DATA_PATH` | **Required.** The generator's default points at a STALE DUPLICATE of the profile data in `plugin-designs/` (58 slugs, no `adm0`) — without this, Nepal silently vanishes and the sheet renders 58 pins instead of 92. |
| `OFFLINE=1` | Uses the cached `out/_records.json` instead of calling the API. |
| `VITE_API_BASE_URL` | Satisfies the fail-closed config gate. No request is made under `OFFLINE=1`. |
| `SERIES_INDEX` | Optional. Overrides the sheet's position in the series. |

View file

@ -24,7 +24,7 @@ import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'app-profiles', 'my-upg-100-list', 'data');
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });

View file

@ -24,7 +24,7 @@ import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'app-profiles', 'my-upg-100-list', 'data');
const DATA = process.env.UPG100_DATA_PATH ? resolve(process.env.UPG100_DATA_PATH) : join(__dirname, '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });

View file

@ -16,7 +16,7 @@ cd doxa-maps/library/scripts/posters
cp snapshots/v5-3x3-approved/generate.mjs .
cp snapshots/v5-3x3-approved/poster-regions.mjs .
# clusters.json only if the DATA also needs rolling back:
# cp snapshots/v5-3x3-approved/clusters.json ../../app-profiles/my-upg-100-list/data/
# cp snapshots/v5-3x3-approved/clusters.json ../../plugin-designs/my-upg-100-list/data/
bun run posters && bun run posters:shoot && bun run posters:previews && bun run build
```

View file

@ -24,14 +24,14 @@ import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// The profile data lives in THIS repo — library/scripts/posters -> app-profiles.
// The profile data lives in THIS repo — library/scripts/posters -> plugin-designs.
// This generator used to sit outside doxa-maps entirely and reach in via
// UPG100_DATA_PATH; forgetting that env var silently rendered a STALE COPY of the
// data (that is how Nepal vanished from a poster). Now the default path is the
// repo's own profile, so the generator cannot read anyone else's data by accident.
const DATA = process.env.UPG100_DATA_PATH
? resolve(process.env.UPG100_DATA_PATH)
: resolve(__dirname, '..', '..', 'app-profiles', 'my-upg-100-list', 'data');
: resolve(__dirname, '..', '..', 'plugin-designs', 'my-upg-100-list', 'data');
const OUT = join(__dirname, 'out');
const POSTERS = join(OUT, 'posters');
mkdirSync(POSTERS, { recursive: true });

View file

@ -2,7 +2,7 @@
//
// node build-language-resources.mjs
// -> out/language-resources.json (per ISO 639-3 language)
// -> ../../app-profiles/gospel-resources/data/resources.json (the app's source)
// -> ../../plugin-designs/gospel-resources/data/resources.json (the app's source)
//
// ═══ WHAT THIS ANSWERS ══════════════════════════════════════════════════════
// "Which of the DX people groups have scripture in their own language, and which
@ -46,7 +46,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT = join(__dirname, 'out');
const RECORDS = resolve(__dirname, '..', 'posters', 'out', '_records.json');
const EBIBLE = join(OUT, 'ebible-translations.csv');
const APPDATA = resolve(__dirname, '..', '..', '..', 'app-profiles', 'gospel-resources', 'data');
const APPDATA = resolve(__dirname, '..', '..', '..', 'plugin-designs', 'gospel-resources', 'data');
const POOLS = [{
id: 'ebible',

View file

@ -34,7 +34,7 @@ const OUT = join(__dirname, 'out');
const TABLES = join(OUT, 'tables');
// Tables that must NEVER ship publicly (see data-private/README.md).
const PRIVATE_TABLES = new Set(['field-access-visas']);
const PROFILES = resolve(__dirname, '..', '..', '..', 'app-profiles');
const PROFILES = resolve(__dirname, '..', '..', '..', 'plugin-designs');
const read = (f) => JSON.parse(readFileSync(join(OUT, f), 'utf8'));
const has = (f) => existsSync(join(OUT, f));

View file

@ -89,7 +89,7 @@ const GATE_CAVEATS = [
'A FLOOR IS NOT A FIDELITY PROOF. It catches WHOLESALE coarsening (a 3x point collapse), not a subtle reshape — geometry can be moved, smoothed, or distorted at a constant point count and this passes.',
'IT DOES NOT CHECK SHAPE. Points are counted, never compared to a reference outline. A garbled coastline with enough vertices is GREEN.',
'gb-CHN-ADM1.geojson HAS NO FLOOR. Shipping 3,264 points vs 2,000 under the same coarsening knobs = 1.63x — too thin to separate honestly. That file is UNGUARDED here; treat it as device-gated.',
'THIS REPO HAS 83 FILES NAMED world-adm0.geojson (72 under app/, 10 under public/, 1 under app-profiles/; none in node_modules). A GREEN here covers ONLY the path printed above — the other 82 are GENERATED BUNDLE ARTIFACTS and this floor says NOTHING about their freshness or even their validity. Do NOT infer a second floor is needed: a 2026-08-02 snapshot found the app/ copies were mostly PRE-INDIA BUILDS (161 features / 111,013 points, differing from the guarded file by IND alone — stale, not coarsened, and above this floor). But that snapshot is NOT a durable fact: the copies are rewritten by bundle builds, one was mid-sweep TRUNCATED and would not even parse, and any census of them is a moving target. The durable question is whether bundles are REBUILT AND VALIDATED after a boundary change — a build-pipeline property, not something a point floor can hold. NOTE THE STAKES: measured 2026-08-02, ALL NINE per-app copies under public/doxa-maps/ were missing IND (161 features, built 22:05 the previous day, before India landed). "Stale by one country" IS the India-paints-nothing regression this guard was written for — and the "India paints" assertion above only covers the ONE canonical path, not the generated per-app trees.',
'THIS REPO HAS 83 FILES NAMED world-adm0.geojson (72 under app/, 10 under public/, 1 under plugin-designs/; none in node_modules). A GREEN here covers ONLY the path printed above — the other 82 are GENERATED BUNDLE ARTIFACTS and this floor says NOTHING about their freshness or even their validity. Do NOT infer a second floor is needed: a 2026-08-02 snapshot found the app/ copies were mostly PRE-INDIA BUILDS (161 features / 111,013 points, differing from the guarded file by IND alone — stale, not coarsened, and above this floor). But that snapshot is NOT a durable fact: the copies are rewritten by bundle builds, one was mid-sweep TRUNCATED and would not even parse, and any census of them is a moving target. The durable question is whether bundles are REBUILT AND VALIDATED after a boundary change — a build-pipeline property, not something a point floor can hold. NOTE THE STAKES: measured 2026-08-02, ALL NINE per-app copies under public/doxa-maps/ were missing IND (161 features, built 22:05 the previous day, before India landed). "Stale by one country" IS the India-paints-nothing regression this guard was written for — and the "India paints" assertion above only covers the ONE canonical path, not the generated per-app trees.',
'THE ADM1 LAZY FIGURE IS MODELED, NOT OBSERVED. resimplify() only ever rewrites world-adm0.geojson, so the ADM1 files have no lazy counterpart that the shipping pipeline actually produces. Their floors were set by applying the same knobs offline.',
'STILL DEVICE-GATED: whether the coastlines READ at print size. This converts a wholesale-coarsening regression into a CI failure; it does not replace a human looking at a printed poster.',
];
@ -101,7 +101,7 @@ function checkPointFloors() {
// coarsens "world-adm0.geojson" to test this floor has 83 candidates and only
// ONE is the file read here — and coarsening any other yields a GREEN run that
// reads exactly like "the floor does not fire". That happened during this
// card's own review (a coarsened app-profiles/.../data/adm0 copy, exit 0, read
// card's own review (a coarsened plugin-designs/.../data/adm0 copy, exit 0, read
// for a minute as the guard failing). A gate that names an ambiguous file is
// under-declaring what it measured, so it says the full path out loud.
console.log(` reading from: ${join(ASSETS, 'boundaries')}`);

View file

@ -2,7 +2,7 @@
* Back-compat shim. The real DataSourceManager.js lives in the `api/` seam
* folder. New code imports from '@map/api/_registry.js' (or
* '@map/api/DataSourceManager.js'). This shim keeps existing
* `@map/utils/DataSourceManager.js` imports (3 app-profiles + the barrel)
* `@map/utils/DataSourceManager.js` imports (3 plugin-designs + the barrel)
* working.
*/
export { DataSourceManager, dataSourceManager } from '../api/DataSourceManager.js'

View file

@ -1,7 +1,7 @@
# app-profiles/ — where YOUR maps live
# plugin-designs/ — where YOUR maps live
The shared template lives in `../library/` (aliased `@map`) — you never edit it per-map.
You build your map by making a folder **here**, `app-profiles/<your-map>/`, and composing the
You build your map by making a folder **here**, `plugin-designs/<your-map>/`, and composing the
template's building blocks. Your folder is yours (colors, API, i18n); the template is shared
(Apache-2.0, propagated to every map on the next build).
@ -12,7 +12,7 @@ template's building blocks. Your folder is yours (colors, API, i18n); the templa
│ (every map reuses these)
│ imported by
▼
app-profiles/<your-map>/ ── YOURS ──► the map's "main" class (the .vue profile = the Vue
plugin-designs/<your-map>/ ── YOURS ──► the map's "main" class (the .vue profile = the Vue
│ instance for that map)
└── src/ ── YOURS ──► this map's OWN non-reusable components, colors, API, utils
@ -21,7 +21,7 @@ template's building blocks. Your folder is yours (colors, API, i18n); the templa
- **Reusable → `library/`.** The legend, the map, the map-controls are shared, parameterized
components every map reuses. Fix once, every map benefits. Never edit it to customize one map.
- **This map's own → `app-profiles/<your-map>/`.** It holds two things: the map's **"main" class**
- **This map's own → `plugin-designs/<your-map>/`.** It holds two things: the map's **"main" class**
(each `.vue` profile *is* the Vue instance for that map) **and** that map's **own non-reusable
components** (in `src/`). Nothing here leaks to another map.
- **Many maps per bundle.** The bundler bundles one map or many — add another flat `.vue` and rebuild.
@ -42,7 +42,7 @@ encouraged** — create as many as you need:
## The pattern in one picture
```
app-profiles/<your-map>/ ← one FOLDER = one BUNDLE, named after the folder
plugin-designs/<your-map>/ ← one FOLDER = one BUNDLE, named after the folder
├── index.js ← entry: registers the custom element, finds the profiles
├── overview.vue ← one .vue = one PROFILE (flat in the folder — no profiles/ subfolder)
└── detail.vue ← another profile in the SAME bundle
@ -64,7 +64,7 @@ app-profiles/<your-map>/ ← one FOLDER = one BUNDLE, named after the folder
```bash
# 1. create the folder + one profile
mkdir -p app-profiles/<your-map>
mkdir -p plugin-designs/<your-map>
# index.js ← copy doxa-simple-map/index.js, rename the tag
# <name>.vue ← your screen, built from @map/* blocks (flat in the folder)
@ -78,7 +78,7 @@ npm run build # → public/doxa-maps/<your-map>/<your-map>.js
## Your three things (everything else is shared)
- **Colors** — how pins are colored. Pick one the template ships (`getColorStrategy('language-family')`),
or drop your own in `app-profiles/<your-map>/src/colors/` (start from
or drop your own in `plugin-designs/<your-map>/src/colors/` (start from
`template-bundle/src/colors/example-mode.js`). Never edit `@map/colors/` to recolor your map.
- **API** — your map reads its data from the `profile-config` on the page, so the same bundle points
at different data with no rebuild. `DataSourceManager` accepts `csv` (bundled file), `api` (one JSON

View file

@ -1,5 +1,5 @@
/**
* app-profiles/all-people-groups/index.js
* plugin-designs/all-people-groups/index.js
*
* Bundle entry for the ALL PEOPLE GROUPS map — every people group on earth
* (peoplegroups.org, 12,379 group×country rows) around the DOXA set.

View file

@ -1,5 +1,5 @@
/**
* app-profiles/cities-internet/index.js — bundle entry for CITIES WITH INTERNET.
* plugin-designs/cities-internet/index.js — bundle entry for CITIES WITH INTERNET.
*
* Registers ONE custom-element tag:
* <cities-internet-map profile-config='{"profile":"connectivity-list"}'></cities-internet-map>

View file

@ -1,5 +1,5 @@
/**
* app-profiles/country-posters/index.js
* plugin-designs/country-posters/index.js
*
* Bundle entry — registers the country-posters web component.
*

View file

@ -1,8 +1,8 @@
/**
* app-profiles/demo-map/index.js
* plugin-designs/demo-map/index.js
*
* Bundle entry — registers the <demo-map> web component.
* Adapted from app-profiles/template-bundle/index.js (the copy-me reference).
* Adapted from plugin-designs/template-bundle/index.js (the copy-me reference).
*
* Folder name = bundle name. This folder builds to exactly one output file:
* app/doxa-maps/demo-map/demo-map.js

View file

@ -179,7 +179,7 @@
just by using a new value.
profile application profile name (must match
app-profiles/doxa-research-map/<name>.vue)
plugin-designs/doxa-research-map/<name>.vue)
label header title shown on the gem-frame
gem amethyst | sapphire | aquamarine | emerald |
topaz | citrine | tourmaline. Omit to auto-cycle.

View file

@ -179,7 +179,7 @@
just by using a new value.
profile application profile name (must match
app-profiles/doxa-simple-map/<name>.vue)
plugin-designs/doxa-simple-map/<name>.vue)
label header title shown on the gem-frame
gem amethyst | sapphire | aquamarine | emerald |
topaz | citrine | tourmaline. Omit to auto-cycle.

View file

@ -1,5 +1,5 @@
/**
* app-profiles/doxa-simple-map/index.js
* plugin-designs/doxa-simple-map/index.js
*
* Bundle entry — registers the doxa-simple-map web component(s).
*

View file

@ -164,7 +164,7 @@ const count = (fn) => languages.value.filter(fn).length
// ═══ A FIELD THAT IS NOT IN THE TABLE IS NOT A ZERO ═════════════════════════
// MEASURED 2026-08-02: the copy of people-group-resources.json served from the
// bundle tree predates `has_open_bible_stories`, while the copy under app-profiles/
// bundle tree predates `has_open_bible_stories`, while the copy under plugin-designs/
// carries it for 38 languages. Counting a missing field gives 0, and a 0 bar reads as
// "no language anywhere has this" — a fabricated absence, from a build artifact being
// a version behind. So a metric whose FIELD is absent renders as not-measured,

View file

@ -1,5 +1,5 @@
/**
* app-profiles/gospel-resources/index.js — bundle entry for GOSPEL RESOURCES.
* plugin-designs/gospel-resources/index.js — bundle entry for GOSPEL RESOURCES.
*
* Registers ONE custom-element tag:
* <gospel-resources-map profile-config='{"profile":"resources-list"}'></gospel-resources-map>

View file

@ -1,6 +1,6 @@
// verify-gospel-click-sheet.mjs — prove the COUNTRY-CLICK SHEET is correct without a GPU.
//
// node app-profiles/gospel-resources/scripts/verify-gospel-click-sheet.mjs # exits 1 on any failure
// node plugin-designs/gospel-resources/scripts/verify-gospel-click-sheet.mjs # exits 1 on any failure
//
// ═══ WHY THIS EXISTS ════════════════════════════════════════════════════════
// Clicking a country on the gospel map needs Mapbox TILES (WebGL), which headless
@ -27,7 +27,7 @@ import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const A = join(__dirname, '..', '..', '..', 'app', 'doxa-maps', 'gospel-resources', 'assets', 'tables');
const P = join(__dirname, '..', '..', '..', 'app-profiles', 'gospel-resources', 'data');
const P = join(__dirname, '..', '..', '..', 'plugin-designs', 'gospel-resources', 'data');
const load = (p) => JSON.parse(readFileSync(p, 'utf8'));
const countries = (() => { const d = load(join(P, 'by-country.json')).countries; return Array.isArray(d) ? d : Object.values(d); })();

View file

@ -47,7 +47,7 @@
* A failure here exits non-zero WITHOUT writing output. Stale honest data beats
* fresh data from an instrument nobody proved was working.
*
* Usage: node app-profiles/gospel-resources/scripts/verify-gospel-links.mjs [--limit N] [--out <path>]
* Usage: node plugin-designs/gospel-resources/scripts/verify-gospel-links.mjs [--limit N] [--out <path>]
*/
import { readFileSync, writeFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'

View file

@ -1,5 +1,5 @@
/**
* app-profiles/key-cities/index.js — bundle entry for KEY CITIES.
* plugin-designs/key-cities/index.js — bundle entry for KEY CITIES.
*
* Registers ONE custom-element tag:
* <key-cities-map profile-config='{"profile":"cities-list"}'></key-cities-map>

View file

@ -1,6 +1,6 @@
// adjacency-proof.mjs — slice 09b-2 §1(d), can-fail
//
// node app-profiles/my-upg-100-list/data/adjacency-proof.mjs
// node plugin-designs/my-upg-100-list/data/adjacency-proof.mjs
//
// Proves:
// 1. graph builds; every admin has >=1 neighbour EXCEPT declared island-orphans

View file

@ -5,7 +5,7 @@
## Origin (read-only mirror — the RICHER copy)
```
<earlier-bundler-checkout>/app-profiles/my-upg-100-list/data/clusters.json
<earlier-bundler-checkout>/plugin-designs/my-upg-100-list/data/clusters.json
```
- **md5:** `3040eadf7d8526de396618b15d7e97f0`

View file

@ -1,7 +1,7 @@
#!/usr/bin/env node
// color-isolation-proof.mjs — slice 09b toolC §1(d) · THE LOAD-BEARING GATE
//
// node app-profiles/my-upg-100-list/data/color-isolation-proof.mjs
// node plugin-designs/my-upg-100-list/data/color-isolation-proof.mjs
//
// Proves, can-fail, that recoloring a my-upg-100-list zone has ZERO blast radius:
//

View file

@ -1,8 +1,8 @@
#!/usr/bin/env node
// generate-region-shades.mjs — slice 09b-9 (color-strategy) §1(b) · the SHADE GENERATOR
//
// node app-profiles/my-upg-100-list/data/generate-region-shades.mjs # write non-Asia families
// node app-profiles/my-upg-100-list/data/generate-region-shades.mjs --dry # print ladder, write nothing
// node plugin-designs/my-upg-100-list/data/generate-region-shades.mjs # write non-Asia families
// node plugin-designs/my-upg-100-list/data/generate-region-shades.mjs --dry # print ladder, write nothing
//
// WHAT THIS DOES (qa-color-strategy.md §1b + IDEATION shade sketch):
// For each NON-ASIA WAGF region family, give every child zone a DISTINCT SHADE of

View file

@ -1,6 +1,6 @@
// oceania-color-proof.mjs — slice 09b-oceania-color-fix §1(4) the missed gate
//
// node app-profiles/my-upg-100-list/data/oceania-color-proof.mjs
// node plugin-designs/my-upg-100-list/data/oceania-color-proof.mjs
//
// THE BUG (09b-oceania-color-fix): oceania-indonesia (union of wagf_region:oceania
// + country_code:IDN → 18 ISO3) painted MULTIPLE colors on the map until clicked.

View file

@ -1,10 +1,10 @@
#!/usr/bin/env node
// palette-preview.mjs — slice 09b-9b §1(a–c) · the CYBER/neon palette designer + gate
//
// node app-profiles/my-upg-100-list/data/palette-preview.mjs # design + assert + (optional) shoot
// node app-profiles/my-upg-100-list/data/palette-preview.mjs --assert # math-only (no firefox, no write)
// node app-profiles/my-upg-100-list/data/palette-preview.mjs --write # write zone-colors.json then assert
// node app-profiles/my-upg-100-list/data/palette-preview.mjs --shoot # write + assert + firefox dark/light shots
// node plugin-designs/my-upg-100-list/data/palette-preview.mjs # design + assert + (optional) shoot
// node plugin-designs/my-upg-100-list/data/palette-preview.mjs --assert # math-only (no firefox, no write)
// node plugin-designs/my-upg-100-list/data/palette-preview.mjs --write # write zone-colors.json then assert
// node plugin-designs/my-upg-100-list/data/palette-preview.mjs --shoot # write + assert + firefox dark/light shots
//
// WHAT THIS DOES (qa-cyber-palette.md §1 a–c):
// (a) Designs a curated RGB-gaming-keyboard NEON palette for all 33 zones and
@ -32,7 +32,7 @@ const SHOTS = join(HERE, 'visual-gate-shots');
const SETTER = join(HERE, 'set-zone-color.mjs');
mkdirSync(SHOTS, { recursive: true });
const URL = process.env.UPG_URL || 'http://localhost:5180/app-profiles/my-upg-100-list/';
const URL = process.env.UPG_URL || 'http://localhost:5180/plugin-designs/my-upg-100-list/';
const EL = 'my-upg-100-list-map';
// ───────────────────────────────────────────────────────────────────────────

View file

@ -1,6 +1,6 @@
// pip-proof.mjs — slice 09b-1 §1(d) can-fail proof for the PIP admin-stamp.
//
// node app-profiles/my-upg-100-list/data/pip-proof.mjs
// node plugin-designs/my-upg-100-list/data/pip-proof.mjs
//
// Asserts (exits 1 on any FAIL — a broken PIP yields 0 stamped -> FAILs):
// 1. >=99% of IND+CHN PGs got a REAL admin1 (report stamped/total + fallback).

View file

@ -1,6 +1,6 @@
// pip-stamp.mjs — slice 09b-1 (Asia repartition GATE)
//
// node app-profiles/my-upg-100-list/data/pip-stamp.mjs
// node plugin-designs/my-upg-100-list/data/pip-stamp.mjs
//
// Point-in-polygon stamps every PG to its TRUE admin-1.
// For each record with finite lat/lng, ray-cast [lng,lat] against every

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# proof.sh — full runnable proof (one command) · updated 09b-6 §1(d) L13
#
# bash app-profiles/my-upg-100-list/data/proof.sh
# bash plugin-designs/my-upg-100-list/data/proof.sh
#
# Proves, can-fail:
# 1. rop1 / full re-cache present (or re-fetch) §1h / 04u §1a
@ -18,7 +18,7 @@
# RESULT line is green ONLY when every proof genuinely passed.
set -uo pipefail
cd "$(dirname "$0")/../../.." # -> repo root
PROFILE=app-profiles/my-upg-100-list
PROFILE=plugin-designs/my-upg-100-list
fail=0
# run_proof "<label>" <tail-lines> <node-script> [args...]

View file

@ -1,7 +1,7 @@
#!/usr/bin/env node
// region-hue-proof.mjs — slice 09b-9 (color-strategy) §1(d) · the CAN-FAIL CT GATE
//
// node app-profiles/my-upg-100-list/data/region-hue-proof.mjs
// node plugin-designs/my-upg-100-list/data/region-hue-proof.mjs
//
// Proves the tapestry zones inherit their parent WAGF region hue (data-copy only),
// scoped to the families recolored THIS pass (Asia is DEFERRED to the Asia agent):

View file

@ -1,6 +1,6 @@
// retag-adm1.mjs — slice 09b-7 §1(a) reproducible ADM1 listId re-tag
//
// node app-profiles/my-upg-100-list/data/retag-adm1.mjs
// node plugin-designs/my-upg-100-list/data/retag-adm1.mjs
//
// THE BUG (09b-6 honest visual gate): gb-IND-ADM1.geojson + gb-CHN-ADM1.geojson
// features carry STALE `listId` tags from the OLD pre-09b-3/09b-4 partition. The

View file

@ -1,8 +1,8 @@
#!/usr/bin/env node
// set-zone-color.mjs — slice 09b toolC §1(c) · the override WRITER
//
// node app-profiles/my-upg-100-list/data/set-zone-color.mjs <zoneId> <hex>
// node app-profiles/my-upg-100-list/data/set-zone-color.mjs <zoneId> --clear
// node plugin-designs/my-upg-100-list/data/set-zone-color.mjs <zoneId> <hex>
// node plugin-designs/my-upg-100-list/data/set-zone-color.mjs <zoneId> --clear
//
// Upserts (or clears) one zone color in ./zone-colors.json. This is what toolA
// (color picker) + toolB (apply) call. Reproducible: stable formatting, keys

View file

@ -1,9 +1,9 @@
// source-boundaries.mjs — THE boundary source. One script, every level, one schema.
//
// node app-profiles/my-upg-100-list/data/source-boundaries.mjs # ADM0 + ADM1
// node app-profiles/my-upg-100-list/data/source-boundaries.mjs --level ADM0
// node app-profiles/my-upg-100-list/data/source-boundaries.mjs --iso NPL,BGD --force
// node app-profiles/my-upg-100-list/data/source-boundaries.mjs --resimplify [tol]
// node plugin-designs/my-upg-100-list/data/source-boundaries.mjs # ADM0 + ADM1
// node plugin-designs/my-upg-100-list/data/source-boundaries.mjs --level ADM0
// node plugin-designs/my-upg-100-list/data/source-boundaries.mjs --iso NPL,BGD --force
// node plugin-designs/my-upg-100-list/data/source-boundaries.mjs --resimplify [tol]
//
// Supersedes source-adm0.mjs (04t) and source-adm0-uncovered.mjs (09b-5), which
// fetched the SAME provider through two code paths with two property schemas and
@ -404,8 +404,8 @@ async function buildAdm1({ force }) {
if (results.some((r) => r.action === 'written')) {
console.log(`\n[adm1] listId was written from the clusters files above. Now re-run the`);
console.log(`[adm1] canonical re-tag so the tag has ONE authority and is proven:`);
console.log(`[adm1] node app-profiles/my-upg-100-list/data/retag-adm1.mjs`);
console.log(`[adm1] node app-profiles/my-upg-100-list/data/zone-resolves-proof.mjs`);
console.log(`[adm1] node plugin-designs/my-upg-100-list/data/retag-adm1.mjs`);
console.log(`[adm1] node plugin-designs/my-upg-100-list/data/zone-resolves-proof.mjs`);
}
return results;
}

View file

@ -1,6 +1,6 @@
// visual-gate.mjs — slice 09b-6 §1(g) Playwright-FIREFOX visual gate
//
// node app-profiles/my-upg-100-list/data/visual-gate.mjs
// node plugin-designs/my-upg-100-list/data/visual-gate.mjs
//
// FIREFOX ONLY (hard rule: always Firefox, never Chrome). NEVER
// chromium / mcp-chrome. Loads the live dev bundle, asserts the tapestry paints
@ -31,7 +31,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const SHOTS = join(__dirname, 'visual-gate-shots');
mkdirSync(SHOTS, { recursive: true });
const URL = process.env.UPG_URL || 'http://localhost:5180/app-profiles/my-upg-100-list/';
const URL = process.env.UPG_URL || 'http://localhost:5180/plugin-designs/my-upg-100-list/';
const EL = 'my-upg-100-list-map';
let fails = 0;

View file

@ -1,6 +1,6 @@
// zone-resolves-proof.mjs — slice 09b-7 §1(b) the assertion 09b-3/09b-4 MISSED (L17)
//
// node app-profiles/my-upg-100-list/data/zone-resolves-proof.mjs
// node plugin-designs/my-upg-100-list/data/zone-resolves-proof.mjs
//
// L17: 09b-3/09b-4 rewrote the cluster SELECTORS but never re-tagged the ADM1
// geojson `listId`, so 4 zones (odisha, assam, yunnan, tibet) resolved 0 polygons

View file

@ -1,5 +1,5 @@
/**
* app-profiles/my-upg-100-list/index.js
* plugin-designs/my-upg-100-list/index.js
*
* Bundle entry — registers the my-upg-100-list web component.
*

View file

@ -3,7 +3,7 @@
*
* Vite multi-entry build target. Emits app/research-dashboard.js.
*
* Mirrors app-profiles/doxa-research-map/index.js (same ProfileLoader plumbing).
* Mirrors plugin-designs/doxa-research-map/index.js (same ProfileLoader plumbing).
* Registers a SINGLE tag (UNCHANGED from the original profile — only the bundle folder
* is now plural so it can host N dashboard profiles tomorrow):
* - <research-dashboard-map> config-driven people-group research dashboard

View file

@ -1,7 +1,7 @@
/**
* useDashboardLayout.js — reusable drag-resize + persistence engine for the CMV dashboard grid.
*
* A dashboard shell (see app-profiles/doxa-research-dashboards/research-dashboards.vue) is a CSS
* A dashboard shell (see plugin-designs/doxa-research-dashboards/research-dashboards.vue) is a CSS
* grid whose three flexible tracks are driven by CSS custom properties:
* --facet-w (left facet-rail column width) default 248px
* --rail-w (right charts-rail column width) default 312px

View file

@ -9,7 +9,7 @@ data) plus a `src/` sandbox laid out for your real code.
| Zone | What | Edit it? |
|---|---|---|
| **`library/`** (imported as `@map/…`) | shared, reusable building blocks — map, legends, toolbars, composables, color + API registries | **No — reuse only.** A change here can break every other map. |
| **`app-profiles/<your-map>/src/`** | your private sandbox — screens, colors, API wiring, components, utils | **Yes — build anything.** Nothing here leaks to another map. |
| **`plugin-designs/<your-map>/src/`** | your private sandbox — screens, colors, API wiring, components, utils | **Yes — build anything.** Nothing here leaks to another map. |
Rule of thumb: **local by default.** Code only moves into `library/` once a *second* map genuinely
needs it — an expert move, not a first move. To see everything `library/` offers, read
@ -17,14 +17,14 @@ needs it — an expert move, not a first move. To see everything `library/` offe
## Build a new map — the steps
1. **Copy this folder** → `app-profiles/<your-map>/`, then change **exactly four things**:
1. **Copy this folder** → `plugin-designs/<your-map>/`, then change **exactly four things**:
- the **folder name** (= bundle name = output file `<your-map>.js`),
- the **tag** in `index.js` (`customElements.define('<your-map>', …)`),
- the **tag + `profile` strings** in `index.html` (the staging page must mount your tag),
- the **`.vue` filenames** — rename `template-map-a.vue` to your screen's name and delete
`template-map-b.vue` if you only want one screen (filename = the `profile` name).
Nothing else. Check it: `bun run dev` → your map renders at
`http://localhost:5173/app-profiles/<your-map>/index.html`.
`http://localhost:5173/plugin-designs/<your-map>/index.html`.
2. **List what your map needs** — data, colors, controls (legend, search, share, fullscreen…).
3. **Pick pieces from `../../docs/reference.md`** and import them by reference (`@map/…`) — never
copy library code into your folder.
@ -72,6 +72,6 @@ dropping another flat `.vue` next to these and rebuilding — no config to edit.
## See also
- `src/README.md` — sandbox layout · `../README.md` — the app-profiles authoring guide
- `src/README.md` — sandbox layout · `../README.md` — the plugin-designs authoring guide
- `../../docs/tutorial.md` — 3 zones + 3 rules in one page · `../../docs/reference.md` — the `@map/…` menu
- `../doxa-simple-map/index.js` — a fuller, real-map reference entry

View file

@ -1,5 +1,5 @@
/**
* app-profiles/template-bundle/index.js
* plugin-designs/template-bundle/index.js
*
* Bundle entry — the minimal, copy-me reference for a MULTI-PROFILE bundle.
*

View file

@ -7,7 +7,7 @@
* @bundle template-bundle
* @element template-bundle
*
* This file sits DIRECTLY in the bundle folder (app-profiles/template-bundle/),
* This file sits DIRECTLY in the bundle folder (plugin-designs/template-bundle/),
* NOT in a nested profiles/ subfolder. Its filename without `.vue` —
* `template-map-a` — is the profile name you request from the host page:
*

File diff suppressed because one or more lines are too long

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <all-people-groups> custom element. -->
<script src="../all-people-groups.js" integrity="sha384-reLYgfQVOBo4SK15sXYteULZYBjIgAz4ZbSLWISMupJJbuYOeUNix30fEd9NL8sV" crossorigin="anonymous"></script>
<script src="../all-people-groups.js" integrity="sha384-j5Ypx+EI9DGlTClzgLs1R8wAnmTnkQ7HgAhHrxyGoVoHDMTlFVY7slU6JNicn77T" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"all-people-groups","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"all-people-groups","scriptUrl":"./all-people-groups/all-people-groups.js","output":"doxa-maps/all-people-groups/all-people-groups.js","integrity":"sha384-reLYgfQVOBo4SK15sXYteULZYBjIgAz4ZbSLWISMupJJbuYOeUNix30fEd9NL8sV","elements":["all-people-groups"],"profiles":[{"profile":"all-people-groups","file":"all-people-groups.vue","description":"All 12,379 people groups (peoplegroups.org) in four colors — reached,","element":"all-people-groups","iframeUrl":"./all-people-groups/all-people-groups/index.html","scriptUrl":"./all-people-groups/all-people-groups.js","embed":{"iframe":"\u003ciframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-reLYgfQVOBo4SK15sXYteULZYBjIgAz4ZbSLWISMupJJbuYOeUNix30fEd9NL8sV\" crossorigin=\"anonymous\">\u003c/script>\n\u003call-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/all-people-groups>"},"bundle":"all-people-groups"}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"all-people-groups","scriptUrl":"./all-people-groups/all-people-groups.js","output":"doxa-maps/all-people-groups/all-people-groups.js","integrity":"sha384-j5Ypx+EI9DGlTClzgLs1R8wAnmTnkQ7HgAhHrxyGoVoHDMTlFVY7slU6JNicn77T","elements":["all-people-groups"],"profiles":[{"profile":"all-people-groups","file":"all-people-groups.vue","description":"All 12,379 people groups (peoplegroups.org) in four colors — reached,","element":"all-people-groups","iframeUrl":"./all-people-groups/all-people-groups/index.html","scriptUrl":"./all-people-groups/all-people-groups.js","embed":{"iframe":"\u003ciframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-j5Ypx+EI9DGlTClzgLs1R8wAnmTnkQ7HgAhHrxyGoVoHDMTlFVY7slU6JNicn77T\" crossorigin=\"anonymous\">\u003c/script>\n\u003call-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/all-people-groups>"},"bundle":"all-people-groups"}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*
@ -143,7 +143,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
* (paste once, stored locally in the browser, never in files/links unless you
* use ?tk= on purpose). Maps must Just Work after one paste.
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
* - Auto-generated: whatever plugin-designs exist, the tree detects and shows them.
*
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
@ -229,7 +229,7 @@ function buildFlat() {
kind: kind === 'nested' ? 'nested' : 'profile',
label: p.profile,
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
edit: 'plugin-designs/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})

File diff suppressed because one or more lines are too long

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"cities-internet","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-PAqYfQxuqlTa0wMwATfLtaJIeDnBhlHqijIMW4ILMtpy4VOXubmV19L8iiu3LI/u\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-gPdqkrwfePG3KfeoSRe6X6laMtcVdN7tqsJXm15WUA9UWWhxW9j99P+gyKe/dl8Z\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*
@ -143,7 +143,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
* (paste once, stored locally in the browser, never in files/links unless you
* use ?tk= on purpose). Maps must Just Work after one paste.
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
* - Auto-generated: whatever plugin-designs exist, the tree detects and shows them.
*
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
@ -229,7 +229,7 @@ function buildFlat() {
kind: kind === 'nested' ? 'nested' : 'profile',
label: p.profile,
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
edit: 'plugin-designs/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-55+Ke4uM9UOSblliojP0Q/PjsXxJ4eeO2Pq8zSIC6m4wPq18T2QCSTyM1NMBz0bo" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-X9SslLiOGDh8ASpogPFqNUI34WlQHj7pMHc2hPWMQxnuZfUk67ZFpnBa17eRFLcD" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-55+Ke4uM9UOSblliojP0Q/PjsXxJ4eeO2Pq8zSIC6m4wPq18T2QCSTyM1NMBz0bo" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-X9SslLiOGDh8ASpogPFqNUI34WlQHj7pMHc2hPWMQxnuZfUk67ZFpnBa17eRFLcD" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"country-posters","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-55+Ke4uM9UOSblliojP0Q/PjsXxJ4eeO2Pq8zSIC6m4wPq18T2QCSTyM1NMBz0bo","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-55+Ke4uM9UOSblliojP0Q/PjsXxJ4eeO2Pq8zSIC6m4wPq18T2QCSTyM1NMBz0bo\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-55+Ke4uM9UOSblliojP0Q/PjsXxJ4eeO2Pq8zSIC6m4wPq18T2QCSTyM1NMBz0bo\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"plugin-designs/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-X9SslLiOGDh8ASpogPFqNUI34WlQHj7pMHc2hPWMQxnuZfUk67ZFpnBa17eRFLcD","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-X9SslLiOGDh8ASpogPFqNUI34WlQHj7pMHc2hPWMQxnuZfUk67ZFpnBa17eRFLcD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-X9SslLiOGDh8ASpogPFqNUI34WlQHj7pMHc2hPWMQxnuZfUk67ZFpnBa17eRFLcD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*
@ -143,7 +143,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
* (paste once, stored locally in the browser, never in files/links unless you
* use ?tk= on purpose). Maps must Just Work after one paste.
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
* - Auto-generated: whatever plugin-designs exist, the tree detects and shows them.
*
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
@ -229,7 +229,7 @@ function buildFlat() {
kind: kind === 'nested' ? 'nested' : 'profile',
label: p.profile,
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
edit: 'plugin-designs/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})

Some files were not shown because too many files have changed in this diff Show more