I landed glFor as a local copy of pplr-data-maps' helper and flagged the
duplication as a decision on the record rather than drift. It did not need to be
a decision at all: library/composables/useMapProvider.js already exports
isMapboxMap(map) — the same per-instance test, documented for the same reason
("works even on dual-engine pages where both globals are loaded").
So the copy is gone and the part that could drift is delegated. What remains in
the plugin is the namespace choice, one expression with nowhere to drift to.
When exp-shepherd's engineFor(map) lands, that line goes too.
WHAT IS PROVEN, and what is not, because the browser slots have been saturated
for fifteen minutes and RULE 3 says gates green before committing. This is a pure
expression substitution, so its logic is provable without a GPU:
_chk-glfor-equivalence.mjs stubs the globals, builds BOTH implementations, and
compares their decisions across every engine combination — mapbox map with both
globals, maplibre map with both, either alone, and the case the whole fix exists
for: a MapLibre map with no Mapbox global at all. Identical in all of them.
That establishes the refactor cannot have changed the decision, so the pin-click
integration result already measured still applies. It does NOT re-run that click;
the browser re-proof is queued and will run when a slot frees.
check:sri PASS, composite freshness PASS.
|
||
|---|---|---|
| .github/workflows | ||
| data | ||
| deployment | ||
| documentation | ||
| internal | ||
| library | ||
| plugin-designs | ||
| public | ||
| serving | ||
| skills/make-a-doxa-map | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| bun.lock | ||
| capacitor.config.json | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| vite.config.js | ||
| vite.doxa-maps-build-config.json | ||
Goal
Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His paths straight.
Isaiah 40:3 “...Prepare the way for the LORD in the wilderness; make a straight highway for our God in the desert."
dx-maps — one build, maps that embed everywhere
Write a map once in plugin-designs/<your-map>/, run one build, and drop it on any site —
iframe, script embed, mobile app — or open every map at once as one app. Five plugin
capabilities, five deployment methods, one shared library, one build.
| What dx-maps set out to do | Proof |
|---|---|
| One build → one folder you can drop on any host | bun run check:drop |
| Every map embeddable from a copy-paste snippet | deployment methods · bun run check:embed-targets |
| Every map interoperable through props and events | bun run verify:interop |
| One app nests every map — and each stays embeddable on its own | capability 5 · bun run check:composite-reg |
| 5 capabilities × 5 deployment methods | bun run verify:5-caps |
| Shared library, private plugins — reuse without breaking each other | bun run check:library-index |
One agent per plugin, one shepherd, one working main |
agent swarm |
Click any ▸ to read more. Everything below is folded so you can scroll the pictures first.
The 7 production maps
Seven maps DX uses today — each one a plugin you can embed on its own, and all seven together in the Research Gallery.
![]() Big Picture the whole research dashboard: filters, map, charts, people groups |
![]() Simple Research the research map with the people-groups grid, made for phones |
![]() Regions every region and its countries, then a dashboard per region |
![]() UPG-100 List prayer lists of 100 people groups, with posters to print |
![]() Prayer Progress where prayer is happening, and where it is still needed |
![]() Cities with Internet internet access where unreached people groups live |
![]() Gospel Resources which people groups have scripture, film or audio — and which have nothing |
![]() Research Gallery all seven in one app — capability 5 |
On a phone
![]() Simple Research |
![]() Regions |
![]() UPG-100 List |
![]() Prayer Progress |
![]() Cities with Internet |
![]() Gospel Resources |
![]() Research Gallery |
Big Picture a desktop dashboard by design — on a phone, Simple Research is the dashboard, and the Research Gallery opens on it. |
Every capability ships every way
| Capability | Example | iframe | Mobile app | JavaScript embed | WordPress / Disciple Tools | Desktop |
|---|---|---|---|---|---|---|
| 5 — app shell | Research Gallery | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| 4 — composite | UPG-100 List | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| 3 — multi-view | research map | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| 2 — presets | Prayer Progress | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| 1 — single element | all people groups | ✅ | ✅ | ✅ | 🧪 | 🧪 |
✅ works today · 🧪 proven, not yet in production. Every plugin builds into the same folder, and that folder is what all five methods ship — so a capability never limits where a plugin can go.
The Experimental shelf
New plugins the Research Gallery picks up by itself — each builds and embeds like any other, and graduates to a named tab when it's ready.
![]() Wall of People Groups all 2,107 Doxa people groups as photo cards on one zoomable prayer wall, with a pray dialog |
![]() Prayer Walk Planner plan a prayer walk through a zone, open it full screen from any embed, and print a walking map that stays sharp when zoomed |
Also on the shelf: UPG neighbor, all people groups, country posters, the vision card, and the PPLR data maps.
How one build becomes five ways to ship — the "map cruncher"
map · map · map · map · map · map · map many maps, each once its own codebase
\ \ | | | / /
+----- plugin-designs/ ------+ one folder per plugin — the free zone
|
library/ (@map) shared, reusable pieces — the high bar
|
ONE build step
|
public/doxa-maps/ one drop-anywhere folder + manifest.json
/ | \ \ iframe mobile JS embed WordPress desktop
The 5 plugin capabilities
A capability is how much a plugin nests, from one element (1) to an app of apps (5). Every screenshot is a real plugin from this repo's build.
Capability 5 — app shell: the Research Gallery
One plugin that holds every map as a tab — and each tab is still a plugin you can embed on its own.
More about capability 5 — the app shell
An app shell is the outer frame of a whole app: a rail of tabs down the side, a header, and navigation. Each tab loads a real plugin from a lower capability, only when you open it, so the app stays fast. Nothing is copied into the shell: when a plugin improves, the shell shows the improvement on the next build.
- Where it lives:
plugin-designs/doxa-maps-spa/— one element,<doxa-maps-spa>. - Changing the tabs: the order and grouping are one list,
NAV_MODELindoxa-maps-spa.vue. - New plugins show up by themselves: any plugin that isn't placed in a section lands on the
folded Experimental shelf, read from the built
manifest.json— no code to edit. - A second app shell ships too: the Plugin Studio (below), which holds every plugin for developers.
What the Research Gallery is made of — seven production maps, an Experimental shelf, all five capabilities in one plugin
Research Gallery <doxa-maps-spa> capability 5
├─ Dashboards
│ ├─ Big Picture dashboard-plus-grid capability 4
│ │ ├─ research dashboard research-dashboard capability 3
│ │ └─ people-groups grid people-groups-grid capability 2
│ ├─ Simple Research research-map-plus-grid capability 4
│ │ ├─ research map doxa-research-map capability 3
│ │ └─ people-groups grid people-groups-grid capability 2
│ └─ Regions regions-mfe capability 4
│ ├─ regions list regions-list capability 1
│ └─ region dashboards region-dashboards capability 1
├─ Prayer Mobilization
│ ├─ UPG-100 List upg-100-mfe capability 4
│ │ ├─ list upg-100-list capability 1
│ │ └─ poster wall poster-wall capability 1
│ └─ Prayer Progress doxa-simple-map capability 2
├─ Gospel Research
│ ├─ Cities with Internet connectivity-map capability 1
│ └─ Gospel Resources resources-map capability 1
└─ Experimental (a folded shelf, filled automatically from manifest.json)
├─ prayer walk planner prayer-walk-planner capability 1
├─ UPG neighbor upg-neighbor capability 3
├─ all people groups all-people-groups capability 1
├─ country posters country-posters capability 4
├─ vision card doxa-vision-card capability 2
├─ PPLR data maps pplr-data-maps capability 3
└─ Wall of People Groups wall-of-people-groups capability 3
Capability 4 — composite: plugins wired together
Several plugins glued into one, talking to each other — and each piece still embeddable alone.
| List (capability 1, embeddable alone) | Poster wall (capability 1, embeddable alone) |
|---|---|
![]() |
![]() |
More about capability 4 — composites
A composite takes plugins that already exist and places them together — side by side, stacked, or as views you switch between — and ships the set as one new element. The pieces talk through props (settings passed in) and events (messages sent out): click a region in one piece and another piece updates. Nothing is copied, so each piece can still be embedded on its own.
- Example:
plugin-designs/my-upg-100-list/— the UPG-100 list and its poster wall, joined intoupg-100-mfe. Big Picture, Simple Research and Regions are composites too. - How to declare one: a
<name>.meta.jsonbeside it with"type": "nested-elements-mfe"and a"composes"list of the plugins inside. - The layouts that do the placing:
VerticalTabsLayoutandStackLayoutinlibrary/layouts/.
Capability 3 — multi-view: many screens, one element
One plugin with several screens inside — tabs you switch between.
More about capability 3 — multi-view
All the screens live in one plugin, so they share code and data and switch instantly. The trade-off: you embed the whole thing — you can't embed just one of its tabs. When you need that, split the tabs into separate plugins and join them with a composite (capability 4).
- Example:
plugin-designs/doxa-research-map/— Prayer, Engagement, People Groups, Regions, Languages and Religions in one map. - How to declare one: a
<name>.meta.jsonwith"type": "monolithic".
Capability 2 — presets: one plugin, many maps
The same map code, several ready-made versions — each with its own page and embed code.
| Prayer | Engagement | Adoption |
|---|---|---|
![]() |
![]() |
![]() |
More about capability 2 — presets
You write one map, then list its versions in a small table: a name and the settings for each. The build turns every row into its own standalone page and embed snippet — same code, different map. Adding a version means adding a row, not writing code.
- Example:
plugin-designs/doxa-simple-map/— Prayer, Engagement and Adoption from one map. - How to make one: add
<name>.instances.jsonbeside the plugin:{ "instances": [{ "id": "prayer", "title": "Prayer", "props": { … } }] }.
Capability 1 — single element
One folder, one map, one tag you can paste into any page.
More about capability 1 — single element
The simplest plugin, and where every map starts: a folder with three files — the map's .vue, an
index.js that registers its tag, and an index.html for previewing it. The build turns it into
one custom HTML tag, like <all-people-groups>, that works on any web page.
- Example:
plugin-designs/all-people-groups/— every people group on one map. - How to make one: copy
plugin-designs/template-bundle/toplugin-designs/<your-map>/and rename it. The studio finds it automatically. - Which plugin is the example of each capability is generated, not remembered: documentation/reference-capability-exemplars.md.
The 5 deployment methods
1. iframe — the prayer map on any site ✅ production
Paste one line of HTML and the map appears — on any website, no coding.
More about method 1 — iframe
An iframe is a window in your page that shows another page — here, the map's own page. It's the
easiest way to share a map: it works on any site that accepts HTML, and the map keeps its own look.
Every plugin's exact snippet is embed.iframe in public/doxa-maps/manifest.json:
<iframe src="https://YOUR_HOST/doxa-maps/doxa-simple-map/doxa-simple-map/prayer/index.html?utm_source=YOUR_SOURCE"
width="100%" height="600" style="border:none;border-radius:12px;" allowfullscreen></iframe>
Host the built public/doxa-maps/ folder anywhere (it works from any address), then point the
iframe at it.
2. Mobile app — UPG-100 on a phone ✅ built
The same maps, wrapped as a phone app.

More about method 2 — mobile app
Capacitor wraps the built maps into an Android app — the same files, no new code. Shown here: the UPG-100 prayer lists and posters at phone size, each poster with its own print options.
bun run check:mobile # checks the app is wired up correctly — needs no Android tools
bun run phone # builds the app and installs it on a phone or emulator
bun run phone needs the Android SDK, JDK 17 and adb. Only Android is set up; iOS is not yet.
3. JavaScript embed — the Research Gallery on dx-life ✅ production
Two lines of code put a whole app inside your own page — here, the Research Gallery on dx-life.
More about method 3 — JavaScript embed
Instead of a window onto another page, the map becomes part of your page, so your page can talk
to it. The dx-life research page replaced its separate research map, list and regions dashboards
with this one embed of the Research Gallery. The exact snippet is embed.script in manifest.json;
copy its integrity hash from there, because it changes with every build:
<script>window.MAP_APP_API_URL="https://pray.doxa.life";</script>
<script src="https://YOUR_HOST/doxa-maps/doxa-maps-spa/doxa-maps-spa.js"
integrity="sha384-…" crossorigin="anonymous"></script>
<doxa-maps-spa profile-config='{"profile":"doxa-maps-spa","tk":"YOUR_MAPBOX_TOKEN","utmSource":"YOUR_SOURCE","assetsBase":"https://YOUR_HOST/doxa-maps/assets"}'></doxa-maps-spa>
4. WordPress / Disciple Tools — 🧪 proven, not yet in production
We got a JAMstack map running inside WordPress and Disciple Tools.
More about method 4 — WordPress / Disciple Tools
It works, but it isn't yet a production feature of the map bundler. Two ways to try it:
- Easiest: paste a method 1 or method 3 snippet into a WordPress Custom HTML block.
- As a plugin: install
deployment/wordpress/doxa-maps-embed/inwp-content/plugins/. It adds a[doxa_map]shortcode and a block for the block editor.
bun run check:wp-embed confirms the plugin writes the right HTML, without needing WordPress
installed.
5. Desktop app (Tauri) — 🧪 proven, not yet in production
We got the JAMstack map app running as a desktop app.
More about method 5 — desktop app
Tauri wraps the same built maps into a desktop program: it produced a Linux
app and a .deb installer. It isn't in the dx bundler for production yet — and the Research Gallery
could become a strong desktop app.
bun run check:tauri # checks the desktop setup — needs no Rust tools
bun run tauri:build # builds the app — needs Rust, the Tauri CLI and webview libraries
WordPress and Tauri marks from Simple Icons (CC0); the trademarks belong to their owners.
Build it with an agent swarm
Many AI agents can build many plugins at once without breaking each other's work. Every plugin
has one agent building it, and one shepherd agent makes sure every agent does its job well. All the
plugins live in one project with one shared library, and changes are added to main — the one
official version — one at a time.
What problem does this solve? (in plain words)
All the maps here share one project, one library and one build step. That's what makes them work together, and it's also the danger: if one agent's change breaks the shared library, every map breaks at once. And when several agents work in the same project at the same time, they step on each other — two agents edit the same file, one change quietly erases another, or half-finished work lands in the version everyone else is using.
So the project works like a busy kitchen with one head chef:
- Each agent works in its own copy of the project — its own counter, so nobody bumps anybody.
- Before adding its work, each agent catches up: it pulls in everyone else's latest work and fixes any clashes with its own. The agent fixes the clash, not the next person.
- Only one agent adds work at a time. They wait in a line (the merge queue), the checks run,
and only then does the change go into
main.mainalways works. - A shepherd agent watches everyone: plugins built in the studio, shared parts really shared, and every change going through the line.
That's how many plugins get built at the same time, in one project, without the maps breaking.
The words, if you're new to git
| Word | In plain terms |
|---|---|
| repo | the project folder, with its full history |
| main | the one official version of the project that everyone builds from |
| worktree | a private copy of the project for one agent to work in |
| merge conflict | two changes touched the same lines; someone has to decide how to combine them |
| rebase | catching your work up with everyone else's latest before adding it |
| merge queue | the line agents wait in, so only one change goes into main at a time |
The commands (for the curious)
# one private copy (worktree) + one agent per plugin
git worktree add -b regions/work ../dx-maps-regions main
git worktree add -b upg100/work ../dx-maps-upg100 main
# one preview server per agent, each on its own port
bun x vite --port 5301 --strictPort
# add work through the line: one at a time, caught up first, checks must pass
flock /tmp/dx-maps-main.lock sh -c '
git fetch origin && git rebase origin/main &&
bun run gates:pre && git push origin HEAD:main'
One agent owns the full build (it rebuilds everyone's output); the others check just their own
plugin with BUNDLE=<name> bun x vite build. How to contribute, and the bar for shared code:
documentation/CONTRIBUTING.md. Diagram source:
delegation-model.svg.
The Plugin Studio — ★ where you work
Every plugin in one place, sorted by capability. Pick any plugin to run it live, fold open the plugins inside it, and copy its embed code. Tell your agent which plugins to group, and it builds a higher-capability plugin from them — that's how the Research Gallery was built.
Get it running
You need git, Bun and Node ≥ 18, plus a free
Mapbox public token (it starts with pk.) so the maps have a
background.
git clone https://git.nuxtbase.app/doxa-open/doxa-maps.git
cd doxa-maps
bun install --frozen-lockfile
cp .env.example .env # then paste your pk. token after VITE_MAPBOX_TOKEN=
bun run dev # → http://localhost:5173/ is the Plugin Studio
No build needed first — the built maps are already in the repo. To start a plugin, copy
plugin-designs/template-bundle/ to plugin-designs/<your-map>/; the studio finds it automatically.
Make changes to the studio itself
The studio is a plugin like any other: plugin-designs/plugin-studio/plugin-studio.vue.
-
Run
bun run devand open http://localhost:5173/ — that page is the studio. -
Edit
plugin-studio.vue(or ask your agent to). The page updates as you save. -
When it looks right, build just the studio and refresh its security hashes:
BUNDLE=plugin-studio bun x vite build bun run fix:sri -
Check it opens every plugin:
bun run smoke:studio(withbun run devrunning).
No Mapbox token? And other map settings
- No token: set
"provider": "maplibre"on any plugin (or add?provider=maplibreto a map's page) and it draws with MapLibre, which needs no key. - Built pages take a token from
?tk=pk.…in the address, from a token you paste once into the page, or from a token endpoint. - Every command and prerequisite, per deployment method: documentation/README-SECTION-3-cli-commands.md.
| Command | What it does |
|---|---|
bun run dev |
The Plugin Studio, updating as you save, at http://localhost:5173 |
BUNDLE=<name> bun x vite build |
Build one plugin only |
BUILD_ALLOW=1 bun run build |
Build everything into public/doxa-maps/ (~4 min; refuses without the flag, to protect a shared project) |
bun run serve |
Serve the built maps at http://localhost:4173 |
bun run gates:pre · bun run gates:post |
The checks that keep main working |
bun run shots |
Retake these screenshots (ONLY=<name> for one) |
Repository layout
doxa-maps/
├── plugin-designs/ YOUR maps — one folder per plugin (the free zone)
│ ├── doxa-maps-spa/ the Research Gallery (capability 5)
│ ├── plugin-studio/ the Plugin Studio (capability 5)
│ ├── my-upg-100-list/ UPG-100 List (capability 4)
│ ├── doxa-simple-map/ Prayer Progress and its presets (capability 2)
│ ├── all-people-groups/ a single map (capability 1)
│ └── template-bundle/ copy this to start a new plugin
│ ├── template-map-a.vue the map itself
│ ├── index.js registers its HTML tag
│ ├── index.html a preview page
│ └── src/ private pieces only this plugin uses
├── library/ the SHARED toolkit, imported as @map (the high bar)
│ ├── layouts/ VerticalTabsLayout, StackLayout — how composites compose
│ ├── components/ legends, map controls, search, posters, detail panels
│ ├── composables/ map instance, data, layers, shared helpers
│ ├── colors/ styles/ colour strategies and the house style
│ ├── i18n/locales/ the 11 languages
│ └── data/ api/ lookup tables and data sources
├── public/doxa-maps/ the BUILD OUTPUT — the drop-anywhere folder + manifest.json
├── deployment/ mobile (Capacitor) · WordPress · desktop (Tauri)
├── internal/ the build and its checks
└── documentation/ tutorial · how-to · reference · architecture
Where your code goes — a plugin, or the shared library?
- Write your app in
plugin-designs/<your-map>/. On the build step it becomes its own plugin in the output folder — its own embeddable app. Nothing you do there can break anyone else's map. - Only put something in
library/if it is genuinely reusable and parameterized — built so other maps can use it with their own settings. The library is shared by every map, so every edit there can break every map: it is a high bar. Keep anything specific to one map inside that map's own folder. - The full contribution guide: documentation/CONTRIBUTING.md · every reusable piece you can import: documentation/reference-library-index.md.
Maps should be open source, run everywhere, and multiply. One build makes each map a self-contained file for your site, your partners' sites and a phone. The Plugin Studio is where you compose them into something bigger.



























