doxa-maps/README.md
doxa-maps-88655c70 c46a9a04bd
Some checks are pending
build-gate / build (push) Waiting to run
README: lead with the objectives, showcase the Research Gallery, teach the agent swarm
- Opens with what dx-maps is for: seven objectives, each paired with the gate
  or section that proves it is met.
- New Research Gallery section: the cap-5 app shell and the tree of cap-4..1
  plugins it nests, each still independently embeddable.
- Plugin Studio: clone main (the old branch was 198 commits behind), a command
  table, and a fresh studio screenshot (the old one showed a retired nav and
  tofu icons).
- Agent swarm: renamed to industry terms (trunk-based development, merge queue,
  maintainer), a laptop recipe (worktrees, strict ports, one builder, flock as
  the queue), and a redrawn diagram whose inner ring is an actual queue with
  one gate. SVG source committed next to the PNG.
- MapLibre is now described as it is: a one-prop, token-free engine.
- Removed internal credential/push instructions (AGENT-BRIEF.md covers agents).
- documentation/: fixed 19 dead links left by the docs/ move, removed internal
  references, and listed the working docs in the index.
- readme-shots: ONLY=<name> retakes a single image.
2026-09-24 18:34:02 +00:00

28 KiB
Raw Blame History

dx-maps — one build, maps that embed everywhere

The map cruncher. Many hand-built maps go in; one shared library, one repo and one build come out — and every map fans out to five deployment targets from the same artifacts.

 map · map · map · map · map · map · map        (7 production maps, once 7 codebases)
   \    \    |    |    |    /    /
    +----- plugin-designs/ ------+              one folder per plugin, free zone
                 |
           library/ (@map)                      shared, parameterized, high-bar zone
                 |
            ONE bun run build
                 |
          doxa-maps/  (drop-anywhere tree: bundles + leaf pages + manifest.json)
           /     |      \        \       \
       iframe  JS-embed  mobile  WordPress  desktop
       (proven) (proven)  (built)  (built)   (built)

A Vite bundler that turns each map into a self-contained web component. Write a map in plugin-designs/<your-map>/, run one build, and get a folder you can drop on any host — iframe it, script-embed it, wrap it as a mobile app, or open every map at once as one app.


What dx-maps is for — and how each objective is met

Maps multiply a prayer movement only when they can live on every partner's site, not just one. dx-maps exists to make that cheap. It set out to meet seven objectives; each one is met by something you can open or run, not by a claim.

# Objective How it is met See it / check it
1 One build → one folder. Seven maps that were seven codebases become one repo and one build. bun run build writes a single drop-anywhere tree, public/doxa-maps/, with a manifest.json describing every bundle. bun run check:drop · bun run check:settled
2 Every map is embeddable. Any map, on any site, with a copy-paste snippet. Each bundle is one custom element; manifest.json ships an iframe snippet and an SRI-pinned <script> snippet for it. Deployment methods · bun run check:embed-targets
3 Every map is interoperable. Maps talk to each other through props and events. Composites wire their children with props, events and shared layouts — no copy-pasted code. bun run verify:interop
4 One app nests every map — and each stays independently embeddable. The Research Gallery is a single capability-5 plugin that nests all seven production maps. The Research Gallery · bun run check:composite-reg
5 5 capabilities × 5 deployment methods. Anything you build, at any tier, ships everywhere. A plugin's tier is read from its files; every tier builds to the same tree, and the tree feeds all five methods. Capabilities · bun run verify:5-caps
6 A clean shared/private boundary. Reuse without breaking each other. library/ holds shared, parameterized pieces (generated index); anything map-specific stays in its own plugin-designs/ folder. reference-library-index.md · bun run check:library-index
7 Many builders, one green main. The repo is built to be worked by a swarm of agents at once. One agent per plugin, one worktree each, and every change landed through a merge queue. Build it with an agent swarm

Plus one practical objective: a token-free option. Mapbox is the default engine; setting a single prop, provider: "maplibre", swaps in MapLibre so a partner running many embeds needs no Mapbox key. bun run check:provider-switch proves the switch really changes the engine.

Contents

  1. The Research Gallery — the showcase: five capabilities in one plugin.
  2. The 5 plugin capabilities — what you can build, most advanced first.
  3. The 5 deployment methods — where a plugin can run.
  4. The Plugin Studio — ★ where you work, and every command to get it running. Start here if you are new.
  5. Build it with an agent swarm — how to run many agents on one laptop without collisions.

The Research Gallery (plugin-designs/doxa-maps-spa/) is the product a user opens. It is one capability-5 plugin — one custom element, <doxa-maps-spa> — and every tab inside it is a real, independently embeddable plugin from a lower tier. Nothing is copied into it and nothing is framed: each tab loads its own bundle and mounts that bundle's own custom element.

Capability 5 — the Research Gallery app shell (doxa-maps-spa)

This is how five different capabilities compose into one:

Research Gallery                          <doxa-maps-spa>          capability 5 — app shell
│
├─ Dashboards
│  ├─ Big Picture                         dashboard-plus-grid      capability 4 — composite
│  │   ├─ research dashboard              research-dashboard       capability 3 — monolithic
│  │   └─ people-groups grid              people-groups-grid       capability 2 — prop-table
│  ├─ Simple Research                     research-map-plus-grid   capability 4 — composite
│  │   ├─ research map                    doxa-research-map        capability 3 — monolithic
│  │   └─ people-groups grid              people-groups-grid       capability 2 — prop-table
│  └─ Regions                             regions-mfe              capability 4 — composite
│      ├─ regions list                    regions-list             capability 1 — single
│      └─ region dashboards               region-dashboards        capability 1 — single
├─ Prayer Mobilization
│  ├─ UPG-100 List                        upg-100-mfe              capability 4 — composite
│  │   ├─ list                            upg-100-list             capability 1 — single
│  │   └─ poster wall                     poster-wall              capability 1 — single
│  └─ Prayer Progress                     doxa-simple-map (prayer) capability 2 — prop-table preset
├─ Gospel Research
│  ├─ Cities with Internet                connectivity-map         capability 1 — single
│  └─ Gospel Resources                    resources-map            capability 1 — single
└─ Experimental (folder)                  every other bundle, discovered automatically from manifest.json

What that buys you:

  • Seven production maps, one URL. The three sections above are the seven curated tabs.
  • Every tab is still a standalone embed. Want only the UPG-100 list on a partner site? Embed upg-100-mfe — or just its upg-100-list leaf. Taking the whole app apart costs nothing.
  • Proving the whole proves the parts. When the Research Gallery was embedded cross-origin on a foreign host page (see deployment), every nested plugin rode along.
  • New plugins appear on their own. Any bundle not placed in a named section lands in Experimental, read from the built manifest.json. A new folder in plugin-designs/ shows up with zero edits to the shell.

The tab order and grouping live in one place, NAV_GROUPS in doxa-maps-spa.vue; bun run check:spa-tab-order guards it.


2. The 5 plugin capabilities — most advanced first

A "capability" is how much a plugin nests — not how hard it is to use. The tour below starts from the most advanced thing this repo produces (an app of apps) and walks down to the simplest (one element), because the top of the ladder is the point: every rung below it stays independently embeddable. Every screenshot is a real bundle from this repo's build output.

# Capability What it is Example bundle
5 Nested-SPA / app shell Many SPAs nested into one deep-linkable app. doxa-maps-spa
4 Nested-element SPA A composite that nests other plugins and wires them with props/events. big-picture
3 Monolithic element One root.vue nests many views internally → one element. doxa-research-map
2 Prop-table element One root.vue + a preset table → many maps from one plugin. doxa-simple-map
1 Single element One folder → one bundle → one custom element. all-people-groups

The tier is decided by the files, not by how simple a folder looks: an *.instances.json makes a bundle cap-2 however light it is, and a meta.json type declares tiers 3/4/5. The authoritative, generated answer lives in documentation/reference-capability-exemplars.md — cite that file rather than remembering.

Capability 5 — nested-SPA / app shell

Every plugin in one deep-linkable page. Two cap-5 apps ship, and the difference matters when you embed one: doxa-maps-spa (the Research Gallery, section 1) is what an end user gets; plugin-studio is what a developer works in.

plugin-studio — the developer studio, grouping every built plugin by capability (5 → 1). It is auto-generated: the build discovers every folder in plugin-designs/, so a new plugin appears in the studio without any wiring. This is section 4.

Capability 5 — the developer studio (plugin-studio)

Capability 4 — nested-element SPA

A composite that nests lower-tier plugins and wires them with props and events. Each inner plugin stays independently embeddable, and the composite is embeddable too. plugin-designs/my-upg-100-list/ ships three leaves:

Composite (upg-100-mfe) List (upg-100-list) Poster wall (poster-wall)
Capability 4 — the UPG-100 composite (my-upg-100-list) UPG-100 list leaf UPG-100 poster-wall leaf

Capability 3 — monolithic element

A whole multi-view map from one root.vue. The trade-off: you cannot embed just one of its tabs.

Capability 3 — the monolithic research map (doxa-research-map)

Capability 2 — prop-table element

One root.vue, many maps. plugin-designs/doxa-simple-map/ ships a preset prop-table (simple-map.instances.json) that renders it three ways — each a standalone embeddable page from the same code.

Prayer Engagement Adoption
Capability 2 — prayer preset (doxa-simple-map) Capability 2 — engagement preset (doxa-simple-map) Capability 2 — adoption preset (doxa-simple-map)

Capability 1 — single element

One folder, one .vue, one embeddable element. plugin-designs/all-people-groups/ shows every people group on one map.

Capability 1 — a single-element plugin (all-people-groups)


3. The 5 deployment methods

Ordered easiest to hardest. Status is stated honestly — "proven" means it has been run and seen, and nothing below is upgraded past what the files support.

# Method Status Command / install
1 iframe on any site ✅ proven bun run build, then paste the embed.iframe snippet from manifest.json
2 Mobile app (Capacitor, Android) 🛠️ built, not shot bun run check:mobile (no toolchain) · bun run phone needs Android SDK + JDK 17 + adb
3 JavaScript embed (<script src> + SRI) ✅ proven bun run build, then paste the embed.script + integrity snippet
4 WordPress plugin (shortcode + block) 🛠️ built, contract-proven, not shot bun run check:wp-embed, then install deployment/wordpress/doxa-maps-embed/
5 Desktop app (Tauri) 🛠️ built, not shot bun run check:tauri (no toolchain) · cargo tauri build needs Rust + webview libs

What "not shot" means: the code exists and its contract test passes, but nobody has captured it running on a real device or install. Each is blocked by a missing local toolchain, not by missing code — Android SDK for method 2, a live WordPress for method 4, and a GUI for method 5.

Methods 1 and 3 are the two proven-live paths. If you need a map on a site today, use one of those.

Both were certified cross-origin on 2026-09-24 — the entire Research Gallery, embedded on a foreign host page, every inner plugin riding along (prove the whole = prove the parts):

Method 1 — iframe on a host site Method 3 — JS embed on a host site
The cap-5 app inside a host page iframe The cap-5 app JS-embedded on a host page

For a JS embed on another site the host page sets window.MAP_TOKEN (nested elements read it) and the serving layer must send Access-Control-Allow-Origin: * — the tree is fetched cross-origin by manifest.json-composed surfaces.

The full per-method command matrix, with every prerequisite, is in documentation/README-SECTION-3-cli-commands.md.

The Mapbox token (read this before you file a bug)

The maps use Mapbox by default, which needs a pk. token. The token works differently in dev than in the built tree, and diagnosing one from the other is the most common way to lose an hour.

Where How it gets a token
Dev server (bun run dev) Baked into the page for you — from .env (VITE_MAPBOX_TOKEN) if present, otherwise from token in vite.doxa-maps-build-config.json.
Built pages (bun run serve, or hosted) Not baked. Each page resolves a token at runtime: ?tk=<TOKEN> on the URL, a token you paste into the page's own input box (kept in localStorage), or a token endpoint (tokenUrl).

So on a built leaf page you typically need:

http://localhost:4173/doxa-maps/demo-map/demo-map/index.html?tk=pk.your_token_here

To use your own token in dev, create a .env — it is gitignored, so it never reaches the repo:

printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env

If a basemap is an empty grey void while the pins still draw, that is the token failing — not a broken app.

Or skip the token entirely. A public pk. token can be restricted to specific URLs in the Mapbox dashboard, but a JAMstack embed still has nowhere to hide one. So the engine is one prop: set provider: "maplibre" on an instance (or add ?provider=maplibre to a leaf page) and the map renders on MapLibre with no Mapbox key. plugin-designs/doxa-research-map/research-map.instances.json ships a ready-made MapLibre instance. The trade-off is deliberate: under MapLibre the place-search bar is hidden, because the geocoder would still bill the Mapbox key the instance exists to avoid.


4. The Plugin Studio

★ This is where you work — and the keyword is INTEROPERABILITY.

The Plugin Studio is one place to view all of your plugins at once and edit all of them in one place using your agent. Every plugin appears in a single view, sorted highest capability down to lowest, so you can see the whole catalogue instead of hunting through folders.

That sorting is the point. Once you can see every plugin side by side, you can tell your agent which ones to group together — any number of them — and compose them into higher-capability plugins using props, events and layouts. Group cap-1 and cap-2 elements and you get a cap-4 nested-element SPA; group those and you get a cap-5 app shell. That is exactly how the Research Gallery was built.

Get it running

Four steps from nothing to every map in one place. You need git, Bun and Node ≥ 18.

# 1. Clone (the repo root IS the studio — there is no subfolder to cd into)
git clone https://git.nuxtbase.app/doxa-life/doxa-maps.git
cd doxa-maps

# 2. Install dependencies
bun install --frozen-lockfile

# 3. Optional — your own Mapbox token for dev (see section 3)
printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env

# 4. Open the Plugin Studio
bun run dev

Then open http://localhost:5173/ — that root URL is the Plugin Studio, with every plugin in one spot. (If port 5173 is busy Vite picks another one and prints it; read the line it logs.)

You do not need to build first. The build output in public/ is committed, so a fresh clone has plugins to show the moment the dev server starts.

Two things worth knowing:

  • / is the studio; /__gallery is the old staging gallery, kept but demoted to an explicit URL.
  • Edits hot-reload. The studio mounts the real plugin-studio custom element through its own index.js — the same path the built bundle takes — so what you see is the real element, not a preview.

Every command you need

Command What it does
bun run dev The Plugin Studio with hot reload, at http://localhost:5173
bun x vite --port 5299 --strictPort A dev server on your own port (see the swarm rules below)
BUNDLE=<name> bun x vite build Build one bundle, without cleaning anyone else's output
bun run build The full production tree in public/doxa-maps/ (~4 minutes, takes a lock)
bun run serve Serve the built tree at http://localhost:4173, the way production does
bun run check:settled Exit 0 = the built tree is safe to measure or publish right now
bun run gates:pre / bun run gates:post The static and post-build verification gates
bun run verify:5-caps · bun run verify:interop Prove the five capabilities, and cap-4 interoperability, in a real browser
bun run shots Regenerate the README screenshots

The full list — every deployment command with its prerequisites — is in documentation/README-SECTION-3-cli-commands.md.

Work with your agent

The studio's companion is your coding agent. The loop:

  1. Open the studio (bun run dev) and look at the catalogue, sorted cap-5 → cap-1.
  2. Tell your agent which plugins to group — e.g. "combine the research map and the people-groups grid into one composite."
  3. The agent writes a new folder in plugin-designs/<name>/, wiring the children with props, events and a layout from library/layouts/.
  4. Refresh the studio. The new, higher-capability plugin is there.

The whole contract is: write your app inside plugin-designs/<your-map>/. There is no registration step and no per-plugin wiring — the build discovers every folder under plugin-designs/. Copy plugin-designs/template-bundle/ to start a new one.

Two shared layouts do the composing, both in library/layouts/:

  • VerticalTabsLayout — one element visible at a time, lazy-mounted on first open.
  • StackLayout — the opposite: elements stacked top-to-bottom at natural height, so the page scrolls through them.

The production build

The dev server is for working; you do not need a build to see a change. When you want the deployable tree:

bun run build     # ~4 minutes; writes public/doxa-maps/
bun run serve     # serve the BUILT tree at http://localhost:4173

bun run build takes a lock: if another build is already running it fails fast rather than corrupting the shared output. Set BUILD_LOCK_WAIT=600 to queue behind it instead.

The lock protects a second WRITER. It does not protect a READER. Every build begins by deleting public/doxa-maps/, so while a build runs the tree is transiently near-empty — and anything reading it then gets a confident, wrong answer ("that bundle is missing"). Run bun run check:settled before any browser check, any publish, and any claim about what the tree contains, and grade it by exit code: like every gate here, it prints explanatory prose after its verdict, so | tail -1 will mislead you. Do not substitute a pgrep check — a process list samples an instant, while the lock knows the queue.


5. Build it with an agent swarm

This repo is designed for many builders working in parallel without stepping on each other — and the builders can be coding agents on your own laptop. It is trunk-based development with a merge queue, the same model as GitHub's merge queue, GitLab's merge trains and Rust's bors, arranged as concentric rings:

The delegation model: agents in their own worktrees send changes through one merge queue into main

  • main — the trunk. The one shared branch. Everything converges here, and it is always green.
  • Merge queue. Changes land one at a time, in order. Each change is rebased onto the latest main — its author resolves any conflicts — and lands only if the gates pass. A change that fails leaves the queue; the next one goes. Nothing writes to main except the queue.
  • Plugin Studio. One repo and one shared library/, with every plugin visible in one place, so each builder can see what already exists and reuse it instead of rebuilding it.
  • Agents. One agent per plugin, each in its own git worktree. Each owns its plugin end to end; its private components stay inside its own plugin-designs/ folder.
  • Maintainer. One reviewer — human or agent — keeps everyone honoring the model: plugins built in the studio, shared components genuinely shared, and every change going through the queue.

The effect is cooperative: every agent races to land working features, but the only way to land is through the queue, one change at a time, leaving main green.

The diagram's source is documentation/readme-images/src/delegation-model.svg.

Spin one up on your laptop

1. One worktree per agent, as sibling folders, so no two agents share a working directory:

git worktree add -b regions/work  ../dx-maps-regions  main
git worktree add -b upg100/work   ../dx-maps-upg100   main
git worktree add -b maintainer/work ../dx-maps-maintainer main
git worktree list

2. One agent per worktree, each owning one plugin. Start your coding agent in each folder and give it a single plugin-designs/<name>/ folder to own. The maintainer gets no plugin; it reviews what lands and pushes back when shared code is duplicated or a plugin reaches into another's folder.

3. One dev server per agent, each on its own port, always with --strictPort:

bun install --frozen-lockfile              # once per worktree
bun x vite --port 5301 --strictPort        # regions agent
bun x vite --port 5302 --strictPort        # upg100 agent

Without --strictPort, Vite silently moves a busy port to the next one — and several gates have 5173 written into them, so a drifted server leaves those gates reporting a confident wrong answer about somebody else's page. With --strictPort the duplicate exits non-zero instead.

4. One owner for the full build. Agents edit source and check their own bundle with BUNDLE=<name> bun x vite build. Only one agent runs bun run build, because every full build wipes the shared output tree first.

5. Land through the merge queue. On one machine, a file lock is a complete merge queue — agents wait their turn on it (flock ships with Linux; on macOS, brew install flock):

flock /tmp/dx-maps-main.lock sh -c '
  git fetch origin &&
  git rebase origin/main &&
  bun run gates:pre &&
  git push origin HEAD:main
'

The lock guarantees one landing at a time; the rebase makes the lander resolve every conflict; the gates keep main green. When several machines share one remote, use the hosting platform's merge queue instead (GitHub merge queue, GitLab merge trains), or protect main so only one builder at a time may push.

Agents joining this repo should also read AGENT-BRIEF.md.


Repository layout

doxa-maps/
├── plugin-designs/          YOUR maps live here — one folder per plugin (the FREE zone).
│   ├── doxa-maps-spa/       the Research Gallery, capability 5 (nests the production maps)
│   ├── plugin-studio/       the developer studio, capability 5 (nests every plugin)
│   ├── dashboard-plus-grid/ capability-4 composite: research dashboard + people-groups grid
│   ├── my-upg-100-list/     capability-4: UPG-100 list + poster wall
│   ├── doxa-simple-map/     capability-2: one root.vue, three preset maps
│   └── template-bundle/     copy this to start a new plugin
├── library/                 the SHARED zone (@map) — reusable, parameterized only.
│   ├── layouts/             VerticalTabsLayout, StackLayout — how composites compose
│   ├── components/          map controls, legends, search, poster, detail panels
│   ├── composables/         map instance, shadow styles, print-in-place, retry
│   └── styles/ colors/ data/  style registry, palettes, lookup tables
├── data/                    the public datasets the maps ship with
├── deployment/              Capacitor (mobile), WordPress plugin, Tauri (desktop)
├── documentation/           tutorial · how-to · reference · explanation + architecture
├── internal/                the build generator + the verification gates
└── public/doxa-maps/        the BUILD OUTPUT — the drop-anywhere tree (bundles, leaf pages,
                             manifest.json). What gets served and embedded.

plugin-designs/ is a free zone. Your plugin is its own application in its own folder, building to its own output. Nothing you do there can break another author's map.

library/ is a HIGH BAR. It is multi-tenant — reused by many maps and authors — so every edit is a breaking-change risk to everyone. Only contribute here when a component is genuinely reusable and parameterized, so other maps can use it. Keep map-specific, non-parameterized code in your own plugin-design. See documentation/contributing/.


More docs

Start at documentation/README.md — the index of everything below.

Read this When
documentation/develop-a-plugin-and-see-it-live.md ★ the golden path — zero to a plugin live, in 6 steps
documentation/README-SECTION-3-cli-commands.md the full per-method CLI + install matrix
documentation/reference-capability-exemplars.md which bundle is the capability-N example (generated)
documentation/reference-library-index.md the menu of reusable library/ pieces you can import
documentation/why.md 2 min — the problem, the idea, one diagram
documentation/tutorial.md build your first map, step by step
documentation/how-to.md task recipes (embed, data, mobile)
documentation/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. The Plugin Studio is where you compose them into something bigger; the Research Gallery is what that looks like when you do.