JAMstack bundler for embeddable map applications
Find a file
doxa-maps-88655c70 cad913bc6f scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA
Method 5 was the last aspirational entry in the 5-deploy-method model: 0
tauri.conf.json, 0 Cargo.toml, 0 .rs. It is now scaffolded against the v2
config schema, mirroring the Capacitor pattern that already works.

frontendDist points at the SAME public/ tree Capacitor and the CDN serve, so
one build still feeds every consumer - now four instead of three. The crate
carries no application logic on purpose: the product is the web tree, this is
a window around it.

check:tauri is the analogue of cap:verify and exists for the same reason
capacitor.config.json warns about in capitals: a wrong frontendDist or a window
url that does not exist launches a BLANK WHITE WINDOW, not an error. It runs
with no Rust toolchain and no Tauri CLI, so CI can run it. Negative-tested five
ways - wrong frontendDist, missing window url, identifier drift from the
Capacitor appId, empty windows array, missing crate file - each exits 1, and
the config was restored byte-identical after every test.

Status stays BUILT, NOT SHOT. rustc parses main.rs (it reaches line 14 and
fails only on the uninstalled tauri crate, a dependency error not a syntax
one), but producing a binary needs the Tauri CLI plus system webview libs.
Also recorded the token/CORS caveat up front: a Tauri webview serves from
tauri://localhost, so the token endpoint must send CORS for that origin -
the same surprise Capacitor documents for https://localhost.
2026-09-21 21:06:04 +00:00
.forgejo/workflows generate the capability exemplar table so the answer stops being remembered 2026-09-21 12:38:50 +00:00
.github/workflows doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
capacitor/android doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
contributing Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
data Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
data-private Release governance: field-access data is private, never shipped 2026-09-16 04:34:33 +00:00
docs correct Step 2: three files are required, not exactly four 2026-09-21 20:54:30 +00:00
internal scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA 2026-09-21 21:06:04 +00:00
library adopt Material nav metrics in VerticalTabsLayout 2026-09-21 16:25:43 +00:00
plugin-designs correct the dot-visibility comment to match the two-part fix actually shipped 2026-09-21 17:34:37 +00:00
public light the remaining unconditional loading overlays and the copy-me starter 2026-09-21 16:40:53 +00:00
serving Frame-policy as a real artifact + docs discoverability 2026-09-16 02:37:19 +00:00
skills/make-a-doxa-map Brand-neutral shared VerticalTabsLayout with lazy nested-element hosting; fix cap-5 prayer tab leaf path; neutralize library identifiers; add Forgejo CI build-gate workflow 2026-09-19 17:24:30 +00:00
src-tauri scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA 2026-09-21 21:06:04 +00:00
wordpress add a WordPress embed-contract test that runs without WordPress, and harden the SRI filter against a non-string handle 2026-09-20 07:25:54 +00:00
.env.example doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
.gitattributes Rename app-profiles -> plugin-designs; build green under new naming (load-bearing paths updated, guards pass) 2026-09-17 18:18:18 +00:00
.gitignore write the extensionless token path the client actually requests; ignore it too 2026-09-20 09:55:39 +00:00
bun.lock feat: map provider swap — mapbox|maplibre engine + hybrid geocoder + provider badge; staging title doxa-maps 2026-09-11 21:47:25 +00:00
capacitor.config.json doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
CONTRIBUTING.md Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
index.html Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
LICENSE doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
package.json scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA 2026-09-21 21:06:04 +00:00
README.md point the README at the output path the build actually writes 2026-09-21 16:55:15 +00:00
tauri.conf.json scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA 2026-09-21 21:06:04 +00:00
vite.config.js Brand-neutral shared VerticalTabsLayout with lazy nested-element hosting; fix cap-5 prayer tab leaf path; neutralize library identifiers; add Forgejo CI build-gate workflow 2026-09-19 17:24:30 +00:00
vite.doxa-maps-build-config.json Public data ships without working notes; unused data files stay out of the drop-in 2026-09-16 20:16:08 -05:00

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) aspirational (no code yet) §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.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)

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 Engagement map Adoption map

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

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 UPG-100 list UPG-100 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.

Capability 5 — doxa-maps-spa, the curated product SPA

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


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

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 2 — the doxa-maps SPA at phone viewport (the Capacitor mobile-app surface)

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)


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 + Tauri CLI (not yet scaffolded) aspirational — no Tauri code exists yet

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 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
2 built, not yet shot mobile (Capacitor scaffolded, APK never emitted) · WordPress (wordpress/doxa-maps-embed/*.php written, install never captured)
1 aspirational Tauri desktop (no code exists yet)

All 5 capabilities are proven by a built bundle. Only the deploy methods carry the two lower states.


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.