doxa-maps/README.md
doxa-maps-88655c70 ea53a2e19a emit a real desktop binary and .deb for deploy method 5
tauri build produced target/release/doxa-maps-desktop (65 MB ELF) and a 58 MB
.deb (Package: doxa-maps, Depends: libwebkit2gtk-4.1-0 libgtk-3-0). tauri info
reports the environment fully green and correctly detects frontendDist
../public, Vue.js, Vite.

The web tree is compiled INTO the binary rather than shipped as files, which is
why the .deb holds only 6 entries - confirmed by 19 distinct embedded
doxa-maps/... asset paths in the ELF and by size (a logic-free shell is 65 MB
only because the 132 MB public/ tree is compressed inside it).

bundle.targets narrowed from all to deb: rpmbuild and linuxdeploy are absent
here, so all would fail two of three formats at the END of a ~6-minute release
build.

The CLI is a devDependency (@tauri-apps/cli, prebuilt binary) rather than
cargo install tauri-cli, which compiles from source.

Status stays BUILT, NOT SHOT. The GUI launch is unverified because this box is
headless - the binary panics in tao with 'Failed to initialize GTK', the
absence of a display rather than a defect, and no Xvfb is installed.
2026-09-21 21:36:18 +00:00

308 lines
18 KiB
Markdown

# 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](#cap-1--single-element) |
| 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](#cap-2--preset-element-prop-table) |
| 3 | **Monolithic / multi-view** | A whole research map, all in one `root.vue`. | `doxa-research-map` | [§1](#cap-3--monolithic-research-map) |
| 4 | **Composite element** (nested-element SPA) | A composite element that nests other elements and wires them via props/events. | `my-upg-100-list` | [§1](#cap-4--composite-element) |
| 5 | **App shell** (nested-SPA) | The full doxa-maps app — every plugin in one deep-linkable page. | the doxa-maps SPA | [§1](#cap-5--app-shell--the-doxa-maps-app) |
### The 5 deployment methods
| # | Method | Status | Section |
|---|--------|--------|---------|
| 1 | **iframe embed** on any site | required | [§2](#deploy-1--iframe-embed) |
| 2 | **Mobile app** (Android / Capacitor) | required | [§2](#deploy-2--mobile-app) |
| 3 | **Direct-JS / research dashboard** (props + events + SRI) | required | [§2](#deploy-3--research-dashboard-direct-js) |
| 4 | **WordPress** (PHP plugin: shortcode + Gutenberg block) | built, not yet shot | [§3](#3-cli-commands--installs-per-deploy-method) |
| 5 | **Desktop** (Tauri) | built, not shot (binary + `.deb` emitted; launch unverified) | [§3](#3-cli-commands--installs-per-deploy-method) |
### CLI commands + what to install
Install **[Bun](https://bun.sh)** once, then:
```bash
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](./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](./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.json` is typed
> `parameterized` (cap-2) by `discover.mjs`, because one profile then renders as *many* preset pages.
> `all-people-groups` ships no instances file, so `manifest.json` types it `single`. That file — not the
> folder's apparent simplicity — is what decides the tier.
![Capability 1 — a single-element plugin (all-people-groups)](docs/readme-images/cap1-single-element.png)
### 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 |
|--------|------------|----------|
| ![Prayer map](docs/readme-images/cap2-prayer.png) | ![Engagement map](docs/readme-images/cap2-engagement.png) | ![Adoption map](docs/readme-images/cap2-adoption.png) |
*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.
![Capability 3 — the monolithic research map](docs/readme-images/cap3-monolithic-research.png)
### 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`) |
|-----------------------------------|-----------------------|-----------------------------|
| ![UPG-100 composite](docs/readme-images/cap4-upg100-mfe.png) | ![UPG-100 list](docs/readme-images/cap4-upg100-list.png) | ![UPG-100 poster wall](docs/readme-images/cap4-upg100-poster-wall.png) |
### 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.
![Capability 5 — doxa-maps-spa, the curated product SPA](docs/readme-images/cap5-doxa-maps-spa.png)
*`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.
![Capability 5 — plugin-studio, the auto-generated developer gallery](docs/readme-images/cap5-plugin-studio.png)
---
## 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 1 — a map running in an iframe embed on a real site](docs/readme-images/deploy1-iframe.png)
### 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.
<p align="center">
<img src="docs/readme-images/deploy2-mobile-spa.png" alt="Deploy 2 — the doxa-maps SPA at phone viewport (the Capacitor mobile-app surface)" width="280">
</p>
### 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.
![Deploy 3 — the unified research dashboard (map + people-groups, connected via props/events)](docs/readme-images/deploy3-research-dashboard.png)
---
## 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](./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>/` and `bun run build`
> puts 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-maps` segment is the **output tree name**, set by `name` in
> `vite.doxa-maps-build-config.json` — it is not your plugin's name and not this repo's folder name.
> (This line previously read `public/<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](#5-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](./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](./docs/README-SECTION-3-cli-commands.md)** | the full per-method CLI + install matrix |
| **[docs/why.md](./docs/why.md)** | 2 min — the problem, the idea, one diagram |
| **[docs/tutorial.md](./docs/tutorial.md)** | build your first map, step by step |
| **[docs/how-to.md](./docs/how-to.md)** | task recipes (embed, data, mobile) |
| **[docs/README-longform.md](./docs/README-longform.md)** | the original story-first README (the "why maps" manifesto) |
| **[docs/architecture/](./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.