Everything the flights landed since the first snapshot: all 11 interface languages complete, the gospel-resources campaign (audio Scripture pins for 2,121 languages, the Deaf-gap completion, 3,603 resource links verified), the shared poster wall with print-in-place, prayer-walk sharing, and the 34-gate build pipeline. Tree rebuilt from source; SRI consistent; hygiene clean.
386 lines
21 KiB
Markdown
386 lines
21 KiB
Markdown
# doxa-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.
|
|
>
|
|
> ```text
|
|
> 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 SPA.
|
|
|
|
This README has three parts, in the order you need them:
|
|
|
|
1. **[The 5 plugin capabilities](#1-the-5-plugin-capabilities)** — what you can build, simplest to richest.
|
|
2. **[The 5 deployment methods](#2-the-5-deployment-methods)** — where a plugin can run.
|
|
3. **[The Plugin Studio](#3-the-plugin-studio)** — ★ where you *work*. Start here if you are new.
|
|
|
|
---
|
|
|
|
## 1. 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 (a whole 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](./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` is what an end user gets; `plugin-studio` is what a developer works in.**
|
|
|
|
*`doxa-maps-spa` — the curated product SPA*, with tabs grouped by audience.
|
|
|
|

|
|
|
|
*`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 3](#3-the-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 3 — monolithic element
|
|
|
|
A whole multi-view map from one `root.vue`. The trade-off: you cannot embed just one of its tabs.
|
|
|
|

|
|
|
|
### 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 1 — single element
|
|
|
|
One folder, one `.vue`, one embeddable element. `plugin-designs/all-people-groups/` shows every people
|
|
group on one map.
|
|
|
|

|
|
|
|
---
|
|
|
|
## 2. 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 (this box is headless) 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 capability-5 app, 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 |
|
|
|----------------------------------|-------------------------------------|
|
|
|  |  |
|
|
|
|
> 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](./documentation/README-SECTION-3-cli-commands.md)**.
|
|
|
|
### The Mapbox token (read this before you file a bug)
|
|
|
|
The maps use Mapbox, 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** (`.gitignore` lines 21-24), so it
|
|
never reaches the repo:
|
|
|
|
```bash
|
|
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.
|
|
|
|
**Why this is genuinely awkward:** the five deployment methods each handle the token differently, and a
|
|
JAMstack embed has nowhere safe to keep a secret. The answer is not to hide the token but to make hiding
|
|
it unnecessary: a public `pk.` token is restricted to specific URLs in the Mapbox dashboard. **MapLibre
|
|
is being pursued to remove the per-embed token requirement entirely — that work is IN PROGRESS, not
|
|
done** (`maplibre-gl` is a dependency and the page generator has a MapLibre path, but no plugin-design
|
|
runs on it yet).
|
|
|
|
---
|
|
|
|
## The repo at a glance
|
|
|
|
```text
|
|
doxa-maps/
|
|
├── plugin-designs/ YOUR maps live here — one folder per plugin (the FREE zone).
|
|
│ ├── doxa-maps-spa/ the capability-5 app (nests everything below)
|
|
│ ├── dashboard-plus-grid/ capability-4 composite: dashboard + people-groups grid
|
|
│ ├── my-upg-100-list/ capability-4: UPG-100 lists + poster wall + map
|
|
│ ├── 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.
|
|
│ │ A change here reaches EVERY bundle: high bar, see
|
|
│ │ documentation/contributing/.
|
|
│ ├── components/ map controls, legends, search, poster, detail panels
|
|
│ ├── composables/ useMapInstance, shadow styles, print-in-place, retry
|
|
│ └── styles/ colors/ data/ style registry, palettes, lookup tables
|
|
├── data/ the public datasets the maps ship with
|
|
├── 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 + embedded.
|
|
```
|
|
|
|
## 3. 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 what makes the studio the right place to build
|
|
map plugins, rather than a gallery you merely look at.
|
|
|
|
**Why this matters.** Without a working studio, a developer *sees the fruit but not the deeper
|
|
strategy* — they get the maps, but they cannot reproduce or extend them. The studio is what turns a set
|
|
of finished maps into something you can build on.
|
|
|
|
### Get it running
|
|
|
|
Four steps from nothing to every map in one place.
|
|
|
|
```bash
|
|
# 1. Clone (the repo root IS the studio — there is no subfolder to cd into).
|
|
# This host is PRIVATE: it will prompt for your the git forge username + token.
|
|
git clone --branch doxa-maps-plugin-studio https://your-git-host.example/doxa-maps.git
|
|
cd doxa-maps
|
|
|
|
# 2. Install dependencies (installs 206 packages; needs Node >= 18)
|
|
bun install --frozen-lockfile
|
|
|
|
# 3. Optional — your own Mapbox token for dev (see section 2)
|
|
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.
|
|
|
|
> **If step 1 fails with `remote: Unauthorized`,** the clone is not broken — `your-git-host.example` is a
|
|
> private self-hosted the git forge and anonymous cloning is refused. Ask for an account, then use your
|
|
> username plus a the git forge **access token** (not your password) when git prompts.
|
|
|
|
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.
|
|
|
|
### 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.
|
|
|
|
### Working fast: use the dev server, not the build
|
|
|
|
A full `bun run build` rebuilds every bundle and takes minutes. **You do not need it to see a
|
|
change.** The dev server hot-reloads:
|
|
|
|
```bash
|
|
bun run dev # http://localhost:5173
|
|
bun x vite --port 5299 --strictPort # any other port, when 5173 is taken
|
|
```
|
|
|
|
★ **If more than one of you is working in this repo at once, EVERY dev server must name its own
|
|
port, and must pass `--strictPort`.** Vite's default behaviour on a busy port is to silently move to
|
|
the next one — 5173 becomes 5174 — and five gates (`check-facet-changes-map`,
|
|
`plugin-gallery-smoke`, `smoke-studio`, `generate-gallery`, `check-scrim-covers-viewport`) have
|
|
`5173` written into them. A second server without `--strictPort` therefore leaves those gates
|
|
talking to somebody else's server, or to none, and reporting a confident wrong answer with no error.
|
|
With `--strictPort` the duplicate exits non-zero instead of drifting — verified: port taken → exit 1.
|
|
|
|
Reach for the full build only when you need the deployable tree, an SRI-consistent leaf page, or a
|
|
manifest. To check one bundle's compiled output without touching anyone else's:
|
|
|
|
```bash
|
|
BUNDLE=<name> bun x vite build # this bundle only; does NOT clean the tree
|
|
```
|
|
|
|
### Push as you go
|
|
|
|
Commit and push to your fork or branch as usual; small, frequently pushed commits are
|
|
easier to review and impossible to lose.
|
|
|
|
|
|
### The production build
|
|
|
|
The dev server is for working. When you want the deployable tree:
|
|
|
|
```bash
|
|
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.
|
|
|
|
### Before you MEASURE the built tree — `bun run check:settled`
|
|
|
|
```bash
|
|
bun run check:settled # exit 0 = safe to measure/publish; exit 1 = do not
|
|
```
|
|
|
|
**The lock protects a second WRITER. It does not protect a READER.** Every build begins with
|
|
`bun run clean`, which `rm -rf`s `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",
|
|
"the dashboard tab does not exist", "461 files were deleted". Those are not hypotheticals; each has
|
|
happened here, and one nearly produced a bug report against a correct change.
|
|
|
|
So run this gate before any browser check, any publish, and any claim about what the tree contains.
|
|
It reports `builders`, `waiters`, `churn(45s)`, `dirs`, `leaves` and `quietFor`, and **grade it by
|
|
exit code** — like every gate here, it prints teaching prose after its verdict, so `| tail -1` will
|
|
mislead you.
|
|
|
|
★ **Do not substitute a `pgrep` check for this.** A process list samples an INSTANT; the lock knows
|
|
the QUEUE. Between one queued build finishing and the next acquiring the lock there is a real window
|
|
where zero build processes exist and the tree is nonetheless about to be wiped — and a long quiet
|
|
period makes that window look *more* convincing, not less, because the queue is idle precisely
|
|
because it is between runs. Measured 2026-09-23: `pgrep` reported 0 builders while the gate reported
|
|
`builders=1 waiters=1`, and `ps` confirmed a build had started 17 seconds earlier.
|
|
|
|
If you are one of several agents working this repo at once, the stronger rule is simply: **do not run
|
|
`bun run build` at all.** Edit source, and let one owner run the single authoritative build. To
|
|
sanity-check one bundle without touching anyone else's output, use
|
|
`BUNDLE=<name> bun x vite build` — it does not clean the whole tree.
|
|
|
|
---
|
|
|
|
## Repository layout
|
|
|
|
Two directories matter to a plugin author:
|
|
|
|
```
|
|
plugin-designs/ ← YOUR FREE ZONE. One folder per plugin. Copy template-bundle/ to start.
|
|
library/ ← the SHARED toolkit (layouts, components, composables, colors, stores, i18n).
|
|
```
|
|
|
|
Everything else is supporting cast: `internal/` (build + gates), `public/` (committed build output),
|
|
`deployment/` (Capacitor, WordPress, Tauri), `documentation/`.
|
|
|
|
**`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. Never widen or casually add. Keep map-specific,
|
|
non-parameterized code in your own plugin-design.
|
|
|
|
---
|
|
|
|
## More docs
|
|
|
|
| Read this | When |
|
|
|---|---|
|
|
| **[documentation/develop-a-plugin-and-see-it-live.md](./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](./documentation/README-SECTION-3-cli-commands.md)** | the full per-method CLI + install matrix |
|
|
| **[documentation/reference-capability-exemplars.md](./documentation/reference-capability-exemplars.md)** | which bundle is the capability-N example (generated) |
|
|
| **[documentation/reference-library-index.md](./documentation/reference-library-index.md)** | the menu of reusable `library/` pieces you can import |
|
|
| **[documentation/why.md](./documentation/why.md)** | 2 min — the problem, the idea, one diagram |
|
|
| **[documentation/tutorial.md](./documentation/tutorial.md)** | build your first map, step by step |
|
|
| **[documentation/how-to.md](./documentation/how-to.md)** | task recipes (embed, data, mobile) |
|
|
| **[documentation/architecture/](./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.
|