consolidate docs into documentation/ and deployment shells into deployment/
Some checks are pending
build-gate / build (push) Waiting to run

Driver 2026-09-21: 'a folder called deployment which will store the capacitor
folder, wordpress folder and tauri folder', and 'a folder called documentation
to store the contributing folder so docs are in one place'.

docs/ + contributing/ + CONTRIBUTING.md -> documentation/ (three doc homes into
one). capacitor/ + wordpress/ + src-tauri/ -> deployment/.

The public/ -> dist/ rename the Driver asked about is NOT here: after seeing the
measured blast radius (855 tracked files, 54 source refs, and public/ is the
served web-root four consumers point at) the Driver withdrew it - 'keep the
public folder the same'.

Path references were rewritten mechanically (258 across 87 files), but three
classes needed hand fixes because a prefix regex cannot see them:
  - join(ROOT,'docs',...) in 10 internal scripts - a quoted SEGMENT, not a prefix
  - tauri frontendDist '../public' -> '../../public', since src-tauri is now one
    level deeper; check:tauri caught this exactly as the card predicted
  - the WordPress contract test's dirname(__DIR__,2) -> 3, same reason

Also reverted one over-reach: the rewrite renamed a FINDING-CATEGORY label
'docs' in public-hygiene-check.mjs, which is a rule name in its own header, not
a path.

Verified: full build ALL GUARDS PASS, SRI consistent; check:tauri, check:mobile
(cap sync + webDir OK), check:wp-embed, quoted-scripts, capability-citations,
capability-exemplars, library-index, public-hygiene, parse and drop all pass.
This commit is contained in:
doxa-maps-88655c70 2026-09-21 23:23:23 +00:00
parent 0c7bfb9b9c
commit 9dc4dda8cf
235 changed files with 299 additions and 291 deletions

View file

@ -115,14 +115,14 @@ jobs:
# ★ WORDPRESS EMBED CONTRACT — deployment method 4 of the 5 the hand-off promises.
# The plugin is hand-written PHP but reads manifest.json at RUNTIME, so it can drift from a
# CONTRACT rather than from a file: rename a manifest field and the shortcode silently emits
# broken markup. wordpress/tests/ stubs ~20 WP functions and renders the REAL shortcode against
# broken markup. deployment/wordpress/tests/ stubs ~20 WP functions and renders the REAL shortcode against
# the REAL built manifest (with a negative test that a malformed integrity is rejected) — and it
# ran NOWHERE automatic until now.
# SELF-DETECTING on purpose: ubuntu-latest usually ships php, but asserting that would make this
# step fail for a reason unrelated to the contract. If php is absent we SAY SO rather than
# silently skipping — an unrun check must be visible, not invisible.
# ★ LIBRARY MENU FRESHNESS — CONTENT-based, deliberately NOT mtime-based (2026-09-21).
# docs/reference-library-index.md is DERIVED from library/ and its own header promises it
# documentation/reference-library-index.md is DERIVED from library/ and its own header promises it
# "can't drift from the code" — a promise that had NO mechanism: nothing invoked the generator
# and the file did not exist at all. The local gate (check:library-index) compares mtimes, but
# ★ THAT CANNOT WORK HERE: a fresh checkout gives every file the SAME mtime, so an mtime gate
@ -132,15 +132,15 @@ jobs:
- name: Report a stale library menu
continue-on-error: true
run: |
cp docs/reference-library-index.md /tmp/idx.before
cp documentation/reference-library-index.md /tmp/idx.before
bun run library-index >/dev/null
if ! diff -q /tmp/idx.before docs/reference-library-index.md >/dev/null; then
echo "::warning::docs/reference-library-index.md is STALE — run 'bun run library-index' and commit."
diff /tmp/idx.before docs/reference-library-index.md | head -20 || true
if ! diff -q /tmp/idx.before documentation/reference-library-index.md >/dev/null; then
echo "::warning::documentation/reference-library-index.md is STALE — run 'bun run library-index' and commit."
diff /tmp/idx.before documentation/reference-library-index.md | head -20 || true
else
echo "library menu is current."
fi
cp /tmp/idx.before docs/reference-library-index.md
cp /tmp/idx.before documentation/reference-library-index.md
- name: Verify the WordPress embed contract
continue-on-error: true

View file

@ -43,11 +43,11 @@ 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)**.
**§3 → [documentation/README-SECTION-3-cli-commands.md](./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 [docs/how-to.md](./docs/how-to.md).
> keeps them embeddable everywhere. See [documentation/how-to.md](./documentation/how-to.md).
---
@ -66,7 +66,7 @@ one `.vue`, no preset table, one embeddable element showing every people group o
> `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)
![Capability 1 — a single-element plugin (all-people-groups)](documentation/readme-images/cap1-single-element.png)
### CAP 2 — Preset element (prop-table)
@ -76,7 +76,7 @@ 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) |
| ![Prayer map](documentation/readme-images/cap2-prayer.png) | ![Engagement map](documentation/readme-images/cap2-engagement.png) | ![Adoption map](documentation/readme-images/cap2-adoption.png) |
*All three come from the **same** `doxa-simple-map` plugin-design folder — differentiated only by preset props.*
@ -84,7 +84,7 @@ each a standalone embeddable page. Same code, three maps.
*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)
![Capability 3 — the monolithic research map](documentation/readme-images/cap3-monolithic-research.png)
### CAP 4 — Composite element
@ -94,7 +94,7 @@ the UPG-100 list plugin. It ships **three profiles**, each its own embeddable le
| 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) |
| ![UPG-100 composite](documentation/readme-images/cap4-upg100-mfe.png) | ![UPG-100 list](documentation/readme-images/cap4-upg100-list.png) | ![UPG-100 poster wall](documentation/readme-images/cap4-upg100-poster-wall.png) |
### CAP 5 — App shell / the doxa-maps app
@ -122,12 +122,12 @@ reviews. (Captioning a studio shot as the product SPA is a documented mistake
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)
![Capability 5 — doxa-maps-spa, the curated product SPA](documentation/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)
![Capability 5 — plugin-studio, the auto-generated developer gallery](documentation/readme-images/cap5-plugin-studio.png)
---
@ -140,7 +140,7 @@ The exact commands + install prereqs for each are in **§3**. These shots show w
*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 1 — a map running in an iframe embed on a real site](documentation/readme-images/deploy1-iframe.png)
### Deploy 2 — mobile app
@ -148,7 +148,7 @@ their site. Below: the prayer map running in an iframe inside a church's mission
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">
<img src="documentation/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)
@ -157,7 +157,7 @@ SPA at a phone viewport — the exact web output that `cap:sync` copies into the
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)
![Deploy 3 — the unified research dashboard (map + people-groups, connected via props/events)](documentation/readme-images/deploy3-research-dashboard.png)
---
@ -166,7 +166,7 @@ map, the facet rail, and the people-groups table brush-and-link over one store.
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)**
**→ [documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md)**
Quick reference:
@ -175,7 +175,7 @@ Quick reference:
| 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)* |
| 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
@ -258,7 +258,7 @@ 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) |
| 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
@ -294,13 +294,13 @@ plugin-design; contribute to `library/` only when it is genuinely reusable and p
| 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 |
| **[documentation/develop-a-plugin-and-see-it-live.md](./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](./documentation/README-SECTION-3-cli-commands.md)** | the full per-method CLI + install matrix |
| **[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/README-longform.md](./documentation/README-longform.md)** | the original story-first README (the "why maps" manifesto) |
| **[documentation/architecture/](./documentation/architecture/)** | the deep end — embeddability, app-wrappers, file map |
---

View file

@ -11,13 +11,13 @@
" public/assets/fonts/ Bebas Neue / Poppins (both SIL OFL, so self-hostable)",
"ONE build, THREE consumers, all reading THIS root:",
" 1. Railway / CDN \u2014 serve public/ statically; partner sites embed from stable URLs",
" 2. Capacitor \u2014 this webDir; `cap sync` copies public/ into capacitor/android/app/src/main/assets/public/",
" 2. Capacitor \u2014 this webDir; `cap sync` copies public/ into deployment/capacitor/android/app/src/main/assets/public/",
" 3. local staging \u2014 `bun run serve` (public/ on :4173)",
"NOTE the drop-portability gate still governs what goes INSIDE a bundle: bundle-internal refs must stay",
"RELATIVE. public/ is the HOST root, which is a different layer \u2014 putting shared assets here does not",
"make a bundle root-relative, so the gate stays green.",
"\u26a0\ufe0f SOFT FAILURE: a wrong webDir yields an EMPTY assets/public and a BLANK WHITE SCREEN at launch, not an",
"error. After every sync, verify capacitor/android/app/src/main/assets/public/index.html exists (`bun run cap:verify`)."
"error. After every sync, verify deployment/capacitor/android/app/src/main/assets/public/index.html exists (`bun run cap:verify`)."
],
"webDir": "public",
"server": {
@ -33,7 +33,7 @@
"androidScheme": "https"
},
"android": {
"//": "Native project lives in ./capacitor/android and is COMMITTED \u2014 `cap add` scaffolds it ONCE, then it is hand-edited source (manifest, icons, versionCode). It sits under capacitor/ rather than app/ because app/ is the BUNDLE OUTPUT tree: this repo emits several kinds of product, and Capacitor is one facet of them, not the whole. Only app/src/main/assets/public/ is generated and gitignored. An iOS project would join it at capacitor/ios (ios.path), scaffolded on a Mac.",
"path": "capacitor/android"
"//": "Native project lives in ./capacitor/android and is COMMITTED \u2014 `cap add` scaffolds it ONCE, then it is hand-edited source (manifest, icons, versionCode). It sits under deployment/capacitor/ rather than app/ because app/ is the BUNDLE OUTPUT tree: this repo emits several kinds of product, and Capacitor is one facet of them, not the whole. Only app/src/main/assets/public/ is generated and gitignored. An iOS project would join it at deployment/capacitor/ios (ios.path), scaffolded on a Mac.",
"path": "deployment/capacitor/android"
}
}

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,3 +1,3 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../node_modules/@capacitor/android/capacitor')
project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor')

View file

@ -1,9 +1,9 @@
# `src-tauri/` — the desktop shell (deploy method 5)
# `deployment/src-tauri/` — the desktop shell (deploy method 5)
A Tauri window around the **same `public/` tree** the CDN, Capacitor and local staging already
serve. This crate deliberately contains **no application logic**: the product is the web tree in
`plugin-designs/` → `public/`, and this is a window around it — the desktop counterpart of
`capacitor/android`.
`deployment/capacitor/android`.
## Why this file exists instead of comments in the config
@ -20,7 +20,7 @@ lives here and `tauri.conf.json` stays schema-pure. **Do not add `//` keys to it
## The two placement facts that cost a build each
1. **`tauri.conf.json` must live in `src-tauri/`, not the repo root.** `tauri-build` reads it from
1. **`tauri.conf.json` must live in `deployment/src-tauri/`, not the repo root.** `tauri-build` reads it from
the crate directory; at the root it is invisible and the build dies with *"unable to read Tauri
config file … because entity not found"*. `check:tauri` now asserts the canonical location and
fails on a stray root copy.

View file

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -4,7 +4,7 @@
"version": "0.1.0",
"identifier": "life.doxa.maps",
"build": {
"frontendDist": "../public",
"frontendDist": "../../public",
"beforeBuildCommand": ""
},
"app": {
@ -19,10 +19,14 @@
"url": "doxa-maps/doxa-maps-spa/doxa-maps-spa/index.html"
}
],
"security": { "csp": null }
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": ["deb"]
"targets": [
"deb"
]
}
}

View file

@ -20,7 +20,7 @@ against a configurable catalog base:
From this plugin directory:
```bash
npm -g install @wordpress/env # once
npm -g install @deployment/wordpress/env # once
wp-env start # boots WordPress with THIS folder mounted as a plugin
```
@ -41,7 +41,7 @@ Then in wp-admin (http://localhost:8888/wp-admin, admin/password):
## 2. Manual WordPress
Copy `wordpress/doxa-maps-embed/` into `wp-content/plugins/`, activate, then follow steps 2–4 above.
Copy `deployment/wordpress/doxa-maps-embed/` into `wp-content/plugins/`, activate, then follow steps 2–4 above.
## 3. No-WordPress sanity check (what a reviewer can do now)

View file

@ -28,7 +28,9 @@ $PLUGIN = dirname(__DIR__).'/doxa-maps-embed';
require $PLUGIN.'/includes/class-doxa-maps-embed.php';
// Read the AUTHORITATIVE contract from the built catalog manifest.
$mf = json_decode(file_get_contents(dirname(__DIR__,2).'/public/doxa-maps/manifest.json'), true);
// dirname(__DIR__,3) — THREE levels, not two: wordpress/ moved under deployment/ on
// 2026-09-21, so tests/ is now deployment/wordpress/tests and the repo root is 3 up.
$mf = json_decode(file_get_contents(dirname(__DIR__,3).'/public/doxa-maps/manifest.json'), true);
$bundles = $mf['bundles'];
$target = null;
foreach($bundles as $b){ foreach(($b['profiles']??[]) as $p){ if(!empty($p['element']) && !empty($p['integrity'])){ $target=['bundle'=>$b['name']??$b['bundle'],'profile'=>$p,'b'=>$b]; break 2; } } }

View file

@ -1,8 +1,8 @@
# Contributing to doxa-maps
Everything a contributor needs, short. Recipes live in [docs/how-to.md](./docs/how-to.md),
lookups in [docs/reference.md](./docs/reference.md), depth in
[docs/architecture/](./docs/architecture/).
Everything a contributor needs, short. Recipes live in [documentation/how-to.md](./documentation/how-to.md),
lookups in [documentation/reference.md](./documentation/reference.md), depth in
[documentation/architecture/](./documentation/architecture/).
## The mental model — three zones
@ -26,7 +26,7 @@ genuinely needs it. One fix in `library/` reaches every map on the next build.
Pick A when the maps differ by *configuration*, B when they differ by *layout/behavior*.
Full walkthrough + the migration story:
[docs/architecture/patterns-and-provenance.md](./docs/architecture/patterns-and-provenance.md).
[documentation/architecture/patterns-and-provenance.md](./documentation/architecture/patterns-and-provenance.md).
## The two build steps (never confuse them)
@ -45,14 +45,14 @@ into `public/` on save) · terminal 2 serves the host site / `public/`.
`public/` is the committed web-root — serve it from anywhere and every map is embeddable from
it (script tag or the generated per-map page). Same-origin vs external-CDN modes, `paths`,
`embedBaseUrl`: [docs/how-to.md → "Where do the maps get served from?"](./docs/how-to.md).
`embedBaseUrl`: [documentation/how-to.md → "Where do the maps get served from?"](./documentation/how-to.md).
## Ground rules
- Never edit `library/` to customize one map (bundle-private code goes in your `src/`).
- Never bake tokens or data into a bundle — everything arrives at runtime via `profile-config`.
- Before a PR: `bun run build && bun run verify` must pass (adds drop-portability + parse gates).
- Colors are values, not logic: [contributing/COLORS.md](./contributing/COLORS.md).
- Colors are values, not logic: [documentation/contributing/COLORS.md](./contributing/COLORS.md).
## One-line recap

View file

@ -29,9 +29,9 @@ The scripts these commands are grounded in (from this branch's `package.json`):
"preview": "bun x vite preview",
"serve": "python3 -m http.server 4173 --bind 0.0.0.0 --directory public",
"cap:sync": "./node_modules/.bin/cap sync android",
"cap:verify": "test -f capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)",
"apk:debug": "bun run build && bun run cap:sync && bun run cap:verify && cd capacitor/android && ./gradlew assembleDebug",
"apk:install": "adb install -r capacitor/android/app/build/outputs/apk/debug/app-debug.apk",
"cap:verify": "test -f deployment/capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)",
"apk:debug": "bun run build && bun run cap:sync && bun run cap:verify && cd deployment/capacitor/android && ./gradlew assembleDebug",
"apk:install": "adb install -r deployment/capacitor/android/app/build/outputs/apk/debug/app-debug.apk",
"phone": "bun run apk:debug && bun run apk:install"
```
@ -46,7 +46,7 @@ The scripts these commands are grounded in (from this branch's `package.json`):
| 1 | **iframe embed** | Bun | `bun install` → `bun run build` → `bun run serve` (or drop `public/doxa-maps/` on any host) |
| 2 | **Mobile app (Android)** | Bun ALONE for `check:mobile`; + Android SDK · `adb` on PATH · JDK 17 (Gradle) for the APK | `bun run check:mobile` (sync + webDir assertion, NO toolchain needed) · `bun run phone` (= `apk:debug` + `apk:install` to a device/emulator) |
| 3 | **Direct-JS / dashboard** | Bun | `bun run build`, then paste the `<script src> + integrity` (SRI) snippet from `manifest.json`. *Proven same-root AND foreign-host (verified 2026-09-20: gl + attribution, 0 errors, 0 404s). Fill the `YOUR_*` placeholders the snippet ships with* |
| 4 | **WordPress** | Bun (+ `php` CLI to run the contract gate) | `bun run build`, then either paste the iframe/script snippet into a **Custom HTML** block, **or** install `wordpress/doxa-maps-embed/` (shortcode + Gutenberg block). `bun run check:wp-embed` verifies the emitted markup. *(Contract-proven; not yet shot on a live WP install.)* |
| 4 | **WordPress** | Bun (+ `php` CLI to run the contract gate) | `bun run build`, then either paste the iframe/script snippet into a **Custom HTML** block, **or** install `deployment/wordpress/doxa-maps-embed/` (shortcode + Gutenberg block). `bun run check:wp-embed` verifies the emitted markup. *(Contract-proven; not yet shot on a live WP install.)* |
| 5 | **Desktop (Tauri)** | Bun ALONE for `check:tauri`; + Rust toolchain · Tauri CLI · system webview libs for the binary | `bun run check:tauri` (config contract, NO toolchain needed) · `cargo tauri build` (needs the toolchain). *(Built, not shot — a 65 MB binary + 58 MB `.deb` WERE emitted; only the GUI launch is unverified.)* |
---
@ -77,7 +77,7 @@ external absolute base, set `embedBaseUrl` in the build config before building.)
### 2. Mobile app — Android / Capacitor *(required)*
**Install:** Bun · **Android SDK** (`sdkmanager`, `adb` on your PATH) · **JDK 17**
(the Gradle wrapper needs it) · Capacitor (`@capacitor/*` are already devDependencies,
(the Gradle wrapper needs it) · Capacitor (`@deployment/capacitor/*` are already devDependencies,
so `bun install` covers them). A connected device or running emulator for install.
```bash
@ -87,7 +87,7 @@ bun run phone # full path — needs the toolchain above
**`check:mobile` is runnable anywhere.** `cap sync` is a file copy plus plugin wiring, so it
needs no Android toolchain at all; `cap:verify` then asserts
`capacitor/android/app/src/main/assets/public/index.html` exists. That guards the soft
`deployment/capacitor/android/app/src/main/assets/public/index.html` exists. That guards the soft
failure `capacitor.config.json` warns about — *a wrong `webDir` yields an EMPTY
`assets/public` and a BLANK WHITE SCREEN at launch, not an error*. Verified 2026-09-20 with
`java`, `gradle`, `adb` and `sdkmanager` all absent: sync EXIT=0, the whole `public/` tree
@ -114,7 +114,7 @@ bun run apk:debug # build the APK only
bun run apk:install # push the built APK to a connected device
```
Only Android is scaffolded on this branch (`capacitor/android`, `capacitor.config.json`);
Only Android is scaffolded on this branch (`deployment/capacitor/android`, `capacitor.config.json`);
iOS is not set up.
### 3. Direct-JS / dashboard *(required)*
@ -169,7 +169,7 @@ absolute URLs. That is a deployment choice, not a defect.
> **Corrected 2026-09-20.** This section previously read *"deferred POC — no dedicated
> WordPress build step exists on this branch"*. That is no longer true: a first-class
> plugin exists at `wordpress/doxa-maps-embed/` (a `[doxa_map]` shortcode + a Gutenberg
> plugin exists at `deployment/wordpress/doxa-maps-embed/` (a `[doxa_map]` shortcode + a Gutenberg
> block), and its emitted markup is now verified by a gate.
**The simple path** — no plugin needed, same artifacts as methods 1 and 3:
@ -181,7 +181,7 @@ bun run build
Then paste either the iframe snippet **or** the `<script src> + SRI` snippet from
`manifest.json` into a WordPress **Custom HTML** block.
**The plugin path** — `wordpress/doxa-maps-embed/` registers the bundle through WordPress's
**The plugin path** — `deployment/wordpress/doxa-maps-embed/` registers the bundle through WordPress's
own dependency graph, so the SRI hash and `crossorigin` survive and the script loads once
per page rather than once per embed.
@ -189,7 +189,7 @@ per page rather than once per embed.
bun run check:wp-embed # renders the REAL shortcode with no WordPress installed
```
That gate stubs the ~20 WordPress functions the plugin actually calls (`wordpress/tests/`),
That gate stubs the ~20 WordPress functions the plugin actually calls (`deployment/wordpress/tests/`),
renders the shortcode against the REAL built `manifest.json`, and asserts the contract
`HOW-TO-TEST.md` defines: the element tag is emitted, `profile-config` decodes to valid
JSON, the script `src` equals the catalog path, the SRI `integrity` is injected by the
@ -199,7 +199,7 @@ that a **malformed** integrity is rejected.
**Status honesty — say "contract-proven", not "proven".** What is proven: the plugin emits
the correct markup, repeatably and in CI. What is *not*: that WordPress renders the page,
that the block editor registers it, or that a browser executes the result. Those still need
a real WP install (`wp-env start` — see `wordpress/doxa-maps-embed/HOW-TO-TEST.md`).
a real WP install (`wp-env start` — see `deployment/wordpress/doxa-maps-embed/HOW-TO-TEST.md`).
### 5. Desktop — Tauri *(built + contract-proven; no binary emitted)*
@ -211,13 +211,13 @@ bun run check:tauri # CONTRACT check — needs NO Rust, NO Tauri CLI
cargo tauri build # the binary — needs the toolchain above
```
`src-tauri/tauri.conf.json` + the `src-tauri/` Rust crate wrap the **same `public/` tree**
`deployment/src-tauri/tauri.conf.json` + the `deployment/src-tauri/` Rust crate wrap the **same `public/` tree**
Capacitor and the CDN serve — `build.frontendDist` is `../public`, the desktop counterpart of
Capacitor's `webDir`. One `bun run build` now feeds four consumers: CDN, Capacitor, local
staging, and Tauri. The crate carries no application logic; the product is the web tree.
**`check:tauri` is runnable anywhere**, exactly like `check:mobile`. It asserts the config points
at files that exist: `frontendDist` resolves (Tauri resolves it relative to `src-tauri/` — the
at files that exist: `frontendDist` resolves (Tauri resolves it relative to `deployment/src-tauri/` — the
easiest thing to get wrong), every window `url` exists in the built tree, the identifier matches
Capacitor's `appId`, and the three crate files are present. That guards the soft failure Tauri
shares with Capacitor — *a wrong path yields a BLANK WHITE WINDOW at launch, not an error*.
@ -242,5 +242,5 @@ this box, so `"all"` would fail two of three formats at the very end of a ~6-min
Still **not "proven"** — say *built, not shot*. The GUI launch is unverified because this VPS is
headless: the binary panics in `tao` with "Failed to initialize GTK" (no display, not a defect) and
no Xvfb is installed. See [`src-tauri/README.md`](../src-tauri/README.md) for placement rules, the
no Xvfb is installed. See [`deployment/src-tauri/README.md`](../src-tauri/README.md) for placement rules, the
token/CORS caveat and the full artifact table.

View file

@ -26,7 +26,7 @@ the goal is never one trained person, but trainers who train trainers. This READ
pass it on with the same button — **[the Playbook](#the-playbook--start-a-prayer-movement-with-this-map)**.
Everything below is for the people who build the maps. If you only want to *run the campaign*, jump to the
Playbook; the full walkthrough is [docs/tutorial.md](./docs/tutorial.md).
Playbook; the full walkthrough is [documentation/tutorial.md](./documentation/tutorial.md).
---
@ -42,8 +42,8 @@ dashboards into a single‑page application that runs as a website, installs as
embeds on partner sites piece by piece. *Same code, every screen.*
<p align="center">
<img src="docs/assets/spa-desktop.png" alt="The composed SPA on desktop — many maps, tables and dashboards in one app" width="680">
<img src="docs/assets/spa-mobile.png" alt="The same SPA, same code, as a phone app" width="170">
<img src="documentation/assets/spa-desktop.png" alt="The composed SPA on desktop — many maps, tables and dashboards in one app" width="680">
<img src="documentation/assets/spa-mobile.png" alt="The same SPA, same code, as a phone app" width="170">
</p>
<p align="center"><em>One build, same code: the SPA in a desktop browser and on a phone — many maps, one place.</em></p>
@ -265,11 +265,11 @@ Your map data has to come from somewhere. Common sources:
| Read this | When |
|---|---|
| **[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 (great to hand to your agent) |
| **[docs/how-to.md](./docs/how-to.md)** | task recipes (embed, data, mobile) |
| **[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 (great to hand to your agent) |
| **[documentation/how-to.md](./documentation/how-to.md)** | task recipes (embed, data, mobile) |
| **[library/layouts/README.md](../library/layouts/README.md)** | the SPA composition machinery (many maps → one app) |
| **[docs/architecture/](./docs/architecture/)** | the deep end — embeddability, app-wrappers, file map |
| **[documentation/architecture/](./documentation/architecture/)** | the deep end — embeddability, app-wrappers, file map |
---

View file

@ -1,4 +1,4 @@
# docs/ — pick your path
# documentation/ — pick your path
```
"what is this?" "teach me" "how do I …?" "look it up" "all the detail"

View file

@ -1,4 +1,4 @@
# docs/architecture/ — the deep end
# documentation/architecture/ — the deep end
Everything complex lives here, on purpose. The rest of the docs stay simple ("what it does");
when a simple doc says *"full contract in architecture/…"*, this is where it points.

View file

@ -61,7 +61,7 @@ in that bundle's `plugin-designs/<bundle>/src/colors/`. At init, `index.js` call
- **New mode key** → local **adds** a bundle-private strategy.
Each bundle is its own IIFE, so a local strategy can't leak to another map. This is the "local by
default, promote deliberately" seam. See `contributing/COLORS.md` and
default, promote deliberately" seam. See `documentation/contributing/COLORS.md` and
`plugin-designs/template-bundle/src/colors/` for the copy-me example.
## Status / custom modes

View file

@ -20,7 +20,7 @@ Fix one here and every map benefits — never edit a shared component to customi
| `ResearchMapSideMenu.vue` | Research-map side menu |
| `SideMenuDrawer.vue` | Generic slide-in side-panel primitive |
Per-file roles and gotchas for the whole catalog are in `docs/reference.md`.
Per-file roles and gotchas for the whole catalog are in `documentation/reference.md`.
## Conventions (every shared component)

View file

@ -50,7 +50,7 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
|---|---|---|
| `library/components/legends/LegendDesktop.vue` | Desktop legend card: collapsible pill rendering LegendRows tabs, swaps to PeopleGroupDetail on pin select, defers to LegendMobile on mobile. Props: legendType, initiallyCollapsed, isDark, popupAction, disableCollapse. | Requires injected mapStore/dataStore/uiStore; styles live in useShadowStyles only. Language-family tab intentionally empty here (SemanticTreeLegend is a sibling). Geometry (top:60px/left:8px/380px) must stay in sync with SemanticTreeLegend's `.stl-panel`. Listens for `doxa:openDesktopLegend` scoped by mapId. |
| `library/components/legends/LegendMobile.vue` | Mobile bottom-sheet legend chrome (drag/collapse/detail) wrapping the same LegendRows/SemanticTreeLegend/PeopleGroupDetail. | All CSS lives in a useShadowStyles template literal — a stray backtick in its comments silently blanks the whole mobile stylesheet. |
| `library/components/legends/LegendRows.vue` | Flat-grid legend table: colored clickable filter rows, count/population columns, collapsible children, sticky title + totals footer. Emits filter-click. | ONE flat CSS grid with subgrid rows (spec: docs/legend-spec.md); `.lrg-item` wrappers are display:contents on desktop only for LegendMobile compat — don't add layout to them. Never use scrollIntoView (host-page scroll regression 554bf10). |
| `library/components/legends/LegendRows.vue` | Flat-grid legend table: colored clickable filter rows, count/population columns, collapsible children, sticky title + totals footer. Emits filter-click. | ONE flat CSS grid with subgrid rows (spec: documentation/legend-spec.md); `.lrg-item` wrappers are display:contents on desktop only for LegendMobile compat — don't add layout to them. Never use scrollIntoView (host-page scroll regression 554bf10). |
| `library/components/legends/LegendTools.vue` | Floating pill toolbar (auto-fly tour + clusters toggle) above the desktop legend card. Emits legend-row-fly. | Only renders for doxa-regions / language-family legend types; must stay a SIBLING of LegendDesktop (own `.lt-` CSS namespace); flight/cluster state lives in injected mapStore; hidden below 768px. |
| `library/components/legends/SemanticTreeLegend.vue` | Hierarchical tabbed legend panel (collapsible, sortable, drill-up/down tree). Props: nodes, tabs, columns, open, exportEnabled; emits select/update:open/export. | Syncs selection via the per-instance mediator (usePplrInstance) when present, else local state; styles via useShadowStyles. Hidden kill-switch `SOW_EXPORT_BUTTON=false` hides the fully-wired Export menu across ALL bundles regardless of `:export-enabled`. |
@ -218,7 +218,7 @@ Generated by a per-file scan. The `@map/…` import path mirrors each `library/`
|---|---|---|
| `internal/clean.mjs` | Pre-build script that deletes/recreates the output tree `public/<name>/`. | Reads the tree name from vite.doxa-maps-build-config.json (fallback "doxa-maps"); must run BEFORE per-bundle vite builds. |
| `internal/generate-tree.mjs` | Post-vite step that scans plugin-designs/ and writes the self-contained doxa-maps/ output tree (manifest.json, staging pages, standalone profile/instance pages) and copies to configured destinations. | Runs AFTER per-bundle builds (expects the IIFE at `public/<name>/<bundle>/<bundle>.js`); discovery is convention-driven (bundle = non-underscore dir with .vue at ROOT — profiles moved into src/ silently vanish); all URLs stay relative unless embedBaseUrl is set; inlines staging-forge.{css,js}. |
| `internal/generate-library-index.mjs` | Scans live library/ code and regenerates docs/reference-library-index.md (one-line import catalog). | Output is DERIVED — never hand-edit the index; rerun after any library/ change; descriptions come from each file's first header-comment line. |
| `internal/generate-library-index.mjs` | Scans live library/ code and regenerates documentation/reference-library-index.md (one-line import catalog). | Output is DERIVED — never hand-edit the index; rerun after any library/ change; descriptions come from each file's first header-comment line. |
| `internal/drop-portability-check.mjs` | CLI gate (`check:drop`): scans built .html/.json for root-relative refs and fails the build if any would break subpath/Drop hosting. | Defaults treeDir to `public/<name>` from the build config — build first; only checks .html/.json and a fixed manifest key list (iframeUrl\|scriptUrl\|url\|embedUrl\|output) — new URL keys need adding here. |
| `internal/inline-parse-check.mjs` | Post-build verifier (`check:parse`): syntax-checks every inline classic `<script>` in the built HTML via `node --check`. | Only matches bare `<script>` (no attributes), checked as classic scripts — import.meta or a literal `</script>` in snippets is exactly the bug class it catches. |
| `internal/staging.js` | Dev-only "Embeddable JAMstack Maps" showcase script: auto-discovers bundles via import.meta.glob, renders bundle tabs + live map tiles with lazy WebGL-guarded mounting and a locale switcher. | A bundle appears only if index.js exists and its tag is regex-scraped from customElements.define; map slots come from parsing window.*SLOTS / profile-config attrs. Never iframe the maps (cross-realm instanceof fails); MAX_LIVE=4 with forced WEBGL_lose_context eviction. |

View file

@ -1,6 +1,6 @@
# Architecture — provenance & the two multi-map patterns
> Contributor front door: [../../CONTRIBUTING.md](../../CONTRIBUTING.md).
> Contributor front door: [../../documentation/CONTRIBUTING.md](../../documentation/CONTRIBUTING.md).
## Where this comes from — the migration story

View file

Before

Width:  |  Height:  |  Size: 517 KiB

After

Width:  |  Height:  |  Size: 517 KiB

View file

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 495 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

Before

Width:  |  Height:  |  Size: 648 KiB

After

Width:  |  Height:  |  Size: 648 KiB

View file

@ -2,7 +2,7 @@
Welcome. You want to help build maps — edit colors, add a map, tune a legend — without
learning the whole architecture first. This folder is the **contributor entry point**; the
full guides live in the `docs/` set and are linked below, so nothing here goes stale by
full guides live in the `documentation/` set and are linked below, so nothing here goes stale by
being duplicated.
## The one rule that keeps everything clean
@ -14,17 +14,17 @@ being duplicated.
"**Local by default, promote deliberately.**" When in doubt, keep your change local to your
own map; something graduates into the shared library only once a *second* map genuinely
needs it. Full reasoning — the promotion / orphan-audit rule — is in
[`../docs/explanation.md`](../docs/explanation.md).
[`../documentation/explanation.md`](../documentation/explanation.md).
## Start here
| You want to… | Read |
|---|---|
| Understand the whole picture (one library, many bundles, no rebuild) | [`../README.md`](../README.md) · [`../docs/explanation.md`](../docs/explanation.md) |
| Learn step-by-step with no jargon (non-coders welcome) | [`../docs/tutorial.md`](../docs/tutorial.md) |
| Do a task (add a map, embed, change colors, pick a CDN) | [`../docs/how-to.md`](../docs/how-to.md) |
| Look something up (file map, config fields, library index) | [`../docs/reference.md`](../docs/reference.md) |
| Understand build-vs-dev + partner-site embedding | [`../CONTRIBUTING.md`](../CONTRIBUTING.md) |
| Understand the whole picture (one library, many bundles, no rebuild) | [`../README.md`](../README.md) · [`../documentation/explanation.md`](../documentation/explanation.md) |
| Learn step-by-step with no jargon (non-coders welcome) | [`../documentation/tutorial.md`](../documentation/tutorial.md) |
| Do a task (add a map, embed, change colors, pick a CDN) | [`../documentation/how-to.md`](../documentation/how-to.md) |
| Look something up (file map, config fields, library index) | [`../documentation/reference.md`](../documentation/reference.md) |
| Understand build-vs-dev + partner-site embedding | [`../documentation/CONTRIBUTING.md`](../documentation/CONTRIBUTING.md) |
| Edit or add colors (the two places a color can live) | [`COLORS.md`](./COLORS.md) |
## Colors live here

View file

@ -136,7 +136,7 @@ Your one element deploys five ways (only an element supports all five — a page
| 1 | **iframe** | point an `<iframe>` at the plugin's leaf page | ✅ proven |
| 3 | **JavaScript embed** | `<script src=".../<plugin>.js">` + `<your-tag>` (SRI-hashed) | ✅ proven |
| 2 | **mobile** | `bun run phone` (Capacitor) — build step, no new code | 🔶 built, not shot |
| 4 | **WordPress plugin** | the `wordpress/doxa-maps-embed/` PHP wrapper emits the embed snippet | 🔶 built, not shot |
| 4 | **WordPress plugin** | the `deployment/wordpress/doxa-maps-embed/` PHP wrapper emits the embed snippet | 🔶 built, not shot |
| 5 | **desktop (Tauri)** | `bun run tauri:build` → a real binary + `.deb` wrapping the same `public/` tree | 🔶 built, not shot |
Say "proven" ONLY for 1 & 3 (2, 4 and 5 are scaffolded but no binary/install has been shot). Verify at `https://<platform>/plugin-assets/doxa-maps/<plugin>/<plugin>/index.html`.

View file

@ -266,10 +266,10 @@ bun run apk:debug # build web → cap sync → verify → assemble a debug A
bun run apk:install # adb install onto a connected device/emulator
```
Step by step instead: `bun run build` → `bun run cap:sync` → open `capacitor/android` in Android Studio.
Step by step instead: `bun run build` → `bun run cap:sync` → open `deployment/capacitor/android` in Android Studio.
**iOS** (scaffold once on a Mac): `./node_modules/.bin/cap add ios`, then `bun run build &&
./node_modules/.bin/cap sync ios`, and open `capacitor/ios` in Xcode. Same `webDir: public`.
./node_modules/.bin/cap sync ios`, and open `deployment/capacitor/ios` in Xcode. Same `webDir: public`.
> **Invoke the local bin, not `bunx`.** `bunx` is not on PATH in every environment (it exits 127,
> *command not found*), which is what broke `cap:sync` → `apk:debug` → `phone` at step one. The
@ -336,7 +336,7 @@ If you point an AI agent at this repo, paste this first, before it reads any cod
> **reuse via the `@map/…` alias, never edit**. `plugin-designs/<map>/` = the sandbox for **one**
> map — **build everything here**. `internal/` = the build machine — **never touch it**.
> To build a map: copy `plugin-designs/template-bundle/`, read its README, pick reusable pieces
> from `docs/reference.md`, then build the map's own code inside `plugin-designs/<the-map>/`.
> from `documentation/reference.md`, then build the map's own code inside `plugin-designs/<the-map>/`.
> Change colors by editing **values** in that map's `src/colors/` folder — not logic in
> `library/`. Then run `bun run build`. If a change seems to belong in `library/`, stop and
> ask — that's an expert move that can break every other map.

View file

Before

Width:  |  Height:  |  Size: 971 KiB

After

Width:  |  Height:  |  Size: 971 KiB

View file

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 354 KiB

View file

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 352 KiB

View file

Before

Width:  |  Height:  |  Size: 965 KiB

After

Width:  |  Height:  |  Size: 965 KiB

View file

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1 MiB

Some files were not shown because too many files have changed in this diff Show more