Driver 2026-09-21: 'all the maps need to have their mapbox token wired properly according to the references - all the maps are not working yet until that fix', then 'all maps can use the pk from the api, no problem - if they want to set a mapbox sk they can'. Every leaf already implemented a 6-rung token ladder; rungs 1-3 are used directly with NO network call. On nuxtbase.app rungs 4-6 all failed: the same-origin /api/maps/token 404s and the absolute doxa.life fallback sends ZERO CORS headers. Rungs 1-3 were unset, hence 'No Mapbox access token provided' at dashboard-plus-grid.js:605. Setting build-config.token (rung 3) inlines the key into all 26 leaves, so a map works on ANY host with no endpoint and no CORS. This required splitting the hygiene rule, which lumped pk with sk/tk. A pk. is a PUBLIC key: designed to ship in client code and protected by URL restrictions, not secrecy (measured - the same pk. serves tiles 200 with a doxa.life Referer and 403s with nuxtbase.app). Flagging it made rung 3 unbuildable, since that gate runs inside build:unlocked. sk. and tk. remain flagged and were negative-tested: sk CAUGHT, tk CAUGHT, GitHub PAT CAUGHT, pk allowed. Verified: full build ALL GUARDS PASS, hygiene clean, SRI consistent across 58 leaf pages; 26 of 26 leaves carry the baked pk; and three maps mount with NO token supplied by the harness - 0 'no token' errors. |
||
|---|---|---|
| .forgejo/workflows | ||
| .github/workflows | ||
| capacitor/android | ||
| contributing | ||
| data | ||
| data-private | ||
| docs | ||
| internal | ||
| library | ||
| plugin-designs | ||
| public | ||
| serving | ||
| skills/make-a-doxa-map | ||
| src-tauri | ||
| wordpress | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| bun.lock | ||
| capacitor.config.json | ||
| CONTRIBUTING.md | ||
| index.html | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| vite.config.js | ||
| vite.doxa-maps-build-config.json | ||
doxa-maps — one build, maps that embed everywhere
A Vite bundler that turns each map into a self-contained web component. One bun run build produces a
doxa-maps/ folder you drop on any host — iframe it, script-embed it, ship it as a mobile app, or open the
whole thing as one SPA. Same code, every screen.
This README is a show, don't tell index. Skim the three tables, then jump to the section you need.
Index
The 5 plugin (app) types — from simplest to full app
| # | Capability | What it is | Example plugin | Section |
|---|---|---|---|---|
| 1 | Single element | One folder → one bundle → one custom element. The lightest tier. | doxa-vision-card |
§1 |
| 2 | Preset element (prop-table) | ONE root.vue rendered N ways by preset props — one plugin, many maps. |
doxa-simple-map (prayer · engagement · adoption) |
§1 |
| 3 | Monolithic / multi-view | A whole research map, all in one root.vue. |
doxa-research-map |
§1 |
| 4 | Composite element (nested-element SPA) | A composite element that nests other elements and wires them via props/events. | my-upg-100-list |
§1 |
| 5 | App shell (nested-SPA) | The full doxa-maps app — every plugin in one deep-linkable page. | the doxa-maps SPA | §1 |
The 5 deployment methods
| # | Method | Status | Section |
|---|---|---|---|
| 1 | iframe embed on any site | required | §2 |
| 2 | Mobile app (Android / Capacitor) | required | §2 |
| 3 | Direct-JS / research dashboard (props + events + SRI) | required | §2 |
| 4 | WordPress (PHP plugin: shortcode + Gutenberg block) | built, not yet shot | §3 |
| 5 | Desktop (Tauri) | built, not shot (binary + .deb emitted; launch unverified) |
§3 |
CLI commands + what to install
Install Bun once, then:
bun install # download node_modules (one time)
bun run dev # live plugin gallery at http://localhost:5173
bun run build # build every plugin-designs/<map>/ → public/doxa-maps/
bun run serve # preview the built tree
bun run phone # build → Capacitor → Android APK on a connected device
The full command matrix (per deploy method, with prereqs like the Android SDK / JDK for mobile) lives in §3 → docs/README-SECTION-3-cli-commands.md.
The data caveat: maps ship without data. Being JAMstack, each fetches its people-group data from an API at runtime, and its Mapbox token from a token endpoint (never baked into the bundle). That is what keeps them embeddable everywhere. See docs/how-to.md.
1. Plugin types — one screenshot each
Every image below is a real map from this repo's bun run build output (public/doxa-maps/), captured in a
headless browser.
CAP 1 — Single element
One folder → one bundle → one custom element. The lightest tier: plugin-designs/all-people-groups/ —
one .vue, no preset table, one embeddable element showing every people group on one map.
What makes it cap-1 rather than cap-2: a bundle with a
<profile>.instances.jsonis typedparameterized(cap-2) bydiscover.mjs, because one profile then renders as many preset pages.all-people-groupsships no instances file, somanifest.jsontypes itsingle. That file — not the folder's apparent simplicity — is what decides the tier.
CAP 2 — Preset element (prop-table)
One root.vue, many maps. plugin-designs/doxa-simple-map/ is a single parameterized profile. A preset
prop-table (simple-map.instances.json) renders it three ways — prayer, engagement, adoption —
each a standalone embeddable page. Same code, three maps.
| Prayer | Engagement | Adoption |
|---|---|---|
![]() |
![]() |
![]() |
All three come from the same doxa-simple-map plugin-design folder — differentiated only by preset props.
CAP 3 — Monolithic research map
A whole research map, all from one root.vue. plugin-designs/doxa-research-map/ — the monolithic tier.
CAP 4 — Composite element
A composite element that nests lower-tier elements and wires them via props/events. plugin-designs/my-upg-100-list/ —
the UPG-100 list plugin. It ships three profiles, each its own embeddable leaf: the composite
(upg-100-mfe), the list (upg-100-list), and the poster wall (poster-wall).
Composite element (upg-100-mfe) |
List (upg-100-list) |
Poster wall (poster-wall) |
|---|---|---|
![]() |
![]() |
![]() |
CAP 5 — App shell / the doxa-maps app
The top tier: every plugin in one deep-linkable single-page app — public/index.html, generated by the
build. A vertical-tab sidebar groups every plugin by capability (CAP 5 → 1); clicking a row mounts that
plugin. This same SPA is the Capacitor mobile-app entry (see deploy 2).
plugin-studio is itself a CAP-5 plugin, AUTO-GENERATED by the Vite build step. You do not hand-write
it — the build discovers every folder in plugin-designs/, bundles each, and generates the studio SPA that
composes them (a nested-spa-single-spa / app-shell tier plugin). Add a plugin-design folder, run the
build, and it appears in the studio automatically. (doxa-maps-spa is the curated product SPA; plugin-studio
is the auto-generated gallery of everything built — same cap-5 tier, both built by bun run build.)
Two shared layouts back the cap-5 tier (in library/layouts/, brand-neutral + reusable by any plugin):
VerticalTabsLayout— one element visible at a time (the sidebar-tab shell above). Lazy-mounts each tab's element on first open.StackLayout— the opposite: all elements STACKED top-to-bottom at natural height, so the host page scrolls through them (the doxa-life / nuxtinator page-flow model). Group two elements and embed the pair.
The two cap-5 apps, side by side. They are different products at the same tier and the distinction
matters when you embed one: doxa-maps-spa is what an END USER gets, plugin-studio is what a DEVELOPER
reviews. (Captioning a studio shot as the product SPA is a documented mistake — see /plugin-designer.)
doxa-maps-spa — the curated PRODUCT SPA. Vertical tabs grouped by audience, not by capability:
Prayer Mobilization (Big Picture · UPG-100 List · Prayer Progress · Gospel Resources) → Research
(Cities & Internet · Research Map) → Experimental. Tab 1 is the cap-4 Big Picture composite.
plugin-studio — the AUTO-GENERATED developer gallery. The same cap-5 tier, but the sidebar groups every
built plugin by capability (CAP 5 → 1) so an author can review the whole catalogue after a build.
2. Deployment methods — one screenshot each
The exact commands + install prereqs for each are in §3. These shots show what each method looks like live.
Deploy 1 — iframe embed
The simplest. One <iframe> line drops a map into an "embed section" on any partner site — no build step on
their site. Below: the prayer map running in an iframe inside a church's missions page.
Deploy 2 — mobile app
Same code, native app. bun run phone wraps the SPA into an Android app via Capacitor. Below: the doxa-maps
SPA at a phone viewport — the exact web output that cap:sync copies into the APK.
Deploy 3 — research dashboard (direct-JS)
Props + events + SRI. plugin-designs/research-dashboard/ embeds via a direct <script src> (with a
Subresource-Integrity hash) so the map talks to the surrounding page — a coordinated-views dashboard where the
map, the facet rail, and the people-groups table brush-and-link over one store.
3. CLI commands + installs (per deploy method)
The complete, copy-paste command matrix — every deploy method mapped to its install prereqs and exact
commands, grounded in this branch's package.json — lives in its own doc:
→ docs/README-SECTION-3-cli-commands.md
Quick reference:
| # | Deploy method | Install first | Command(s) |
|---|---|---|---|
| 1 | iframe embed | Bun | bun install → bun run build → bun run serve (or drop public/doxa-maps/ on any host) |
| 2 | Mobile (Android) | Bun alone for check:mobile; Android SDK + adb + JDK 17 for the APK |
bun run check:mobile (contract check, no toolchain) → bun run phone (full APK path) |
| 3 | Direct-JS / dashboard | Bun | bun run build, then paste the <script src> + integrity snippet from manifest.json. Proven same-root AND on a foreign host page: the snippet now carries assetsBase + the MAP_APP_API_URL global; fill the YOUR_* placeholders |
| 4 | WordPress | Bun | bun run build, then install the wordpress/doxa-maps-embed/ PHP plugin (shortcode + Gutenberg block) (built + contract-proven via bun run check:wp-embed; not yet shot on a live WP install) |
| 5 | Desktop (Tauri) | Rust toolchain + Tauri CLI | bun run check:tauri (contract check, no toolchain needed) · cargo tauri build for the binary (built, not shot — no binary emitted here) |
Every build writes public/doxa-maps/manifest.json — a machine-readable index carrying the ready-to-paste
iframe and <script src> + SRI embed snippet for every map.
4. Repository layout (hand-off map)
Two directories matter to a plugin author: library/ (the shared, high-bar reusable toolkit) and
plugin-designs/ (your free zone — one folder per plugin). The build reads plugin-designs/, pulls
what it needs from library/, and emits each plugin to public/doxa-maps/<bundle>/<bundle>.js
plus a standalone page per profile.
That is the whole contract: write your app inside
plugin-designs/<your-map>/andbun run buildputs it in the output tree — no per-plugin wiring, no registration step. Drop a new folder in and the next build picks it up.The
doxa-mapssegment is the output tree name, set bynameinvite.doxa-maps-build-config.json— it is not your plugin's name and not this repo's folder name. (This line previously readpublic/<name>/, which conflated the two and sent a reader looking in a directory that does not exist.)
library/ — the shared reusable toolkit
Imported by ANY plugin. High blast radius — see Library governance before editing.
library/
├── index.js ← the library's public surface (what plugins import)
├── ProfileLoader.vue ← the runtime loader that mounts a plugin profile
├── layouts/ ← reusable shell layouts (VerticalTabsLayout, app-wrapper-material, pane-wrapper)
├── components/ ← reusable Vue components (map controls, search, tables, charts, legends, detail panels)
├── composables/ ← reusable logic hooks (useMapData, useMapClustering, useGeocoderSearch, legends/, poster/)
├── colors/ ← brand-neutral color scales + registry (prayer-progress, adoption, engagement, religion, language-family)
├── constants/ ← shared defaults (map defaults, zoom, poster sizes)
├── stores/ ← shared Pinia-style stores (dataStore, mapStore, uiStore)
├── i18n/ ← shared translations (index + locales/)
├── api/ ← data-source layer (DataSourceManager, source registry, sources.json)
├── data/ ← shared static reference data (ISO tables, language-family lookups)
├── utils/ ← shared helpers (clustering, MST/network utils, data-source helpers)
├── styles/ ← shared style registry
└── scripts/ ← shared build/gen scripts (posters, research)
plugin-designs/ — your free zone (one folder per plugin)
You write your app HERE. Each folder is its own application; on bun run build it compiles to
public/doxa-maps/<bundle>/<bundle>.js and becomes its own deployable custom element (via
defineCustomElement).
plugin-designs/
├── template-bundle/ ← copy this to start a new plugin
├── doxa-vision-card/ ← CAP 1: single element (one .vue + index.js)
│ ├── doxa-vision-card.vue
│ └── index.js
├── doxa-simple-map/ ← CAP 2: preset element (one root.vue + *.instances.json → many maps)
├── doxa-research-map/ ← CAP 3: monolithic multi-view map
├── my-upg-100-list/ ← CAP 4: composite element (nests lower-tier elements)
├── doxa-maps-spa/ ← CAP 5: app-shell (the full doxa-maps app, one deep-linkable SPA)
│ ├── doxa-maps-spa.vue
│ ├── doxa-maps-spa.meta.json
│ ├── doxa-maps-spa.instances.json
│ └── index.js
├── research-dashboard/ ← direct-JS dashboard (props/events/SRI; deploy method 3)
├── big-picture/ country-posters/ key-cities/ cities-internet/ … ← more example plugins
How a plugin-design works: create a folder in plugin-designs/<name>/ (copy template-bundle/),
write your .vue (plus an optional <name>.meta.json to declare its type and <name>.instances.json for
preset rows), and run bun run build. The build auto-detects the folder, compiles it into a self-contained
element at public/doxa-maps/<bundle>/, and adds its embed snippets to public/doxa-maps/manifest.json. Your plugin
imports shared pieces from library/ but ships as its own deployable artifact.
Deploy honesty ledger
This README does not over-claim. Per the verified ledger (2026-09-19):
| Count | State | Methods |
|---|---|---|
| 2 | proven live | iframe embed · direct-JS embed |
| 3 | built, not yet shot | mobile (Capacitor scaffolded, APK never emitted) · WordPress (wordpress/doxa-maps-embed/*.php written, install never captured) · desktop (src-tauri/; 65 MB binary + 58 MB .deb emitted — launch unverified, headless box) |
| 0 | aspirational | — none. Method 5 moved to built-not-shot on 2026-09-21. |
All 5 capabilities are proven by a built bundle. Every deploy method is now at least scaffolded; three are not yet shot (no APK, no live WP install, no desktop binary).
5. Library governance
The two zones above have very different rules. Follow them so one author's change never breaks another's map.
plugin-designs/ = free zone. Agentically engineer your maps here without asking. A plugin operates as
its own application, isolated to its own folder, and builds to its own output — nothing you do here can break
another author's plugin.
library/ = HIGH BAR. It is multi-tenant: reused by many maps and authors. Editing it is a
breaking-change risk to everyone. From the canonical anti-pattern
(AP-fill-shared-library-with-non-reusable-components):
Agentically engineer your maps IN plugin-designs — a plugin operates as its own application that builds to the output folder on the build step. Only contribute to the shared library if you're experienced AND the component is genuinely reusable + parameterized so OTHER maps (not just yours) can use it. Treat every library edit as breaking-change-risk to other tenants — a HIGH BAR. Never widen or casually add.
Why it matters: a non-reusable component in library/ teaches other agents + future authors that it's OK to
follow the anti-pattern, polluting a high-blast-radius surface over time — and any library edit can make a
breaking change that breaks other tenants' maps. Keep all map-specific, non-parameterized content in your
plugin-design; contribute to library/ only when it is genuinely reusable and parameterized.
More docs
| Read this | When |
|---|---|
| docs/develop-a-plugin-and-see-it-live.md | ★ START HERE — the golden path, zero → a plugin live, in 6 steps |
| docs/README-SECTION-3-cli-commands.md | the full per-method CLI + install matrix |
| docs/why.md | 2 min — the problem, the idea, one diagram |
| docs/tutorial.md | build your first map, step by step |
| docs/how-to.md | task recipes (embed, data, mobile) |
| docs/README-longform.md | the original story-first README (the "why maps" manifesto) |
| docs/architecture/ | the deep end — embeddability, app-wrappers, file map |
Bottom line: maps should be open source, run everywhere, and multiply. This bundler makes each map a self-contained file that embeds on your site, your partners' sites, WordPress, and mobile — from one build.











