JAMstack bundler for embeddable map applications
Find a file
doxa-maps 0138044650
Some checks are pending
build-gate / build (push) Waiting to run
Commit Cargo.lock: it was ignored by association with real build output
deployment/src-tauri/.gitignore listed /target, /gen and Cargo.lock together
with reasoning that fits the first two. /target is 2GB of compiled output and
/gen is emitted by tauri-build on every compile -- both genuinely
regenerated. A lockfile is not build output: it is the RESOLUTION RECORD, the
only artifact that makes cargo build reproducible.

Cargo's rule splits on crate type -- commit for a binary, ignore for a
library -- and cargo metadata reports this crate as kinds=['bin']
(src/main.rs, no lib.rs). Checked with cargo rather than inferred from the
file layout.

The stakes are concrete: Cargo.toml pins nothing tighter than
tauri = { version = "2" }, while the lockfile pins 430 packages. Without it
an inheritor building next month resolves a different dependency tree than
the one that produced the shipped 64 MB binary, and method 5 is already
"built, not shot", so its build must at least be repeatable.

Verified before committing 109KB: cargo metadata --locked --offline succeeds,
so the lockfile is consistent with the manifest, and it contains zero local
paths or credentials.

check:tauri gains a fifth assertion -- Cargo.lock present AND TRACKED.
Tracked-ness is the point: the file sat on disk the whole time and was still
absent from every clone. Negative-tested against the real state, not a
simulation: the gate FAILED naming the file while it was untracked, and
passed once staged.
2026-09-22 06:53:27 +00:00
.forgejo/workflows consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
.github/workflows doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
data Vendor Vue for the gallery shell, so the self-hosted rung finally exists 2026-09-22 04:59:30 +00:00
data-private Release governance: field-access data is private, never shipped 2026-09-16 04:34:33 +00:00
deployment Commit Cargo.lock: it was ignored by association with real build output 2026-09-22 06:53:27 +00:00
documentation Gate: every declared dependency must be a real package name 2026-09-22 06:49:21 +00:00
internal Commit Cargo.lock: it was ignored by association with real build output 2026-09-22 06:53:27 +00:00
library Move the eight CARTO basemap styles to OpenFreeMap 2026-09-22 02:27:48 +00:00
plugin-designs golden-path-check: walk the develop-a-plugin-and-see-it-live path end to end 2026-09-22 03:55:18 +00:00
public Gate: every declared dependency must be a real package name 2026-09-22 06:49:21 +00:00
serving consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +00:00
skills/make-a-doxa-map consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +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 emit a real desktop binary and .deb for deploy method 5 2026-09-21 21:36:18 +00:00
capacitor.config.json consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +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 Gate: every declared dependency must be a real package name 2026-09-22 06:49:21 +00:00
README.md consolidate docs into documentation/ and deployment shells into deployment/ 2026-09-21 23:23:23 +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 bake the public Mapbox pk so every map works out of the box 2026-09-21 22:47:43 +00: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) built, not shot (binary + .deb emitted; launch unverified) §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 → documentation/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 documentation/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:

→ documentation/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 deployment/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 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 (deployment/wordpress/doxa-maps-embed/*.php written, install never captured) · desktop (deployment/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
documentation/develop-a-plugin-and-see-it-live.md ★ START HERE — the golden path, zero → a plugin live, in 6 steps
documentation/README-SECTION-3-cli-commands.md the full per-method CLI + install matrix
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/README-longform.md the original story-first README (the "why maps" manifesto)
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.