diff --git a/README.md b/README.md index adb6f5ad5..befb7d3f5 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,15 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His **Write a map once in `plugin-designs//`, run one build, and drop it on any site** — iframe, script embed, mobile app — or open every map at once as one app. Five plugin -capabilities, five deployment methods, one shared library, one build. +capabilities, three outputs, one shared library, one build. | What dx-maps set out to do | Proof | |---|---| | **One build → one folder** you can drop on any host | `bun run check:drop` | -| **Every map embeddable** from a copy-paste snippet | [deployment methods](#the-5-deployment-methods) · `bun run check:embed-targets` | +| **Every map embeddable** from a copy-paste snippet | [the 3 outputs](#what-the-build-makes--3-outputs) · `bun run check:embed-targets` | | **Every map interoperable** through props and events | `bun run verify:interop` | | **One app nests every map** — and each stays embeddable on its own | [capability 5](#capability-5--app-shell-the-research-gallery) · `bun run check:composite-reg` | -| **5 capabilities × 5 deployment methods** | `bun run verify:5-caps` | +| **5 capabilities, every one shippable in every output** | `bun run verify:5-caps` | | **Shared library, private plugins** — reuse without breaking each other | `bun run check:library-index` | | **One agent per plugin, one shepherd, one working `main`** | [agent swarm](#build-it-with-an-agent-swarm) | @@ -88,7 +88,7 @@ and graduates to a named tab when it's ready. Also on the shelf: UPG neighbor, all people groups, country posters, the vision card, and the PPLR data maps.
-How one build becomes five ways to ship — the "map cruncher" +How one build becomes three outputs — the "map cruncher" ```text map · map · map · map · map · map · map many maps, each once its own codebase @@ -99,8 +99,12 @@ and graduates to a named tab when it's ready. | ONE build step | - public/doxa-maps/ one drop-anywhere folder + manifest.json - / | \ \ iframe mobile JS embed WordPress desktop + public/dx-maps/ OUTPUT 1: one drop-anywhere web folder + / | \ how a page shows a map: + iframe JS embed WordPress (WordPress just for fun) + + the same folder, wrapped: + mobile app (OUTPUT 2) · desktop app (OUTPUT 3) ```
@@ -259,43 +263,134 @@ one custom HTML tag, like ``, that works on any web page. --- -## The 5 deployment methods +## What the build makes — 3 outputs -### 1. iframe — the prayer map on any site ✅ production +**One build step makes one folder. That folder is the product** — every other choice is *where you +put it* and *how a page shows it*. -**Paste one line of HTML and the map appears** — on any website, no coding. +| # | Output | What it is | Status | +|---|---|---|---| +| **1** | **The web folder** `public/dx-maps/` | every map, each embeddable on its own, all working together — 536 files, 76 MiB (about 80 MB) | ✅ production | +| **2** | **Mobile app** | the same folder, wrapped as an Android app by Capacitor | ✅ built | +| **3** | **Desktop app** | the same folder, wrapped as a desktop app by Tauri | 🧪 proven, not yet in production | -![Method 1 — the prayer map embedded in a host page's iframe](documentation/readme-images/deploy1-iframe.png) +### Output 1 — the web folder + +Two questions: **where does the folder go** (hosting), and **how does a page show a map** (embedding). + +#### Where the folder goes + +| Put the folder… | How | iframe | JS embed | Good to know | +|---|---|:-:|:-:|---| +| **in dx-life's `public/` folder** | copy one folder; dx-life becomes the host | ✅ | ✅ | this is how the Research Gallery runs on the dx-life research page (below) | +| **on Cloudflare Pages** | drag and drop the folder in the dashboard | ✅ | ✅ | fits the drag-and-drop limits (1,000 files, 25 MiB per file); add the `_headers` file below for JS embeds on other sites | +| **on GitHub, served by jsDelivr** | push the repo to a public GitHub repo | — | ✅ | a free CDN for scripts; it serves HTML as plain text, so iframes won't render — use it for JS embeds only | +| **on any web server** | copy the folder | ✅ | ✅ | send `Access-Control-Allow-Origin: *` for JS embeds on other sites | + +**Live example:** the dx-life branch [`research-gallery`](https://git.nuxtbase.app/doxa-life/doxa-maps-cdn/src/branch/research-gallery) drops this folder into dx-life's `public/` and fills `/research` with the Research Gallery. Run it with `bun install && bun run dev`, then open `/research/`.
-More about method 1 — iframe +More about hosting the folder -An iframe is a window in your page that shows another page — here, the map's own page. It's the -easiest way to share a map: it works on any site that accepts HTML, and the map keeps its own look. -Every plugin's exact snippet is `embed.iframe` in `public/doxa-maps/manifest.json`: +- **The folder works from any address.** Every link inside it is relative, so you can put it at + `https://your-site/dx-maps/` or at a CDN root and nothing breaks (`bun run check:drop` proves it). +- **iframes need nothing special.** A map's page loads inside the frame from wherever the folder is. +- **JS embeds on another site need one header.** The embed loads scripts and `manifest.json` across + sites with a security hash, so the host must allow it. On Cloudflare Pages (or Netlify), put a file + named `_headers` in the folder you upload: -```html - -``` + ```text + /* + Access-Control-Allow-Origin: * + ``` -Host the built `public/doxa-maps/` folder anywhere (it works from any address), then point the -iframe at it. +- **jsDelivr** serves any public GitHub repo as a CDN, e.g. + `https://cdn.jsdelivr.net/gh//@/public/dx-maps//.js`. It needs a + **GitHub** repo (this project's public copy is on its own git host), files under 20 MB (the largest + here is 7.5 MB), and it returns HTML as plain text — hence scripts only. +- **Netlify Drop** (drag and drop at app.netlify.com/drop) works best under 50 MB; at about 80 MB, use the + Netlify CLI instead.
-### 2. Mobile app — UPG-100 on a phone ✅ built +#### How a page shows a map -**The same maps, wrapped as a phone app.** +##### iframe — the prayer map on any site ✅ production -

Method 2 — the UPG-100 list at phone size

+**Paste one line of HTML and the map appears** — on any website, no coding. + +![iframe — the prayer map embedded in a host page](documentation/readme-images/deploy1-iframe.png)
-More about method 2 — mobile app +More about the iframe -[Capacitor](https://capacitorjs.com) wraps the built maps into an Android app — the same files, -no new code. Shown here: the UPG-100 prayer lists and posters at phone size, each poster with its -own print options. +An iframe is a window in your page that shows another page — here, the map's own page. It's the +easiest way to share a map: it works on any site that accepts HTML, and the map keeps its own look. +Every plugin's exact snippet is `embed.iframe` in `public/dx-maps/manifest.json`: + +```html + +``` + +
+ +##### JavaScript embed — the Research Gallery on dx-life ✅ production + +**Two lines of code put a whole app inside your own page** — here, the Research Gallery on the +dx-life research page. + +![JavaScript embed — the Research Gallery on the dx-life research page](documentation/readme-images/deploy3-js-embed-dxlife.png) + +
+More about the JavaScript embed + +Instead of a window onto another page, the map becomes **part of your page**, so your page can talk +to it. On dx-life, one embed of the Research Gallery replaces the separate research map, list and +regions dashboards. The exact snippet is `embed.script` in `manifest.json`; copy its `integrity` +hash from there, because it changes with every build: + +```html + + + +``` + +
+ +##### WordPress / Disciple Tools — 🧪 proven, just for fun + +**We got a JAMstack map running inside WordPress and Disciple Tools.** + +![WordPress / Disciple Tools — proven, not yet in production](documentation/readme-images/deploy4-wordpress.png) + +
+More about WordPress / Disciple Tools + +It works, but it isn't yet a production feature of the map bundler. Two ways to try it: + +- **Easiest:** paste the iframe or JavaScript snippet into a WordPress **Custom HTML** block. +- **As a plugin:** install `deployment/wordpress/doxa-maps-embed/` in `wp-content/plugins/`. It adds + a `[doxa_map]` shortcode and a block for the block editor. + +`bun run check:wp-embed` confirms the plugin writes the right HTML, without needing WordPress +installed. + +
+ +### Output 2 — the mobile app ✅ built + +**The same maps, wrapped as a phone app.** No new code — the maps are JAMstack: they fetch their data +from a remote API, which is exactly what a mobile app needs. + +

Mobile app — the UPG-100 list at phone size

+ +
+More about the mobile app + +[Capacitor](https://capacitorjs.com) wraps the built folder into an Android app. Shown here: the +UPG-100 prayer lists and posters at phone size, each poster with its own print options. ```bash bun run check:mobile # checks the app is wired up correctly — needs no Android tools @@ -306,59 +401,16 @@ bun run phone # builds the app and installs it on a phone or emulator
-### 3. JavaScript embed — the Research Gallery on dx-life ✅ production - -**Two lines of code put a whole app inside your own page** — here, the Research Gallery on dx-life. - -![Method 3 — the Research Gallery JS-embedded on the dx-life research page](documentation/readme-images/deploy3-js-embed-dxlife.png) - -
-More about method 3 — JavaScript embed - -Instead of a window onto another page, the map becomes **part of your page**, so your page can talk -to it. The dx-life research page replaced its separate research map, list and regions dashboards -with this one embed of the Research Gallery. The exact snippet is `embed.script` in `manifest.json`; -copy its `integrity` hash from there, because it changes with every build: - -```html - - - -``` - -
- -### 4. WordPress / Disciple Tools — 🧪 proven, not yet in production - -**We got a JAMstack map running inside WordPress and Disciple Tools.** - -![Method 4 — WordPress / Disciple Tools, proven but not yet in production](documentation/readme-images/deploy4-wordpress.png) - -
-More about method 4 — WordPress / Disciple Tools - -It works, but it isn't yet a production feature of the map bundler. Two ways to try it: - -- **Easiest:** paste a method 1 or method 3 snippet into a WordPress **Custom HTML** block. -- **As a plugin:** install `deployment/wordpress/doxa-maps-embed/` in `wp-content/plugins/`. It adds - a `[doxa_map]` shortcode and a block for the block editor. - -`bun run check:wp-embed` confirms the plugin writes the right HTML, without needing WordPress -installed. - -
- -### 5. Desktop app (Tauri) — 🧪 proven, not yet in production +### Output 3 — the desktop app 🧪 proven, not yet in production **We got the JAMstack map app running as a desktop app.** -![Method 5 — desktop app (Tauri), proven but not yet in production](documentation/readme-images/deploy5-desktop.png) +![Desktop app (Tauri) — proven, not yet in production](documentation/readme-images/deploy5-desktop.png)
-More about method 5 — desktop app +More about the desktop app -[Tauri](https://tauri.app) wraps the same built maps into a desktop program: it produced a Linux +[Tauri](https://tauri.app) wraps the same built folder into a desktop program: it produced a Linux app and a `.deb` installer. It isn't in the dx bundler for production yet — and the Research Gallery could become a strong desktop app. @@ -373,6 +425,39 @@ bun run tauri:build # builds the app — needs Rust, the Tauri CLI and webvie --- +## Languages + +**Every map speaks 11 languages** — add `?lang=` to a map's page address. Arabic reads right to left. + +![UPG-100 List in Arabic, right to left](documentation/readme-images/lang-arabic-upg100.png) + +UPG-100 List with `?lang=ar`: the interface is in Arabic; people-group and place names come from the data. + +| Code | Language | | Code | Language | +|---|---|---|---|---| +| `en` | English | | `hi` | हिन्दी — Hindi | +| `ar` | العربية — Arabic (right to left) | | `it` | Italiano — Italian | +| `de` | Deutsch — German | | `pt` | Português — Portuguese | +| `es` | Español — Spanish | | `ro` | Română — Romanian | +| `fr` | Français — French | | `ru` | Русский — Russian | +| `zh` | 中文 — Chinese | | | | + +
+More about languages + +- **Pick a language with the address:** `…/prayer/index.html?lang=fr` shows the map in French. It + works on every map's own page and on the Research Gallery. `?locale=` works too. +- **Without it,** a map follows the page's own language (``, which WordPress sets), and + otherwise shows English. +- **Right to left:** in Arabic the whole layout mirrors — the Research Gallery's tab rail moves to + the right. +- **Where the words live:** `library/i18n/locales//`, one folder per language. Adding a + language: [documentation/architecture/i18n.md](./documentation/architecture/i18n.md). + +
+ +--- + ## Build it with an agent swarm **Many AI agents can build many plugins at once without breaking each other's work.** Every plugin @@ -504,8 +589,8 @@ The studio is a plugin like any other: `plugin-designs/plugin-studio/plugin-stud |---|---| | `bun run dev` | The Plugin Studio, updating as you save, at http://localhost:5173 | | `BUNDLE= bun x vite build` | Build one plugin only | -| `BUILD_ALLOW=1 bun run build` | Build everything into `public/doxa-maps/` (~4 min; refuses without the flag, to protect a shared project) | -| `bun run serve` | Serve the built maps at http://localhost:4173 | +| `BUILD_ALLOW=1 bun run build` | Build everything into `public/dx-maps/` (~4 min; refuses without the flag, to protect a shared project) | +| `bun run serve` | Serve the built output folder — the Plugin Studio is http://localhost:4173/dx-maps/index.html | | `bun run gates:pre` · `bun run gates:post` | The checks that keep `main` working | | `bun run shots` | Retake these screenshots (`ONLY=` for one) | @@ -533,7 +618,7 @@ doxa-maps/ │ ├── colors/ styles/ colour strategies and the house style │ ├── i18n/locales/ the 11 languages │ └── data/ api/ lookup tables and data sources -├── public/doxa-maps/ the BUILD OUTPUT — the drop-anywhere folder + manifest.json +├── public/dx-maps/ the BUILD OUTPUT — the drop-anywhere folder + manifest.json ├── deployment/ mobile (Capacitor) · WordPress · desktop (Tauri) ├── internal/ the build and its checks └── documentation/ tutorial · how-to · reference · architecture diff --git a/documentation/CONTRIBUTING.md b/documentation/CONTRIBUTING.md index 5f769321b..f42d5198d 100644 --- a/documentation/CONTRIBUTING.md +++ b/documentation/CONTRIBUTING.md @@ -33,7 +33,7 @@ Full walkthrough + the migration story: | Command | What | Writes to `public/`? | |---|---|---| | `bun run dev` | HMR plugin gallery at `localhost:5173` — iterate on a map in isolation | **No** | -| `bun run build` | every bundle → `public/doxa-maps//.js` + generated pages | **Yes** | +| `bun run build` | every bundle → `public/dx-maps//.js` + generated pages | **Yes** | To see a change on the served site: `bun run build` (or `BUNDLE= bun run build:bundle`), then refresh. `dev` never updates the served tree. diff --git a/documentation/README-SECTION-3-cli-commands.md b/documentation/README-SECTION-3-cli-commands.md index 4289da3a2..c54dae6b5 100644 --- a/documentation/README-SECTION-3-cli-commands.md +++ b/documentation/README-SECTION-3-cli-commands.md @@ -60,7 +60,7 @@ The scripts these commands are grounded in (from this branch's `package.json`): "phone": "bun run apk:debug && bun run apk:install" ``` -`BUILD_ALLOW=1 bun run build` writes the whole deployable tree to `public/doxa-maps/`, including +`BUILD_ALLOW=1 bun run build` writes the whole deployable tree to `public/dx-maps/`, including `manifest.json`: the machine-readable index that carries a ready-to-paste **iframe** snippet and a **` - + @@ -48,18 +48,18 @@ You don't have to use the script tag. **Every profile also builds its own standa a bare, full-screen map at: ``` -https:///doxa-maps///index.html +https:///dx-maps///index.html ``` Link it, share it, or iframe it (perfect for CMSs and tools that only accept iframes, like Start.me or Notion): ```html - ``` -You never write these snippets by hand — `public/doxa-maps/manifest.json` lists a ready-made +You never write these snippets by hand — `public/dx-maps/manifest.json` lists a ready-made `embed.iframe` and `embed.script` snippet for every profile, and the staging pages show them copy-paste ready. Design details: [architecture/embeddability.md](./architecture/embeddability.md). @@ -94,7 +94,7 @@ with `_` or `.` — those are skipped by the build. Import each with the `@map/…` alias; do not copy their code into your folder. 6. **Build and verify.** ```bash - BUNDLE=my-new-map bun run build:bundle # builds just yours → public/doxa-maps/my-new-map/my-new-map.js + BUNDLE=my-new-map bun run build:bundle # builds just yours → public/dx-maps/my-new-map/my-new-map.js bun run verify # data guards (fast, no browser) ``` A green build is not a working map — the build proves the bundle *compiles*; the guards @@ -126,7 +126,7 @@ One parameterized profile can emit **many standalone embed pages** — no extra } ``` -`bun run build` then emits a page **per instance** at `doxa-maps///index.html` — +`bun run build` then emits a page **per instance** at `dx-maps///index.html` — each directly linkable/iframe-able with its preset props baked into the page (the base profile still accepts live props). Instances *split* one profile into many render targets; `tabs` *nest* many views into one element — different tools. @@ -219,7 +219,7 @@ Pick one home and **every partner site embeds the same map from it** — change updates everywhere. **How it's wired** (one file: `vite.doxa-maps-build-config.json`; full field table in -**[reference.md](./reference.md)**): `bun run build` always writes to `public/doxa-maps//`. +**[reference.md](./reference.md)**): `bun run build` always writes to `public/dx-maps//`. Set `paths` to also copy the tree into another host's folder (e.g. doxa-life's `public/`). Set `embedBaseUrl` to the public URL of your chosen home — it generates absolute, paste-anywhere embed snippets (leave it empty for relative URLs that work wherever the folder is dropped). @@ -240,13 +240,12 @@ Several maps + one layout = a single-page app, served from the same `public/` tr - `nav.js` — add a sidebar entry (`{ kind, id, label, icon, target }`; `target` controls desktop/mobile packaging). - the `PROFILES` object in its `index.html` — tell the shell what to mount: - `{ tag: '', src: './doxa-maps//.js', views: ['map'] }`. + `{ tag: '', src: './dx-maps//.js', views: ['map'] }`. 3. **Install the shell**: `bun run shell` — copies the wrapper to `public/index.html`, so the site root IS the SPA and the bundles sit beneath it. (Also required for the mobile app.) - > Note: this OVERWRITES the generated root page, which since 2026-09-22 is the **plugin studio** - > host. That is the intended trade for this opt-in wrapper, and it is not sticky — the next - > `bun run build` regenerates the studio root. The studio's own leaf stays at - > `public/doxa-maps/plugin-studio/plugin-studio/`, and the archived gallery at `/__gallery`. + > Note: the build's own front page — the **Plugin Studio** — is `public/dx-maps/index.html`, + > inside the output folder, so installing the shell at `public/index.html` does not replace it. + > The next `bun run build` leaves the shell's copy alone and regenerates the output folder. 4. **Build & serve**: `bun run build`, then serve `public/` — the shell lazy-loads each bundle the first time you open its nav entry. @@ -257,7 +256,7 @@ Contracts and gotchas (PROFILES shape, tag-vs-bundle names, `--no-token`): ## Build the Android / iOS app (optional) -**Mobile is separate from `bun run build`.** The default build produces **only** the `doxa-maps` +**Mobile is separate from `bun run build`.** The default build produces **only** the `dx-maps` web folder — the embeddable maps (and the SPA, if configured). It never touches Capacitor. The Android/iOS app is an **opt-in** step that *reuses* that web build: Capacitor's `webDir` points at `public/` (`appId: life.doxa.maps`, `appName: "Doxa Maps"`), so `cap sync` wraps the already-built @@ -296,7 +295,7 @@ The engine is a **runtime parameter** — same bundle, no rebuild. Resolution or -https:///doxa-maps///index.html?provider=maplibre +https:///dx-maps///index.html?provider=maplibre ``` `maplibre` runs token-free on the OpenFreeMap style (the right GL script/CSS is injected diff --git a/documentation/readme-images/cap1-single-element.png b/documentation/readme-images/cap1-single-element.png index 206890a1b..7e4f72d93 100644 Binary files a/documentation/readme-images/cap1-single-element.png and b/documentation/readme-images/cap1-single-element.png differ diff --git a/documentation/readme-images/cap2-adoption.png b/documentation/readme-images/cap2-adoption.png index 87f71d7fc..ee5cb2492 100644 Binary files a/documentation/readme-images/cap2-adoption.png and b/documentation/readme-images/cap2-adoption.png differ diff --git a/documentation/readme-images/cap2-engagement.png b/documentation/readme-images/cap2-engagement.png index c710fc31f..6b1a623ae 100644 Binary files a/documentation/readme-images/cap2-engagement.png and b/documentation/readme-images/cap2-engagement.png differ diff --git a/documentation/readme-images/cap2-prayer.png b/documentation/readme-images/cap2-prayer.png index fb80176f4..8ddd7694a 100644 Binary files a/documentation/readme-images/cap2-prayer.png and b/documentation/readme-images/cap2-prayer.png differ diff --git a/documentation/readme-images/cap3-monolithic-research.png b/documentation/readme-images/cap3-monolithic-research.png index 59caee758..420463fdf 100644 Binary files a/documentation/readme-images/cap3-monolithic-research.png and b/documentation/readme-images/cap3-monolithic-research.png differ diff --git a/documentation/readme-images/cap4-upg100-list.png b/documentation/readme-images/cap4-upg100-list.png index 3d9e7b4d6..a716a374a 100644 Binary files a/documentation/readme-images/cap4-upg100-list.png and b/documentation/readme-images/cap4-upg100-list.png differ diff --git a/documentation/readme-images/cap4-upg100-mfe.png b/documentation/readme-images/cap4-upg100-mfe.png index 80a2b597c..024915dc9 100644 Binary files a/documentation/readme-images/cap4-upg100-mfe.png and b/documentation/readme-images/cap4-upg100-mfe.png differ diff --git a/documentation/readme-images/cap5-plugin-studio.png b/documentation/readme-images/cap5-plugin-studio.png index ba50f88eb..3db1400a9 100644 Binary files a/documentation/readme-images/cap5-plugin-studio.png and b/documentation/readme-images/cap5-plugin-studio.png differ diff --git a/documentation/readme-images/delegation-model.png b/documentation/readme-images/delegation-model.png index 0b1260421..4b5268825 100644 Binary files a/documentation/readme-images/delegation-model.png and b/documentation/readme-images/delegation-model.png differ diff --git a/documentation/readme-images/deploy2-mobile-upg100.png b/documentation/readme-images/deploy2-mobile-upg100.png index e46927c78..db0fb6bd2 100644 Binary files a/documentation/readme-images/deploy2-mobile-upg100.png and b/documentation/readme-images/deploy2-mobile-upg100.png differ diff --git a/documentation/readme-images/exp-prayer-walk-planner.png b/documentation/readme-images/exp-prayer-walk-planner.png index 39f119d92..9a43c9f2e 100644 Binary files a/documentation/readme-images/exp-prayer-walk-planner.png and b/documentation/readme-images/exp-prayer-walk-planner.png differ diff --git a/documentation/readme-images/exp-wall-of-people-groups.png b/documentation/readme-images/exp-wall-of-people-groups.png index 16a15dc8b..de1b17960 100644 Binary files a/documentation/readme-images/exp-wall-of-people-groups.png and b/documentation/readme-images/exp-wall-of-people-groups.png differ diff --git a/documentation/readme-images/lang-arabic-upg100.png b/documentation/readme-images/lang-arabic-upg100.png new file mode 100644 index 000000000..6f0dfb35b Binary files /dev/null and b/documentation/readme-images/lang-arabic-upg100.png differ diff --git a/documentation/readme-images/prod-big-picture.png b/documentation/readme-images/prod-big-picture.png index fb8735cd4..49785ef74 100644 Binary files a/documentation/readme-images/prod-big-picture.png and b/documentation/readme-images/prod-big-picture.png differ diff --git a/documentation/readme-images/prod-cities-internet-phone.png b/documentation/readme-images/prod-cities-internet-phone.png index 80eff4570..721b3f10e 100644 Binary files a/documentation/readme-images/prod-cities-internet-phone.png and b/documentation/readme-images/prod-cities-internet-phone.png differ diff --git a/documentation/readme-images/prod-cities-internet.png b/documentation/readme-images/prod-cities-internet.png index 880aa825f..742f77c5e 100644 Binary files a/documentation/readme-images/prod-cities-internet.png and b/documentation/readme-images/prod-cities-internet.png differ diff --git a/documentation/readme-images/prod-gospel-resources-phone.png b/documentation/readme-images/prod-gospel-resources-phone.png index 216ad6a51..e4a22e772 100644 Binary files a/documentation/readme-images/prod-gospel-resources-phone.png and b/documentation/readme-images/prod-gospel-resources-phone.png differ diff --git a/documentation/readme-images/prod-gospel-resources.png b/documentation/readme-images/prod-gospel-resources.png index c62c3b3cb..c68e55386 100644 Binary files a/documentation/readme-images/prod-gospel-resources.png and b/documentation/readme-images/prod-gospel-resources.png differ diff --git a/documentation/readme-images/prod-prayer-phone.png b/documentation/readme-images/prod-prayer-phone.png index 1155ccd93..6e4db7f2b 100644 Binary files a/documentation/readme-images/prod-prayer-phone.png and b/documentation/readme-images/prod-prayer-phone.png differ diff --git a/documentation/readme-images/prod-regions-phone.png b/documentation/readme-images/prod-regions-phone.png index dee0ac92f..c4b3b8077 100644 Binary files a/documentation/readme-images/prod-regions-phone.png and b/documentation/readme-images/prod-regions-phone.png differ diff --git a/documentation/readme-images/prod-regions.png b/documentation/readme-images/prod-regions.png index d5ccf955e..8ad21e462 100644 Binary files a/documentation/readme-images/prod-regions.png and b/documentation/readme-images/prod-regions.png differ diff --git a/documentation/readme-images/prod-simple-research-phone.png b/documentation/readme-images/prod-simple-research-phone.png index acde70197..0719de9ca 100644 Binary files a/documentation/readme-images/prod-simple-research-phone.png and b/documentation/readme-images/prod-simple-research-phone.png differ diff --git a/documentation/readme-images/prod-simple-research.png b/documentation/readme-images/prod-simple-research.png index 4f29bbba9..4b9e64b38 100644 Binary files a/documentation/readme-images/prod-simple-research.png and b/documentation/readme-images/prod-simple-research.png differ diff --git a/documentation/readme-images/shot-bundles.json b/documentation/readme-images/shot-bundles.json index c28074173..ef2f8813f 100644 --- a/documentation/readme-images/shot-bundles.json +++ b/documentation/readme-images/shot-bundles.json @@ -1,35 +1,35 @@ { "cap1-single-element": { "bundle": "all-people-groups", - "sha256": "c990929277d9aa5fd0d4ef78362f7b43e328a7c073ac6bf97aadfde5cb942de9" + "sha256": "715fa7a854ea0b819f5dbe4ae256d95264984584e6c6933ed321bbb4230b49f7" }, "cap2-adoption": { "bundle": "doxa-simple-map", - "sha256": "0e11603ed79a26b16456f23d582549830365a2c5095ba9ad7ad363c7d1ba9a90" + "sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5" }, "cap2-engagement": { "bundle": "doxa-simple-map", - "sha256": "0e11603ed79a26b16456f23d582549830365a2c5095ba9ad7ad363c7d1ba9a90" + "sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5" }, "cap2-prayer": { "bundle": "doxa-simple-map", - "sha256": "0e11603ed79a26b16456f23d582549830365a2c5095ba9ad7ad363c7d1ba9a90" + "sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5" }, "cap3-monolithic-research": { "bundle": "doxa-research-map", - "sha256": "b849fb71c4a96810d840d9f2dfc208c3e13648429e0ae1a1c5784693e6e5774f" + "sha256": "ea9638be007ff6127514e663f1ee79644be5fee4ba7340ef8d3ad6bf18cafc69" }, "cap4-upg100-list": { "bundle": "my-upg-100-list", - "sha256": "fab621d68bdad964da2be67135d0385d40417c6180f008f36cbc0f50076f5e4a" + "sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480" }, "cap4-upg100-mfe": { "bundle": "my-upg-100-list", - "sha256": "fab621d68bdad964da2be67135d0385d40417c6180f008f36cbc0f50076f5e4a" + "sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480" }, "cap4-upg100-poster-wall": { "bundle": "my-upg-100-list", - "sha256": "fab621d68bdad964da2be67135d0385d40417c6180f008f36cbc0f50076f5e4a" + "sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480" }, "cap5-doxa-maps-spa": { "bundle": "doxa-maps-spa", @@ -37,51 +37,55 @@ }, "cap5-plugin-studio": { "bundle": "plugin-studio", - "sha256": "4d3910b06d55c7fccdba7102be4f9420a0e1368efd1695a6abaf12442b4fcda0" + "sha256": "6b8f3ce6cb17075ec26859f12ec0afe0c78bee5dc02b1f9f81b8d699931dc44a" }, "deploy2-mobile-upg100": { "bundle": "my-upg-100-list", - "sha256": "fab621d68bdad964da2be67135d0385d40417c6180f008f36cbc0f50076f5e4a" + "sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480" }, "exp-prayer-walk-planner": { "bundle": "prayer-walk-planner", - "sha256": "cee5f2806bd5cc8a7fa4c49db200df643d07cf303fd75ee739e606dd9ee93aee" + "sha256": "a06c505f4ae24d03f29657065806f65c4dfc36348a1000178d6cb843aaf455e8" }, "exp-wall-of-people-groups": { "bundle": "wall-of-people-groups", - "sha256": "ba718e082f77f50d5775ebf43007385b7293f8099e2ca4d79e8ad1753ac4133f" + "sha256": "de94f7f53fcfbb757ed8216679e5f563db90cde005ec401104d617fc59c5f105" + }, + "lang-arabic-upg100": { + "bundle": "my-upg-100-list", + "sha256": "50e6d3caaa49467f9c867e4cc04b5121d965092c96e09a1f3d0a78a7483db480" }, "prod-big-picture": { "bundle": "dashboard-plus-grid", - "sha256": "e90739f622678db4c421c76855a5c39882aabd4ed8dbdd65cbe1385a45c67edc" + "sha256": "4ec8da3d155150d391ecbc0ac2f96f60bd2efbb12b3b5e8bdf0a869846dbfa16" }, "prod-cities-internet": { "bundle": "cities-internet", - "sha256": "ad145b5a63e7d4b7468a54db53f2f71f397216956fec698d357ae5b841fa8e3b" + "sha256": "a62739180efc54c2d9e9216eafe0b290921da6ef83827749ead027945f56dbd7" }, "prod-cities-internet-phone": { "bundle": "cities-internet", - "sha256": "ad145b5a63e7d4b7468a54db53f2f71f397216956fec698d357ae5b841fa8e3b" + "sha256": "a62739180efc54c2d9e9216eafe0b290921da6ef83827749ead027945f56dbd7" }, "prod-gospel-resources": { "bundle": "gospel-resources", - "sha256": "943fa394060c009d88a3a8e17651980e9c40077be315fb69c98fed92aa17dce0" + "sha256": "8b31fa704a5217be541a804d350551b3f7b7d064eb1096eaecb226ad10e578f4" }, "prod-gospel-resources-phone": { "bundle": "gospel-resources", - "sha256": "943fa394060c009d88a3a8e17651980e9c40077be315fb69c98fed92aa17dce0" + "sha256": "8b31fa704a5217be541a804d350551b3f7b7d064eb1096eaecb226ad10e578f4" }, "prod-prayer-phone": { "bundle": "doxa-simple-map", - "sha256": "0e11603ed79a26b16456f23d582549830365a2c5095ba9ad7ad363c7d1ba9a90" + "sha256": "4f7ecd217d30bcb87a86c3f07f9840b2a500c9637037c4ff64a5e10aba009bf5" }, "prod-regions": { "bundle": "regions-dashboard", - "sha256": "8420509dde0ea1390e0d4ae8d49f80a303d17fa8a38c37755a82b07ab09567f5" + "sha256": "3609a2c914d6af247cb6618585d234e9a2c857df83bfe03c2a699c2615d1828d" }, "prod-regions-phone": { "bundle": "regions-dashboard", - "sha256": "8420509dde0ea1390e0d4ae8d49f80a303d17fa8a38c37755a82b07ab09567f5" + "sha256": "3609a2c914d6af247cb6618585d234e9a2c857df83bfe03c2a699c2615d1828d" }, "prod-research-gallery-phone": { "bundle": "doxa-maps-spa", @@ -89,10 +93,10 @@ }, "prod-simple-research": { "bundle": "research-map-plus-grid", - "sha256": "9e081907bd697b5ac63531cedbdb34d3f47a09d1d5aee3634722128af1136d0f" + "sha256": "8091ad4c3369928fb322ca69fb9319567fc1351a381e1bffbca518a13e78cb53" }, "prod-simple-research-phone": { "bundle": "research-map-plus-grid", - "sha256": "9e081907bd697b5ac63531cedbdb34d3f47a09d1d5aee3634722128af1136d0f" + "sha256": "8091ad4c3369928fb322ca69fb9319567fc1351a381e1bffbca518a13e78cb53" } } diff --git a/documentation/readme-images/src/delegation-model.svg b/documentation/readme-images/src/delegation-model.svg index 35682aa88..fbe716229 100644 --- a/documentation/readme-images/src/delegation-model.svg +++ b/documentation/readme-images/src/delegation-model.svg @@ -161,35 +161,23 @@ the checks must pass, main stays working -main → ONE build → public/doxa-maps/ → five ways to ship - -METHOD 1 -iframe -any website - -PRODUCTION - -METHOD 2 -Mobile app -Capacitor - -BUILT - -METHOD 3 -JavaScript embed -script + SRI - -PRODUCTION - -METHOD 4 -WordPress -Disciple Tools - -PROVEN · NOT YET LIVE - -METHOD 5 -Desktop -Tauri - -PROVEN · NOT YET LIVE +main → ONE build → one web folder → three outputs + +OUTPUT 1 +The web folder +iframe · JS embed · WordPress (for fun) + +PRODUCTION + +OUTPUT 2 +Mobile app +the same folder, wrapped by Capacitor + +BUILT + +OUTPUT 3 +Desktop app +the same folder, wrapped by Tauri + +PROVEN · NOT YET LIVE \ No newline at end of file diff --git a/documentation/reference.md b/documentation/reference.md index fd9d70550..7278eb9cc 100644 --- a/documentation/reference.md +++ b/documentation/reference.md @@ -14,7 +14,7 @@ Each bundle is one self-contained `.js` file that registers a custom HTML elemen | **Fix a legend or control for ALL maps** | edit it once in `library/` (`@map`); rebuild | | **Add a colour strategy** | a new file in `@map/colors/` (shared) or the bundle's `src/colors/` (map-private) | | **See my change while working** | `bun run dev` → http://localhost:5173/ (the Plugin Studio) | -| **Build everything** | `BUILD_ALLOW=1 bun run build` → `public/doxa-maps//.js` | +| **Build everything** | `BUILD_ALLOW=1 bun run build` → `public/dx-maps//.js` | | **Build just one bundle** | `BUNDLE= bun x vite build` | | **See a change on the served site** | rebuild, then refresh (the dev server never writes `public/`) | | **Repoint the API without a rebuild** | set `window.MAP_APP_API_URL` before the bundle loads | @@ -86,7 +86,7 @@ doxa-maps/ ├── deployment/ ← capacitor/ (mobile) · wordpress/ · src-tauri/ (desktop) ├── public/ ← THE SERVED WEB-ROOT, committed: │ ├── index.html the Plugin Studio host page -│ ├── doxa-maps// built bundles + manifest.json + leaf pages +│ ├── dx-maps// built bundles + manifest.json + leaf pages │ └── assets/ posters & data the maps fetch at runtime ├── vite.config.js ← multi-entry: discovers plugin-designs// folders └── vite.doxa-maps-build-config.json ← output tree name + copy destinations + token settings @@ -112,7 +112,7 @@ colours or language with **no rebuild**. // also per page via ?provider= or window.MAP_PROVIDER "assetsBase": "/assets", // where tables/, boundaries/, previews/ are served (see below) "shareUrl": "https://…", // optional; the WHOLE url the Share button links/embeds - "embedBaseUrl": "https://…/doxa-maps", // optional; tree root the Share button joins embed pages to + "embedBaseUrl": "https://…/dx-maps", // optional; tree root the Share button joins embed pages to "tabs": [ /* parameterized screens read this */ ] }'> ``` @@ -139,7 +139,7 @@ i18n, under a **distinct** tag, since `customElements.define()` is one-shot per | Script | What it does | |---|---| | `bun run dev` | Vite dev server with hot reload at http://localhost:5173. **`/` is the Plugin Studio**; `/__gallery` is the archived bundle index. Serves every bundle at its own html path and `/assets/*` from `public/assets/`. **Writes nothing to `public/`.** | -| `BUILD_ALLOW=1 bun run build` | `clean` → one `vite build` per `plugin-designs/*/` folder → `tree` → static gates. Refuses (exit 75) without `BUILD_ALLOW=1`, because it wipes `public/doxa-maps/` first. Takes a lock; `BUILD_LOCK_WAIT=600` queues instead of failing | +| `BUILD_ALLOW=1 bun run build` | `clean` → one `vite build` per `plugin-designs/*/` folder → `tree` → static gates. Refuses (exit 75) without `BUILD_ALLOW=1`, because it wipes `public/dx-maps/` first. Takes a lock; `BUILD_LOCK_WAIT=600` queues instead of failing | | `BUNDLE= bun x vite build` | builds ONE bundle without cleaning the rest of the tree | | `bun run build:watch` | rebuilds into `public/` on every change | | `bun run clean` | wipes and recreates the `public//` output tree | @@ -153,9 +153,9 @@ i18n, under a **distinct** tag, since `customElements.define()` is one-shot per by `BUNDLE=`) as a self-contained **IIFE**; the full build loops every folder. Sizes differ with what each bundle imports (simple ≈ 420 KB, research ≈ 1.3 MB). -**Where output goes.** One bundle → one file: `public/doxa-maps//.js`, with CSS injected by +**Where output goes.** One bundle → one file: `public/dx-maps//.js`, with CSS injected by JS (no separate `.css`). Beyond it, `tree` writes a standalone, iframe-able `index.html` per bundle, per -profile and per preset instance, plus `public/doxa-maps/manifest.json` with ready-made +profile and per preset instance, plus `public/dx-maps/manifest.json` with ready-made `embed.iframe` / `embed.script` snippets for every profile. Full design: [architecture/embeddability.md](./architecture/embeddability.md). Preset instances: [how-to.md](./how-to.md#ship-n-preset-variants-of-one-screen-instances). @@ -200,7 +200,7 @@ export MAPBOX_TOKEN=$(python3 -c "import json;print(json.load(open('vite.doxa-ma ```sh ( node internal/serve-strict.mjs --port 4950 & ) # serve the built tree, loopback only -B=http://127.0.0.1:4950/doxa-maps; ARGS="" +B=http://127.0.0.1:4950/dx-maps; ARGS="" for p in demo-map/demo-map cities-internet/connectivity-map doxa-research-map/research-map \ country-posters/countries-map doxa-simple-map/doxa-simple-map \ gospel-resources/resources-map my-upg-100-list/upg-100-list my-upg-100-list/upg-100-mfe; do @@ -239,7 +239,7 @@ Edit this one file, then rebuild. | Field | What it does | |---|---| -| `name` | output folder name (default `doxa-maps`) | +| `name` | output folder name (`dx-maps`; a config with no name falls back to `doxa-maps`) | | `paths` | **extra** folders to copy the whole built tree into — point at a host repo's `public/` and that host serves the maps. Empty ⇒ `public//` only | | `embedBaseUrl` | the public URL the tree is served from, for **absolute, paste-anywhere** snippets. Empty ⇒ relative URLs (position-independent; required for Capacitor) | | `token` | a `pk.` public Mapbox token baked into every page, so a map works on any host. It is public by design — protected by URL restrictions in the Mapbox dashboard, not by secrecy; `check:public-hygiene` allows `pk.` and fails on `sk.` | diff --git a/internal/check-shot-distinctness.mjs b/internal/check-shot-distinctness.mjs index e158158fc..9631c6404 100644 --- a/internal/check-shot-distinctness.mjs +++ b/internal/check-shot-distinctness.mjs @@ -50,21 +50,25 @@ if (!existsSync(SRC)) { const src = readFileSync(SRC, 'utf8') // Each `await shot('name', '/path', { ...opts })` — capture the opts blob so we can read `vp`. -const RE = /shot\(\s*'([^']+)'\s*,\s*'([^']*)'\s*(?:,\s*(\{[\s\S]*?\}))?\s*\)/g +const RE = /shot\(\s*'([^']+)'\s*,\s*(?:'([^']*)'|`([^`]*)`)\s*(?:,\s*(\{[\s\S]*?\}))?\s*\)/g const shots = [] for (const m of src.matchAll(RE)) { - const [, name, path, opts = ''] = m + const [, name, quoted, templated, opts = ''] = m + const path = quoted ?? templated // '…' or `/${TREE_NAME}/…` — compared as written, so either form is consistent const vp = opts.match(/vp:\s*\{\s*width:\s*(\d+)\s*,\s*height:\s*(\d+)/) shots.push({ name, path, vp: vp ? `${vp[1]}x${vp[2]}` : 'default' }) } // A matcher that finds almost nothing reads exactly like a clean pass — see check-quoted-scripts. -const MIN_EXPECTED = 8 +// FLOOR: every `await shot(` call must be parsed. If not, the matcher has drifted from the file (it did +// once: a codemod turned the paths into template literals and this gate saw 2 of 26), so the result is +// NOT MEASURED (exit 75), never a pass and never a product failure. +const CALLS = src.split('\n').filter((l, i, a) => /await shot\(/.test(l) && !/shot-floor: dynamic/.test(a[i - 1] || '')).length console.log(`SHOT-DISTINCTNESS: ${shots.length} shot(s) declared.`) -if (shots.length < MIN_EXPECTED) { - console.error(`\nSHOT-DISTINCTNESS: FAIL — only ${shots.length} shots parsed (expected >= ${MIN_EXPECTED}).`) - console.error(' The MATCHER is broken, not the shot list. Fix the regex before trusting this.') - process.exit(1) +if (shots.length !== CALLS) { + console.error(`\nSHOT-DISTINCTNESS: NOT MEASURED — parsed ${shots.length} of ${CALLS} shot( calls in readme-shots.mjs.`) + console.error(' The MATCHER is out of date with the file, not the shot list. Fix the regex, then re-run.') + process.exit(75) } const bySignature = new Map() diff --git a/internal/check-shot-freshness.mjs b/internal/check-shot-freshness.mjs index c727cffb0..725b86a6f 100644 --- a/internal/check-shot-freshness.mjs +++ b/internal/check-shot-freshness.mjs @@ -80,7 +80,19 @@ function stamp(file) { // Parse `await shot('name', '/path/...')` — the generator is the source of truth for its own map. const src = readFileSync(SHOTS_SRC, 'utf8') -const shots = [...src.matchAll(/shot\(\s*'([^']+)'\s*,\s*'([^']*)'/g)].map(m => ({ name: m[1], path: m[2] })) +// Paths are written either as '...' or as `/${TREE_NAME}/...` (the tree name comes from the build +// config); accept both and resolve the placeholder, or a template-literal shot would be silently skipped. +const shots = [...src.matchAll(/shot\(\s*'([^']+)'\s*,\s*(['`])([^'`]*)\2/g)] + .map(m => ({ name: m[1], path: m[3].replace(/\$\{TREE_NAME\}/g, TREE_NAME) })) +// FLOOR: every `await shot(` call must be parsed, or the gate would silently check fewer shots than +// exist (it did: after the TREE_NAME codemod it compared 2 of 26 and passed). Mismatch = NOT MEASURED. +{ + const calls = src.split('\n').filter((l, i, a) => /await shot\(/.test(l) && !/shot-floor: dynamic/.test(a[i - 1] || '')).length + if (shots.length !== calls) { + console.error(`SHOT-FRESHNESS: NOT MEASURED — parsed ${shots.length} of ${calls} shot( calls in readme-shots.mjs; the matcher is out of date with the file.`) + process.exit(75) + } +} const stale = [], missing = [], skipped = [] let checked = 0 diff --git a/internal/readme-shots.mjs b/internal/readme-shots.mjs index b7a16b716..4dc76dfed 100644 --- a/internal/readme-shots.mjs +++ b/internal/readme-shots.mjs @@ -390,6 +390,9 @@ await shot('prod-gospel-resources-phone', `/${TREE_NAME}/gospel-resources/resour await shot('exp-wall-of-people-groups', `/${TREE_NAME}/wall-of-people-groups/wall-of-people-groups/index.html`, { wait: 12000, minElements: 40 }) await shot('exp-prayer-walk-planner', `/${TREE_NAME}/prayer-walk-planner/prayer-walk-planner/index.html`, { wait: 12000, minElements: 40 }) +// ---- LANGUAGES: UPG-100 in Arabic (?lang=ar), right-to-left — the most completely translated map ---- +await shot('lang-arabic-upg100', `/${TREE_NAME}/my-upg-100-list/upg-100-list/index.html?lang=ar`, { wait: 12000, minElements: 200 }) + // ---- DEPLOY2: mobile — the UPG-100 list as the phone app ---- // The mobile example is UPG-100 (ruled 2026-09-24; Simple Research was retired as the example because // it was never built for phones). Shot once its owner marked the phone layout ready at 88a566f: @@ -403,6 +406,7 @@ await shot('deploy2-mobile-upg100', `/${TREE_NAME}/my-upg-100-list/upg-100-mfe/i // server is up: DXLIFE_URL=http://127.0.0.1: bun run shots. Without it the shot is skipped // and the committed image stays as it is. if (process.env.DXLIFE_URL) { + // shot-floor: dynamic — the URL comes from DXLIFE_URL at run time, so the gates cannot parse it await shot('deploy3-js-embed-dxlife', process.env.DXLIFE_URL.replace(/\/$/, '') + '/research', { vp: { width: 1440, height: 1100 }, wait: 16000, tokenFree: true, minElements: 300 }) } else if (!ONLY || ONLY.includes('deploy3-js-embed-dxlife')) {