Take main before landing
Some checks are pending
build-gate / build (push) Waiting to run

This commit is contained in:
doxa-maps-88655c70 2026-09-25 03:33:49 +00:00
commit de7388cbf7

View file

@ -60,34 +60,33 @@ The scripts these commands are grounded in (from this branch's `package.json`):
"phone": "bun run apk:debug && bun run apk:install"
```
`bun run build` writes the whole deployable tree to `public/doxa-maps/`, including
`manifest.json` — the machine-readable index that carries the ready-to-paste
**iframe** and **`<script src>` + SRI** embed snippets for every map.
`BUILD_ALLOW=1 bun run build` writes the whole deployable tree to `public/doxa-maps/`, including
`manifest.json`: the machine-readable index that carries a ready-to-paste **iframe** snippet and a
**`<script src>` + SRI** snippet for every plugin.
### Summary table
| # | Deploy method | Install first (prereqs) | Command(s) |
|---|---------------|-------------------------|------------|
| 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 `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.)* |
| # | Deployment method | Install first | Command(s) | Status |
|---|---|---|---|---|
| 1 | **iframe** | Bun | `bun install` → `BUILD_ALLOW=1 bun run build` → drop `public/doxa-maps/` on any host | ✅ production |
| 2 | **Mobile app (Android)** | Bun for the check; + Android SDK · `adb` · JDK 17 for the APK | `bun run check:mobile` (no toolchain) · `bun run phone` | ✅ built |
| 3 | **JavaScript embed** | Bun | `BUILD_ALLOW=1 bun run build`, then paste the `embed.script` snippet from `manifest.json` | ✅ production |
| 4 | **WordPress / Disciple Tools** | Bun (+ `php` for the check) | paste a snippet into a Custom HTML block, or install `deployment/wordpress/doxa-maps-embed/` · `bun run check:wp-embed` | 🧪 proof of concept |
| 5 | **Desktop (Tauri)** | Bun for the check; + Rust · Tauri CLI · webview libs for the binary | `bun run check:tauri` (no toolchain) · `bun run tauri:build` | 🧪 proof of concept |
---
### 1. iframe embed *(required)*
### 1. iframe
**Install:** Bun.
```bash
bun install
BUILD_ALLOW=1 bun run build # builds every plugin-designs/* bundle -> public/doxa-maps/
bun run serve # optional: serves public/ at http://localhost:4173
bun run serve # optional: serves public/ at http://localhost:4173
```
The build writes `public/doxa-maps/manifest.json`. Each profile carries an
`embed.iframe` snippet, e.g.:
Each profile in `manifest.json` carries an `embed.iframe` snippet:
```html
<iframe src="./all-people-groups/all-people-groups/index.html"
@ -95,184 +94,114 @@ The build writes `public/doxa-maps/manifest.json`. Each profile carries an
style="border:none;border-radius:12px;" allowfullscreen></iframe>
```
URLs are root-relative, so the whole `doxa-maps/` folder is position-independent —
drop it on Cloudflare/Vercel/any static host and every iframe resolves. (For an
external absolute base, set `embedBaseUrl` in the build config before building.)
**The folder is position-independent.** Snippet URLs are relative to the tree root, so `doxa-maps/`
works dropped on any static host. For absolute URLs, set `embedBaseUrl` in the build config.
### 2. Mobile app — Android / Capacitor *(required)*
### 2. Mobile app — Android / Capacitor
**Install:** Bun · **Android SDK** (`sdkmanager`, `adb` on your PATH) · **JDK 17**
(the Gradle wrapper needs it) · Capacitor (`@capacitor/*` are already devDependencies,
so `bun install` covers them). A connected device or running emulator for install.
**Install:** Bun · **Android SDK** (`sdkmanager`, `adb` on your PATH) · **JDK 17** · a device or
emulator. The `@capacitor/*` packages are already devDependencies.
```bash
bun run check:mobile # CONTRACT check — needs NO JDK/SDK/device
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
`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
staged into the native assets, `cap:verify` OK. Negative-tested by removing `index.html` — EXIT=1.
> **Why this says "the whole tree" and not a bundle count.** It used to read "21 bundles staged".
> That number matched nothing measurable: the tree today has **18 built bundles**, **17** of them
> listed in `manifest.json` (`key-cities` is withheld by `gatedProfiles`), and **19**
> subdirectories under `public/doxa-maps/` — one of which is `assets/`, not a bundle at all.
> `cap sync` copies `webDir` wholesale, so *bundles* was never the unit it operates on. A count
> that names the wrong noun goes stale the moment the tree changes and cannot be re-checked,
> which is exactly how the original figure survived unnoticed.
**Status: built + contract-proven; not shot.** The APK itself (`gradlew assembleDebug`) and
its launch on a device still need JDK 17 + the Android SDK + `adb`.
`phone` chains the whole path: `apk:debug` (which runs `bun run build`, then
`cap:sync` to copy the web output into the Android project, then `cap:verify`, then
`./gradlew assembleDebug`) and finally `apk:install` (`adb install -r ...`) onto the
device. Run the halves separately if you want to inspect between steps:
```bash
bun run apk:debug # build the APK only
bun run check:mobile # contract check — needs NO JDK, SDK or device
bun run phone # the full path: build → cap:sync → cap:verify → gradlew assembleDebug → adb install
bun run apk:debug # or the halves separately: build the APK only
bun run apk:install # push the built APK to a connected device
```
Only Android is scaffolded on this branch (`deployment/capacitor/android`, `capacitor.config.json`);
iOS is not set up.
**`check:mobile` runs anywhere.** It copies the built tree into the Android project and asserts
`deployment/capacitor/android/app/src/main/assets/public/index.html` exists, which catches the one
silent failure: a wrong `webDir` launches to a blank white screen, not an error. Only Android is
scaffolded; iOS is not set up.
### 3. Direct-JS / dashboard *(required)*
### 3. JavaScript embed
**Install:** Bun.
```bash
BUILD_ALLOW=1 bun run build
```
Then paste the profile's `embed.script` snippet from `manifest.json` — a `<script src>`
with a Subresource-Integrity (SRI) hash plus the custom-element tag:
**Install:** Bun. Build, then paste the plugin's `embed.script` snippet from `manifest.json`: a
`<script src>` with a Subresource-Integrity hash, plus the custom element.
```html
<script src="./all-people-groups/all-people-groups.js"
integrity="sha384-j5Ypx+EI9DGlTClzgLs1R8wAnmTnkQ7HgAhHrxyGoVoHDMTlFVY7slU6JNicn77T"
crossorigin="anonymous"></script>
integrity="sha384-…" crossorigin="anonymous"></script>
<all-people-groups
profile-config='{"profile":"all-people-groups","tk":"YOUR_MAPBOX_TOKEN"}'>
</all-people-groups>
```
The element accepts props / emits events, so it can wire into a surrounding page as a
unified research dashboard (deploy-3 proof). The SRI hash regenerates on every build.
**The hash changes on every build**, so always copy it from the current `manifest.json`. The element
takes props and emits events, so it can wire into the surrounding page.
#### Embedding on a FOREIGN host (a partner site) — the two things that differ
**On a partner's site, a JS embed needs two values an iframe does not.** An iframe carries its own
document base; a script runs in the host's document, so relative asset and API paths would resolve
against the host and 404. The snippet already ships both:
Both embed methods were run to their first failure on a foreign host page (2026-09-20).
**deploy-1 needed nothing**; **deploy-3 needed two values**, and the snippet now ships both.
*Why they differ:* an **iframe carries its own document base**, so the leaf page's relative
asset and API paths resolve correctly. A **direct-JS embed executes in the host's document**,
so those same relative paths resolve against the *host* — and 404. Same bundle, same data,
different containing document.
| | iframe (deploy-1) | direct-JS (deploy-3) |
| | iframe | JavaScript embed |
|---|---|---|
| `assetsBase` | not needed | **required** — in `profile-config` |
| API base | not needed | **required** — `window.MAP_APP_API_URL` (a host global, *not* a profile-config key) |
| Mapbox token | inherited by the leaf | replace `YOUR_MAPBOX_TOKEN` |
| `assetsBase` | not needed | **required**, in `profile-config` (`YOUR_TREE_URL/assets` until you set `assetsBaseUrl`) |
| API base | not needed | **required**, `window.MAP_APP_API_URL` (a host global) |
| Mapbox token | inherited by the leaf page | replace `YOUR_MAPBOX_TOKEN` |
Both are emitted into `embed.script` already: the API base uses the configured value, and
`assetsBase` falls back to a `YOUR_TREE_URL/assets` placeholder when `assetsBaseUrl` is unset.
### 4. WordPress / Disciple Tools — proof of concept
**One rewrite either way.** Snippet URLs are **tree-relative** (`./bundle/...`) because
`embedBaseUrl` is empty by default — that is what keeps a dropped folder position-independent
and the `check:drop` gate green. A partner whose page is not *at* the tree root must either
prefix the path (`./doxa-maps/...`) or set **`embedBaseUrl`** in the build config to emit
absolute URLs. That is a deployment choice, not a defect.
**No plugin needed for the simple path:** build, then paste the iframe or script snippet into a
WordPress **Custom HTML** block.
### 4. WordPress *(built + contract-proven; not yet shot on a live WP install)*
> **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 `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:
```bash
BUILD_ALLOW=1 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** — `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.
**The plugin path:** `deployment/wordpress/doxa-maps-embed/` adds a `[doxa_map]` shortcode and a
Gutenberg block, registering the bundle through WordPress's own dependency graph so the SRI hash and
`crossorigin` survive and the script loads once per page.
```bash
bun run check:wp-embed # renders the REAL shortcode with no WordPress installed
```
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
`script_loader_tag` filter, `crossorigin="anonymous"` is present — plus a negative test
that a **malformed** integrity is rejected.
**What is proven, and what is not.** The check renders the shortcode against the real `manifest.json`
and asserts the element tag, valid `profile-config` JSON, the script `src`, the injected SRI hash and
`crossorigin` — and rejects a malformed hash. It does not prove that WordPress renders the page or a
browser runs it; that needs a live install (`wp-env start`, see
`deployment/wordpress/doxa-maps-embed/HOW-TO-TEST.md`).
**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 `deployment/wordpress/doxa-maps-embed/HOW-TO-TEST.md`).
### 5. Desktop — Tauri — proof of concept
### 5. Desktop — Tauri *(built, not shot — a binary and a `.deb` ARE emitted; only the GUI launch is unverified)*
**Install:** Bun ALONE for the contract check. For the binary: the Rust toolchain (`rustup`),
the Tauri CLI (`cargo install tauri-cli`), and the platform's system webview libs.
**Install:** Bun for the check. For the binary: Rust (`rustup`), the Tauri CLI
(`cargo install tauri-cli`) and the platform's webview libraries.
```bash
bun run check:tauri # CONTRACT check — needs NO Rust, NO Tauri CLI
cargo tauri build # the binary — needs the toolchain above
bun run check:tauri # contract check — needs NO Rust, NO Tauri CLI
bun run tauri:build # the binary and a .deb — needs the toolchain above
```
**What "built, not shot" means here, measured rather than asserted.** `cargo tauri build` has been run
and its outputs are on disk: `deployment/src-tauri/target/release/doxa-maps-desktop` is a 67 MB ELF
64-bit executable, and `target/release/bundle/deb/` holds a 60 MB `.deb`. Eight source files are
git-tracked under `deployment/src-tauri/`. What has *not* happened is a window opening: this build box
is headless with no Xvfb, so nothing has confirmed the app paints rather than launching to a blank
frame. Say "built, not shot" — not "proven", and not "aspirational".
**The desktop app wraps the same tree.** `deployment/src-tauri/tauri.conf.json` points
`build.frontendDist` at `../public`, the desktop twin of Capacitor's `webDir`; the Rust crate carries
no application logic. `check:tauri` asserts that path resolves, every window `url` exists in the built
tree, the identifier matches Capacitor's `appId`, and the crate files are present.
`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.
**Token caveat:** a Tauri webview serves from `tauri://localhost` (or `http://tauri.localhost` on
Windows), so a `tokenUrl` endpoint must send CORS headers for that origin. The fix is a response header
on the token host, not a change in `tauri.conf.json`. Details:
[`deployment/src-tauri/README.md`](../deployment/src-tauri/README.md).
**`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 `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*.
Negative-tested five ways (wrong `frontendDist`, missing window url, identifier drift, empty
windows array, missing crate file); each exits 1.
---
> **The token/CORS caveat, stated before you hit it.** A Tauri webview serves from a custom origin
> (`tauri://localhost`, or `http://tauri.localhost` on Windows), *not* from your token host. The
> `tokenUrl` endpoint must send CORS headers permitting that origin — the same surprise
> `capacitor.config.json` documents for `https://localhost`. **The fix is a response header on the
> token host, not a change in `tauri.conf.json`.**
## Notes and history
**Status: BUILT — artifacts emitted; launch unverified.** Run 2026-09-21 with the real toolchain
(`tauri info` all ✔: webkit2gtk 2.50.6, rustc/cargo 1.96.1, frontendDist `../public`, Vue.js/Vite):
`bun run tauri:build` produced a **65 MB ELF executable** and a **58 MB `.deb`**
(`Package: doxa-maps`, `Depends: libwebkit2gtk-4.1-0, libgtk-3-0`). The web tree is compiled INTO
the binary — 19 distinct `doxa-maps/...` asset paths are embedded in the ELF, which is why the
`.deb` holds only 6 entries.
The measurements behind the sections above.
`bundle.targets` is `["deb"]`, not `"all"`: `rpmbuild` and `linuxdeploy` (AppImage) are absent on
this box, so `"all"` would fail two of three formats at the very end of a ~6-minute release build.
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 [`deployment/src-tauri/README.md`](../deployment/src-tauri/README.md) for placement rules, the
token/CORS caveat and the full artifact table.
- **Mobile — `check:mobile` needs no toolchain (verified 2026-09-20).** With `java`, `gradle`, `adb` and
`sdkmanager` all absent, sync exited 0, the whole `public/` tree was staged into the native assets and
`cap:verify` passed; removing `index.html` made it exit 1. The doc once said "21 bundles staged"; that
count named the wrong unit (`cap sync` copies `webDir` wholesale) and went stale, so it says "the whole
tree" now.
- **JavaScript embed — foreign-host run (2026-09-20).** Both embed methods were run on a foreign host page
until they failed: the iframe needed nothing; the script embed needed `assetsBase` and the API base,
which the snippet now emits. Same-root and foreign-host both then passed (map painted, attribution
shown, 0 errors, 0 404s).
- **WordPress — corrected 2026-09-20.** This section once said no WordPress build step existed; the
shortcode + block plugin and its contract check were added after. The check stubs the ~20 WordPress
functions the plugin calls (`deployment/wordpress/tests/`).
- **Tauri — the build ran (2026-09-21).** With the real toolchain (`tauri info` all ✔: webkit2gtk 2.50.6,
rustc/cargo 1.96.1) `bun run tauri:build` produced a 65 MB ELF executable and a 58 MB `.deb`
(`Depends: libwebkit2gtk-4.1-0, libgtk-3-0`); a later run measured 67 MB and 60 MB. The web tree is
compiled into the binary, which is why the `.deb` holds only 6 entries. `bundle.targets` is `["deb"]`
because `rpmbuild` and `linuxdeploy` were absent, so `"all"` would fail two formats at the end of a
~6-minute build. The window has not been opened: the build box is headless, and the binary stops with
"Failed to initialize GTK" (no display, not a defect). `check:tauri` is negative-tested five ways
(wrong `frontendDist`, missing window url, identifier drift, empty windows array, missing crate file).