Merge origin/development into gospel/gi-csv-guard
138
README.md
|
|
@ -6,7 +6,9 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His
|
|||
|
||||
## Bottom line up front
|
||||
|
||||
- **Why:** dx-maps exists to give disciples, churches and leaders what they need to bless the
|
||||
- **Why:** To Bless the Nations
|
||||
|
||||
- **Great Commission:** dx-maps exists to give disciples, churches and leaders what they need to bless the
|
||||
nations. We believe the deeper answer to the Great Commission is indigenous disciples, churches
|
||||
and leaders multiplying among their own peoples and blessing them, so that the Commission is
|
||||
fulfilled through love. The maps serve that in three ways: praying for the unreached peoples of
|
||||
|
|
@ -32,6 +34,12 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His
|
|||
|
||||
Built with love, with the prayer that these maps bless the nations.
|
||||
|
||||
**A blessing for you.** These maps and posters are given to bless the people who bless the nations.
|
||||
We love you, and we are thankful for your work. We bless you in Jesus' name.
|
||||
|
||||
Every poster is in Posterverse (`plugin-designs/posterverse/`): the UPG 100 List, Country Posters,
|
||||
Poster Design and Wall of People Groups. They are all in the Plugin Studio (`bun run dev`) too.
|
||||
|
||||
# dx-maps — one build, maps that embed everywhere
|
||||
|
||||
**Write a map once in `plugin-designs/<your-map>/`, run one build, and drop it on any site** —
|
||||
|
|
@ -62,7 +70,7 @@ capabilities, three outputs, one shared library, one build.
|
|||
| **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, 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 `development`** | [agent swarm](#build-it-with-an-agent-swarm) |
|
||||
| **One owner per plugin, a second reader for every change, one landing at a time** | [agent swarm](#build-it-with-an-agent-swarm) |
|
||||
|
||||
## For vibe coders: build, open the Studio, embed a map
|
||||
|
||||
|
|
@ -169,11 +177,11 @@ the Research Gallery, which DX Life publishes as **Deep Research**. An eighth pr
|
|||
|
||||
| Capability | Example | iframe | Mobile app | JavaScript embed | WordPress / Disciple Tools | Desktop |
|
||||
|---|---|:-:|:-:|:-:|:-:|:-:|
|
||||
| **5 — app shell** | Research Gallery | ✅ | ✅ | ✅ | 🧪 | 🧪 |
|
||||
| **4 — composite** | UPG-100 List | ✅ | ✅ | ✅ | 🧪 | 🧪 |
|
||||
| **3 — multi-view** | research map | ✅ | ✅ | ✅ | 🧪 | 🧪 |
|
||||
| **2 — presets** | Prayer Progress | ✅ | ✅ | ✅ | 🧪 | 🧪 |
|
||||
| **1 — single element** | all people groups | ✅ | ✅ | ✅ | 🧪 | 🧪 |
|
||||
| **5 — app shell** | Research Gallery | ✅ | ✅ | ✅ | 🧪 | ✅ |
|
||||
| **4 — composite** | UPG-100 List | ✅ | ✅ | ✅ | 🧪 | ✅ |
|
||||
| **3 — multi-view** | research map | ✅ | ✅ | ✅ | 🧪 | ✅ |
|
||||
| **2 — presets** | Prayer Progress | ✅ | ✅ | ✅ | 🧪 | ✅ |
|
||||
| **1 — single element** | all people groups | ✅ | ✅ | ✅ | 🧪 | ✅ |
|
||||
|
||||
<sub>✅ works today · 🧪 proven, not yet in production. Every plugin builds into the same folder, and that folder is what all five methods ship — so a capability never limits where a plugin can go.</sub>
|
||||
|
||||
|
|
@ -425,9 +433,9 @@ put it* and *how a page shows it*.
|
|||
|
||||
| # | Output | What it is | Status |
|
||||
|---|---|---|---|
|
||||
| **1** | **The web folder** `public/dx-maps/` | every map, each embeddable on its own, all working together — 1205 files, 154 MiB (about 160 MB) | ✅ production |
|
||||
| **1** | **The web folder** `public/dx-maps/` | every map, each embeddable on its own, all working together — 1367 files, 163 MiB (about 170 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 |
|
||||
| **3** | **Desktop app** | the same folder, wrapped as a desktop app by Tauri | ✅ works today |
|
||||
|
||||
### Output 1 — the web folder
|
||||
|
||||
|
|
@ -581,16 +589,16 @@ bun run phone # builds the app and installs it on a phone or emulator
|
|||
|
||||
</details>
|
||||
|
||||
### Output 3 — the desktop app 🧪 proven, not yet in production
|
||||
### Output 3 — the desktop app ✅ works today
|
||||
|
||||
**We got the JAMstack map app running as a desktop app.**
|
||||
|
||||

|
||||

|
||||
|
||||
**Rudder, the voice-to-text plugin, runs as its own desktop app too**, built with
|
||||
`bun run desktop:build rudder-voice` ([HOWTO-build-the-desktop-app](./documentation/HOWTO-build-the-desktop-app.md)).
|
||||
|
||||

|
||||

|
||||
|
||||
<details>
|
||||
<summary><b>More about the desktop app</b></summary>
|
||||
|
|
@ -687,11 +695,62 @@ your own page, set `"locale"` in its `profile-config`. Arabic reads right to lef
|
|||
## Build it with an agent swarm
|
||||
|
||||
**Many AI agents can build many plugins at once without breaking each other's work.** Every plugin
|
||||
has one agent building it, and one shepherd agent makes sure every agent does its job well. All the
|
||||
plugins live in one project with one shared library, and changes are added to `development`, the shared
|
||||
working version, one at a time. `main` is the production version, and it moves only when a release is published.
|
||||
has one agent that owns it, and changes are added to `development`, the shared working version, one
|
||||
at a time. `main` is the production version: it moves only when you publish a release.
|
||||
|
||||

|
||||
**Bring your own harness.** dx-maps works with whatever AI coding agent or agent harness you already
|
||||
use. The repo gives you the branch strategy below, the checks (`bun run gates:pre`) and the Plugin
|
||||
Studio; it does not ship a harness. Roles such as a coordinator that puts the landings in order, or a
|
||||
gate-keeper that reads each change before it lands, are jobs your harness gives to its agents.
|
||||
|
||||

|
||||
|
||||
<sub>[Open the diagram full size](documentation/readme-images/delegation-model.png): on a phone its small text needs it.</sub>
|
||||
|
||||
### Set up the branch strategy
|
||||
|
||||
1. **`main` is production.** It moves only when you publish a release (step 7).
|
||||
2. **`development` is the shared working branch.** Create it once, from `main`:
|
||||
```bash
|
||||
git fetch origin && git switch -c development origin/main && git push -u origin development
|
||||
```
|
||||
3. **One branch and worktree per plugin agent**, from `origin/development`, then install once in each:
|
||||
```bash
|
||||
git worktree add -b regions/work ../dx-maps-regions origin/development
|
||||
git worktree add -b upg100/work ../dx-maps-upg100 origin/development
|
||||
cd ../dx-maps-regions && bun install --frozen-lockfile
|
||||
```
|
||||
4. **One preview server per agent, each on its own port:**
|
||||
```bash
|
||||
bun x vite --port 5301 --strictPort # the next agent uses 5302, and so on
|
||||
```
|
||||
5. **Land one at a time, through a lock.** Catch up with `development`, run the checks, and push only
|
||||
if nothing fails (`gates:pre` exits non-zero when a check fails, so the push never runs then):
|
||||
```bash
|
||||
flock /tmp/dx-maps-development.lock sh -c '
|
||||
git fetch origin && git rebase origin/development &&
|
||||
bun run gates:pre && git push origin HEAD:development'
|
||||
```
|
||||
`flock` guards one machine. Agents on several machines need a shared lock, such as one your
|
||||
harness provides.
|
||||
6. **One agent owns the full build**; the others build only their own plugin:
|
||||
```bash
|
||||
BUILD_ALLOW=1 bun run build # the build owner: every plugin
|
||||
BUNDLE=<name> bun x vite build && bun run tree && bun run fix:sri # everyone else: one plugin
|
||||
```
|
||||
7. **Release:** fast-forward `main` to a `development` commit that passed the checks, tag it, and
|
||||
deploy from the tag:
|
||||
```bash
|
||||
git fetch origin && git switch main && git merge --ff-only origin/main
|
||||
git merge --ff-only <development-commit> # a commit that passed the checks
|
||||
bun run gates:pre # the checks must pass on exactly this commit
|
||||
git tag -a v1.0.0 -m "Release v1.0.0" && git push origin main v1.0.0
|
||||
```
|
||||
The tag's `public/dx-maps/` folder is the built site: deploy that
|
||||
([how to deploy](./documentation/DEPLOY.md)).
|
||||
|
||||
How to contribute, and the bar for shared code: [documentation/CONTRIBUTING.md](./documentation/CONTRIBUTING.md).
|
||||
Diagram source: [`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
|
||||
|
||||
<details>
|
||||
<summary><b>What problem does this solve? (in plain words)</b></summary>
|
||||
|
|
@ -702,15 +761,17 @@ map breaks at once**. And when several agents work in the same project at the sa
|
|||
each other — two agents edit the same file, one change quietly erases another, or half-finished work
|
||||
lands in the version everyone else is using.
|
||||
|
||||
So the project works like a busy kitchen with one head chef:
|
||||
So the project works like a busy kitchen:
|
||||
|
||||
1. **Each agent works in its own copy of the project** — its own counter, so nobody bumps anybody.
|
||||
2. **Before adding its work, each agent catches up**: it pulls in everyone else's latest work and
|
||||
fixes any clashes with its own. The agent fixes the clash, not the next person.
|
||||
3. **Only one agent adds work at a time.** They wait in a line (the *merge queue*), the checks run,
|
||||
and only then does the change go into `development`. `development` always works.
|
||||
4. **A shepherd agent watches everyone**: plugins built in the studio, shared parts really shared,
|
||||
and every change going through the line.
|
||||
1. **Each agent works in its own branch and copy of the project** — its own counter, so nobody bumps
|
||||
anybody. One owner per plugin, and one per shared file.
|
||||
2. **Only one change lands at a time.** Whoever holds the *landing lock* first catches up with
|
||||
everyone else's latest work, fixes any clash itself and runs every check; the change goes into
|
||||
`development` only if nothing fails. `development` always works.
|
||||
3. **Someone keeps order.** A coordinator can put the landings in order, and a gate-keeper can read
|
||||
each change by its content before it lands. Your harness gives these jobs to its agents.
|
||||
4. **`main` moves only when you publish a release**, after the checks pass on that exact version.
|
||||
A preview can follow `development`; the published site follows `main`.
|
||||
|
||||
That's how many plugins get built at the same time, in one project, without the maps breaking.
|
||||
|
||||
|
|
@ -727,31 +788,8 @@ That's how many plugins get built at the same time, in one project, without the
|
|||
| **worktree** | a private copy of the project for one agent to work in |
|
||||
| **merge conflict** | two changes touched the same lines; someone has to decide how to combine them |
|
||||
| **rebase** | catching your work up with everyone else's latest before adding it |
|
||||
| **merge queue** | the line agents wait in, so only one change goes into `main` at a time |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>The commands (for the curious)</b></summary>
|
||||
|
||||
```bash
|
||||
# one private copy (worktree) + one agent per plugin
|
||||
git worktree add -b regions/work ../dx-maps-regions origin/development
|
||||
git worktree add -b upg100/work ../dx-maps-upg100 origin/development
|
||||
|
||||
# one preview server per agent, each on its own port
|
||||
bun x vite --port 5301 --strictPort
|
||||
|
||||
# add work through the line: one at a time, caught up first, checks must pass
|
||||
flock /tmp/dx-maps-development.lock sh -c '
|
||||
git fetch origin && git rebase origin/development &&
|
||||
bun run gates:pre && git push origin HEAD:development'
|
||||
```
|
||||
|
||||
One agent owns the full build (it rebuilds everyone's output); the others check just their own
|
||||
plugin with `BUNDLE=<name> bun x vite build`. How to contribute, and the bar for shared code:
|
||||
[documentation/CONTRIBUTING.md](./documentation/CONTRIBUTING.md). Diagram source:
|
||||
[`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
|
||||
| **landing lock** | only one change lands on `development` at a time; its holder catches up and runs every check first |
|
||||
| **tag** | a fixed name for one release commit, such as `v1.0.0`, so you can always deploy exactly it |
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -773,7 +811,7 @@ and choose `"provider": "mapbox"` (see *No Mapbox token?* below). For Rudder's v
|
|||
`bash backend/rudder-engine/setup-runtime.sh` once; without Go, everything except voice works.
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://git.nuxtbase.app/doxa-open/doxa-maps.git # latest code only: seconds, not hundreds of MB of history
|
||||
git clone --depth 1 <your-repo-url> # latest code only: seconds, not hundreds of MB of history
|
||||
cd doxa-maps
|
||||
bun install --frozen-lockfile
|
||||
cp .env.example .env.local # optional: paste your pk. token after VITE_MAPBOX_TOKEN= (.env works too; .env.local wins)
|
||||
|
|
|
|||
BIN
data/posters/india-an.webp
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
data/posters/india-ap.webp
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
data/posters/india-ar.webp
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
data/posters/india-as.webp
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
data/posters/india-br.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
data/posters/india-ch.webp
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
data/posters/india-ct.webp
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
data/posters/india-dh.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
data/posters/india-dl.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
data/posters/india-ga.webp
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
data/posters/india-gj.webp
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
data/posters/india-hp.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
data/posters/india-hr.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
data/posters/india-jh.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
data/posters/india-jk.webp
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
data/posters/india-ka.webp
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
data/posters/india-kl.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
data/posters/india-la.webp
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
data/posters/india-ld.webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
data/posters/india-mh.webp
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
data/posters/india-ml.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
data/posters/india-mn.webp
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
data/posters/india-mp.webp
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
data/posters/india-or.webp
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
data/posters/india-pb.webp
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
data/posters/india-py.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
data/posters/india-rj.webp
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
data/posters/india-sk.webp
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
data/posters/india-tg.webp
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
data/posters/india-tn.webp
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
data/posters/india-tr.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
data/posters/india-up.webp
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
data/posters/india-ut.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
data/posters/india-wb.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
|
|
@ -42,7 +42,7 @@ const refuse = (msg) => (PUBLIC
|
|||
|
||||
// fetch() hands back the body DECOMPRESSED, so the upstream Content-Encoding and Content-Length no longer describe
|
||||
// what is sent on: forwarding them made browsers try to gunzip plain HTML, and PocketBase's dashboard never rendered
|
||||
// (rega, 2026-09-29). And PocketBase redirects to its own root (/_ -> /_/), which is put back under the mount (/pb/_/).
|
||||
// (measured 2026-09-29). And PocketBase redirects to its own root (/_ -> /_/), which is put back under the mount (/pb/_/).
|
||||
// A backend path as the backend will read it: percent-decoded, runs of slashes collapsed. The admin gates run on
|
||||
// THIS form, so /pb/%5F/ (PocketBase decodes it to /_/) meets the same check as /pb/_/ (studio, 2026-09-29).
|
||||
// Encoded slashes or backslashes and undecodable input are refused: nothing legitimate sends them.
|
||||
|
|
@ -179,7 +179,7 @@ Bun.serve({
|
|||
const bp = backendPath(p)
|
||||
if (bp === null) return text('bad path', 400)
|
||||
if (!p.startsWith('/api-prod/api/') || !bp.startsWith('/api-prod/api/')) return text('only /api/ is public', 403)
|
||||
// As on /pb: the superuser and every _system collection stay closed. No public client uses them (dx-base, V2 review).
|
||||
// As on /pb: the superuser and every _system collection stay closed. No public client uses them (a platform security review).
|
||||
if (bp.startsWith('/api-prod/api/collections/_')) return text('for admins only', 403)
|
||||
return proxy(req, url, '/api-prod/', 8091)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ building ones run the live source with hot reload; the showing ones serve the **
|
|||
## 1. Your laptop — `bun run dev`
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://git.nuxtbase.app/doxa-open/doxa-maps.git
|
||||
git clone --depth 1 <your-repo-url>
|
||||
cd doxa-maps
|
||||
bun install --frozen-lockfile
|
||||
bun run dev # → http://localhost:5173/ is the Plugin Studio
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 816 KiB After Width: | Height: | Size: 858 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 38 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1500" height="2030" viewBox="0 0 1500 2030" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1500" height="2086" viewBox="0 0 1500 2086" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
|
||||
<defs>
|
||||
<marker id="aA" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#2563eb"/></marker>
|
||||
<marker id="aS" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#7c3aed"/></marker>
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
<path id="agL" d="M 194,850 A 556,556 0 0 1 1306,850"/>
|
||||
<path id="mqL" d="M 679,884 A 71,71 0 0 0 821,884"/>
|
||||
</defs>
|
||||
<rect width="1500" height="2030" fill="#ffffff"/>
|
||||
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one studio, one agent per plugin, one shepherd</text>
|
||||
<rect width="1500" height="2086" fill="#ffffff"/>
|
||||
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one owner per plugin, one landing at a time</text>
|
||||
<circle cx="750" cy="850" r="540" fill="none" stroke="#2563eb" stroke-width="1.6" stroke-dasharray="3 7" opacity=".75"/>
|
||||
<circle cx="750" cy="850" r="355" fill="none" stroke="#15803d" stroke-width="2.2" opacity=".85"/>
|
||||
<circle cx="750" cy="850" r="606" fill="none" stroke="#7c3aed" stroke-width="2" stroke-dasharray="8 7" opacity=".6"/>
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
<circle cx="750" cy="850" r="120" fill="#fef2f2" stroke="#dc2626" stroke-width="3" stroke-dasharray="10 6"/>
|
||||
<path id="mqT" d="M 646,850 A 104,104 0 0 1 854,850" fill="none"/>
|
||||
<path id="mqB" d="M 644,850 A 106,106 0 0 0 856,850" fill="none"/>
|
||||
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">MERGE QUEUE · GUARD</textPath></text>
|
||||
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE AGENT AT A TIME</textPath></text>
|
||||
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">LANDING LOCK</textPath></text>
|
||||
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE LANDING AT A TIME</textPath></text>
|
||||
<rect x="617" y="846" width="26" height="20" rx="4" fill="#dc2626"/>
|
||||
<path d="M 622,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
|
||||
<circle cx="630" cy="856" r="3" fill="#fff"/>
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
<path d="M 862,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
|
||||
<circle cx="870" cy="856" r="3" fill="#fff"/>
|
||||
<circle cx="750" cy="850" r="72" fill="#166534" stroke="#14532d" stroke-width="3"/>
|
||||
<text x="750" y="852" text-anchor="middle" font-size="34" font-weight="800" fill="#fff">main</text>
|
||||
<text x="750" y="876" text-anchor="middle" font-size="12.5" fill="#dcfce7">the one branch</text>
|
||||
<text x="750" y="892" text-anchor="middle" font-size="12.5" fill="#dcfce7">always green</text>
|
||||
<text x="750" y="850" text-anchor="middle" font-size="18" font-weight="800" fill="#fff">development</text>
|
||||
<text x="750" y="872" text-anchor="middle" font-size="12.5" fill="#dcfce7">every change</text>
|
||||
<text x="750" y="888" text-anchor="middle" font-size="12.5" fill="#dcfce7">lands here</text>
|
||||
<text font-size="16" font-weight="800" fill="#15803d" letter-spacing="3"><textPath href="#plL" startOffset="50%" text-anchor="middle">PLUGINS</textPath></text>
|
||||
<text font-size="16" font-weight="800" fill="#2563eb" letter-spacing="3"><textPath href="#agL" startOffset="18%" text-anchor="middle">AGENTS</textPath></text>
|
||||
<line x1="862.1" y1="579.3" x2="822.7" y2="674.5" stroke="#15803d" stroke-width="1.4" opacity=".45"/>
|
||||
|
|
@ -117,67 +117,83 @@
|
|||
<circle cx="543.4" cy="351.1" r="40" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
|
||||
<text x="543.4" y="349.1" text-anchor="middle" font-size="12" font-weight="700" fill="#155e75">roaming</text>
|
||||
<text x="543.4" y="364.1" text-anchor="middle" font-size="12" font-weight="700" fill="#155e75">agent</text>
|
||||
<circle cx="750" cy="244" r="60" fill="#f5f3ff" stroke="#7c3aed" stroke-width="3.5"/>
|
||||
<text x="750" y="251" text-anchor="middle" font-size="19" font-weight="800" fill="#4c1d95">shepherd</text>
|
||||
<text x="834" y="192" font-size="17" fill="#4c1d95">one shepherd makes sure all plugin agents</text>
|
||||
<text x="834" y="216" font-size="17" fill="#4c1d95">are doing their job building the plugins well</text>
|
||||
<text x="750" y="1502" text-anchor="middle" font-size="20" font-weight="800" fill="#1e3a8a">each plugin has one agent responsible for building it</text>
|
||||
<rect x="36" y="110" width="300" height="280" rx="14" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
|
||||
<circle cx="64" cy="144" r="12" fill="#166534" stroke="#14532d" stroke-width="2.5"/>
|
||||
<text x="88" y="149" font-size="15" fill="#334155">main — the one branch</text>
|
||||
<circle cx="64" cy="180" r="12" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5"/>
|
||||
<text x="88" y="185" font-size="15" fill="#334155">merge queue — one at a time</text>
|
||||
<circle cx="64" cy="216" r="12" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
|
||||
<text x="88" y="221" font-size="15" fill="#334155">Plugin Studio — the one repo</text>
|
||||
<circle cx="64" cy="252" r="12" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="88" y="257" font-size="15" fill="#334155">plugin (badge = capability)</text>
|
||||
<circle cx="64" cy="288" r="12" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
|
||||
<text x="88" y="293" font-size="15" fill="#334155">Experimental shelf</text>
|
||||
<circle cx="64" cy="324" r="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
|
||||
<text x="88" y="329" font-size="15" fill="#334155">agent — builds one plugin</text>
|
||||
<circle cx="64" cy="360" r="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
|
||||
<text x="88" y="365" font-size="15" fill="#334155">shepherd — oversees all</text>
|
||||
<rect x="60" y="1536" width="1380" height="218" rx="20" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
|
||||
<text x="750" y="1578" text-anchor="middle" font-size="22" font-weight="800" fill="#7f1d1d">How main is protected — so many plugins can be built at once</text>
|
||||
<rect x="80.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="116.0" cy="1638" r="20" fill="#dc2626"/>
|
||||
<text x="116.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">1</text>
|
||||
<text x="148.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Work in your own copy</text>
|
||||
<text x="102.0" y="1686" font-size="16" fill="#475569">each agent builds its own plugin,</text>
|
||||
<text x="102.0" y="1710" font-size="16" fill="#475569">in its own private copy of the project</text>
|
||||
<text x="517.5" y="1677" text-anchor="middle" font-size="30" font-weight="800" fill="#dc2626">→</text>
|
||||
<rect x="545.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="581.0" cy="1638" r="20" fill="#dc2626"/>
|
||||
<text x="581.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">2</text>
|
||||
<text x="613.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Catch up, fix the clashes</text>
|
||||
<text x="567.0" y="1686" font-size="16" fill="#475569">pull in everyone else's latest work and</text>
|
||||
<text x="567.0" y="1710" font-size="16" fill="#475569">fix any clashes before adding yours</text>
|
||||
<text x="982.5" y="1677" text-anchor="middle" font-size="30" font-weight="800" fill="#dc2626">→</text>
|
||||
<rect x="1010.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="1046.0" cy="1638" r="20" fill="#dc2626"/>
|
||||
<text x="1046.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">3</text>
|
||||
<text x="1078.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Add changes one at a time</text>
|
||||
<text x="1032.0" y="1686" font-size="16" fill="#475569">the queue lets ONE agent in at a time;</text>
|
||||
<text x="1032.0" y="1710" font-size="16" fill="#475569">the checks must pass, main stays working</text>
|
||||
<circle cx="690" cy="248" r="56" fill="#f5f3ff" stroke="#7c3aed" stroke-width="3.5"/>
|
||||
<text x="690" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#4c1d95">coordinator</text>
|
||||
<circle cx="812" cy="248" r="56" fill="#f1f5f9" stroke="#334155" stroke-width="3.5"/>
|
||||
<text x="812" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#1e293b">gate-keeper</text>
|
||||
<text x="884" y="192" font-size="17" fill="#4c1d95">a coordinator puts the landings in order;</text>
|
||||
<text x="884" y="216" font-size="17" fill="#1e293b">a gate-keeper checks each one by its content</text>
|
||||
<text x="750" y="1502" text-anchor="middle" font-size="20" font-weight="800" fill="#1e3a8a">one owner per plugin, in its own branch · a second reader for every change</text>
|
||||
<rect x="36" y="110" width="304" height="280" rx="14" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
|
||||
<circle cx="64" cy="142" r="11" fill="#166534" stroke="#14532d" stroke-width="2.5"/>
|
||||
<text x="88" y="147" font-size="15" fill="#334155">development — shared branch</text>
|
||||
<circle cx="64" cy="174" r="11" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5"/>
|
||||
<text x="88" y="179" font-size="15" fill="#334155">landing lock — one at a time</text>
|
||||
<circle cx="64" cy="206" r="11" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
|
||||
<text x="88" y="211" font-size="15" fill="#334155">Plugin Studio — the one repo</text>
|
||||
<circle cx="64" cy="238" r="11" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="88" y="243" font-size="15" fill="#334155">plugin (badge = capability)</text>
|
||||
<circle cx="64" cy="270" r="11" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
|
||||
<text x="88" y="275" font-size="15" fill="#334155">Experimental shelf</text>
|
||||
<circle cx="64" cy="302" r="11" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
|
||||
<text x="88" y="307" font-size="15" fill="#334155">agent — owns one plugin</text>
|
||||
<circle cx="64" cy="334" r="11" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
|
||||
<text x="88" y="339" font-size="15" fill="#334155">coordinator — orders landings</text>
|
||||
<circle cx="64" cy="366" r="11" fill="#f1f5f9" stroke="#334155" stroke-width="2.5"/>
|
||||
<text x="88" y="371" font-size="15" fill="#334155">gate-keeper — checks landings</text>
|
||||
<rect x="60" y="1536" width="1380" height="232" rx="20" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
|
||||
<text x="750" y="1578" text-anchor="middle" font-size="22" font-weight="800" fill="#7f1d1d">How a change lands — one at a time, so many plugins can be built at once</text>
|
||||
<rect x="80.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="114.0" cy="1634" r="18" fill="#dc2626"/>
|
||||
<text x="114.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">1</text>
|
||||
<text x="142.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Own branch</text>
|
||||
<text x="100.0" y="1680" font-size="15" fill="#475569">each owner builds its plugin</text>
|
||||
<text x="100.0" y="1702" font-size="15" fill="#475569">in its own branch and copy;</text>
|
||||
<text x="100.0" y="1724" font-size="15" fill="#475569">one owner per shared file</text>
|
||||
<text x="405.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
|
||||
<rect x="425.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="459.0" cy="1634" r="18" fill="#dc2626"/>
|
||||
<text x="459.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">2</text>
|
||||
<text x="487.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Second reader</text>
|
||||
<text x="445.0" y="1680" font-size="15" fill="#475569">another agent reads every</text>
|
||||
<text x="445.0" y="1702" font-size="15" fill="#475569">change by its content</text>
|
||||
<text x="445.0" y="1724" font-size="15" fill="#475569">before it may land</text>
|
||||
<text x="750.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
|
||||
<rect x="770.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="804.0" cy="1634" r="18" fill="#dc2626"/>
|
||||
<text x="804.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">3</text>
|
||||
<text x="832.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">One at a time</text>
|
||||
<text x="790.0" y="1680" font-size="15" fill="#475569">in the lock: merge development,</text>
|
||||
<text x="790.0" y="1702" font-size="15" fill="#475569">rebuild, run every check;</text>
|
||||
<text x="790.0" y="1724" font-size="15" fill="#475569">push only if nothing fails</text>
|
||||
<text x="1095.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
|
||||
<rect x="1115.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
|
||||
<circle cx="1149.0" cy="1634" r="18" fill="#dc2626"/>
|
||||
<text x="1149.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">4</text>
|
||||
<text x="1177.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Promote to main</text>
|
||||
<text x="1135.0" y="1680" font-size="15" fill="#475569">main is production: it moves</text>
|
||||
<text x="1135.0" y="1702" font-size="15" fill="#475569">only when the owner approves,</text>
|
||||
<text x="1135.0" y="1724" font-size="15" fill="#475569">after full checks on that version</text>
|
||||
<line x1="750" y1="956" x2="750" y2="1786" stroke="#334155" stroke-width="2.5" stroke-dasharray="2 5" opacity=".0"/>
|
||||
<rect x="60" y="1780" width="1380" height="230" rx="20" fill="#f8fafc" stroke="#cbd5e1" stroke-width="2"/>
|
||||
<text x="750" y="1824" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">main → ONE build → one web folder → three outputs</text>
|
||||
<rect x="80.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="290.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 1</text>
|
||||
<text x="290.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">The web folder</text>
|
||||
<text x="290.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">iframe · JS embed · WordPress (for fun)</text>
|
||||
<rect x="190.0" y="1946" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
|
||||
<text x="290.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">PRODUCTION</text>
|
||||
<rect x="540.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="750.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 2</text>
|
||||
<text x="750.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Mobile app</text>
|
||||
<text x="750.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Capacitor</text>
|
||||
<rect x="650.0" y="1946" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
|
||||
<text x="750.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">BUILT</text>
|
||||
<rect x="1000.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#d97706" stroke-width="2.5"/>
|
||||
<text x="1210.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 3</text>
|
||||
<text x="1210.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Desktop app</text>
|
||||
<text x="1210.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Tauri</text>
|
||||
<rect x="1110.0" y="1946" width="200" height="26" rx="13" fill="#d97706" opacity=".13"/>
|
||||
<text x="1210.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#d97706">PROVEN · NOT YET LIVE</text>
|
||||
<rect x="60" y="1812" width="1380" height="254" rx="20" fill="#f8fafc" stroke="#cbd5e1" stroke-width="2"/>
|
||||
<text x="750" y="1856" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">main → ONE build → one web folder → three outputs</text>
|
||||
<text x="750" y="1882" text-anchor="middle" font-size="16" fill="#475569">the dev preview follows development · the published site follows main</text>
|
||||
<rect x="80.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="290.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 1</text>
|
||||
<text x="290.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">The web folder</text>
|
||||
<text x="290.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">iframe · JS embed · WordPress (for fun)</text>
|
||||
<rect x="190.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
|
||||
<text x="290.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">PRODUCTION</text>
|
||||
<rect x="540.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="750.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 2</text>
|
||||
<text x="750.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Mobile app</text>
|
||||
<text x="750.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Capacitor</text>
|
||||
<rect x="650.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
|
||||
<text x="750.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">BUILT</text>
|
||||
<rect x="1000.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
|
||||
<text x="1210.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 3</text>
|
||||
<text x="1210.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Desktop app</text>
|
||||
<text x="1210.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Tauri</text>
|
||||
<rect x="1110.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
|
||||
<text x="1210.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">WORKING</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
|
|
@ -13,7 +13,7 @@
|
|||
> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type`
|
||||
> declares tiers 3/4/5.
|
||||
|
||||
Generated from 26 bundle(s).
|
||||
Generated from 27 bundle(s).
|
||||
|
||||
### One canonical exemplar per tier (generated — quote these)
|
||||
|
||||
|
|
@ -51,6 +51,8 @@ Generated from 26 bundle(s).
|
|||
| `gospel-resources` | `resources-map` | `single` |
|
||||
| `my-upg-100-list` | `poster-wall` | `single` |
|
||||
| `my-upg-100-list` | `upg-100-list` | `single` |
|
||||
| `poster-design` | `poster-sizes` | `single` |
|
||||
| `poster-design` | `poster-styles` | `single` |
|
||||
| `prayer-walk-planner` | `prayer-walk-planner` | `single` |
|
||||
| `regions-dashboard` | `region-dashboards` | `single` |
|
||||
| `regions-dashboard` | `regions-list` | `single` |
|
||||
|
|
@ -83,6 +85,7 @@ Generated from 26 bundle(s).
|
|||
| `dashboard-plus-grid` | `dashboard-plus-grid` | `nested-elements-mfe` |
|
||||
| `gospel-resources` | `gospel-dashboard` | `nested-elements-mfe` |
|
||||
| `my-upg-100-list` | `upg-100-mfe` | `nested-elements-mfe` |
|
||||
| `poster-design` | `poster-design-mfe` | `nested-elements-mfe` |
|
||||
| `regions-dashboard` | `regions-mfe` | `nested-elements-mfe` |
|
||||
| `research-map-plus-grid` | `research-map-plus-grid` | `nested-elements-mfe` |
|
||||
| `stack-demo` | `stack-demo` | `nested-elements-mfe` |
|
||||
|
|
|
|||
|
|
@ -86,6 +86,32 @@ ok(dry(formatClipboard(arabic, { color: '#0072b2' })) !== 'ok', 'NEGATIVE CONTRO
|
|||
const indiaText = X.selectionClipboard([{ key: 'adm1:Mahārāshtra', name: 'Mahārāshtra', count: 54, kind: 'adm1' }, { key: 'adm1:Goa', name: 'Goa', count: 5, kind: 'adm1' }], {})
|
||||
ok(dry(indiaText) === 'ok', `apply-grouping's dry run refuses an India state selection: ${dry(indiaText)}`, 'an India state selection resolves too (shape names are canonical)')
|
||||
|
||||
// ── A3. the client's regroupable() agrees with apply-grouping's own verdict ─────────────────────────────
|
||||
// Copy warns when its first line will not regroup (a mix of kinds, or a legend zone). The warning is only honest if
|
||||
// regroupable() says exactly what the tool's dry run says, so each case is run through BOTH.
|
||||
const T = (key, name, count, kind, country) => ({ key, name, count, kind, ...(country ? { country } : {}) })
|
||||
const REGROUP_CASES = {
|
||||
'India states': [T('adm1:Mahārāshtra', 'Mahārāshtra', 54, 'adm1', 'IND'), T('adm1:Goa', 'Goa', 5, 'adm1', 'IND')],
|
||||
'a China province': [T('adm1:Gansu Province', 'Gansu Province', 3, 'adm1', 'CHN')],
|
||||
'countries': [T('adm0:NPL', 'Nepal', 34, 'adm0'), T('adm0:BTN', 'Bhutan', 13, 'adm0')],
|
||||
'a state + a country': [T('adm1:Goa', 'Goa', 5, 'adm1', 'IND'), T('adm0:NPL', 'Nepal', 34, 'adm0')],
|
||||
'India + China': [T('adm1:Goa', 'Goa', 5, 'adm1', 'IND'), T('adm1:Gansu Province', 'Gansu Province', 3, 'adm1', 'CHN')],
|
||||
'a legend zone': [T('zone:test', 'Test Zone', 12, 'zone')],
|
||||
}
|
||||
const disagree = []
|
||||
for (const [name, sel] of Object.entries(REGROUP_CASES)) {
|
||||
const tool = dry(X.selectionClipboard(sel, {})) === 'ok'
|
||||
if (X.regroupable(sel) !== tool) disagree.push(`${name}: regroupable ${X.regroupable(sel)}, apply-grouping ${tool ? 'accepts' : 'refuses'}`)
|
||||
}
|
||||
// …and the COUNTRY an ADM1 territory needs for that verdict is always known: from the feature's own props on a
|
||||
// map tap, else from the baked table (a props-only toggle once left it null, which read as a false warning).
|
||||
const { resolveTerritory, ADM1_COUNTS, ADM1_COUNTRY } = await import(pathToFileURL(P('plugin-designs/my-upg-100-list/data/territory-counts.js')).href)
|
||||
const noCountry = Object.keys(ADM1_COUNTS).filter((n) => !['IND', 'CHN'].includes(ADM1_COUNTRY[n]))
|
||||
ok(!noCountry.length && resolveTerritory({ shapeName: 'Mahārāshtra' }).country === 'IND' && resolveTerritory({ shapeName: 'Guizhou Province' }).country === 'CHN',
|
||||
`an ADM1 territory without a known country: ${noCountry.slice(0, 5).join(', ') || 'a props-only toggle'}`, 'every ADM1 name resolves its country, even from a props-only toggle')
|
||||
ok(!disagree.length, `Copy's regroup warning disagrees with apply-grouping: ${disagree.join('; ')}`,
|
||||
`the regroup warning matches apply-grouping's own verdict in all ${Object.keys(REGROUP_CASES).length} cases (India, China, countries, and the three it refuses)`)
|
||||
|
||||
// ── B. the CSV ───────────────────────────────────────────────────────────────────────────────────
|
||||
function parseCsv(s) { // RFC 4180: quoted fields, doubled quotes, CRLF records
|
||||
const rows = []; let row = [], cell = '', q = false
|
||||
|
|
@ -124,6 +150,17 @@ const wired = {
|
|||
'the prefetch on batch mode': /if \(next\) selectionRecordsLoad\(\)/.test(vue),
|
||||
'one detection for every action': (vue.match(/await selectionPeople\(\)/g) || []).length >= 2 && /selectionPeople\(\)\.then/.test(vue),
|
||||
// India's states resolve through the served table (library/data/indiaStates.js), fetched only once a state is selected
|
||||
// In batch mode a tap on a pin opens NO detail sheet (it covered the whole HUD on a phone); the tapestry
|
||||
// listener under it toggles the territory. The real-tap proof is rega's; this pins the guard in the source.
|
||||
'a pin tap in batch mode opens no sheet': /map\.on\('click', 'upg-pins-hit', \(e\) => \{[\s\S]{0,700}?if \(batchMode\.value\) return\s*\n\s*const f = e && e\.features/.test(vue),
|
||||
// …and that tap toggles the territory EXACTLY ONCE: the tapestry listener already does it, so the pin handler
|
||||
// must not toggle a second time (select-then-deselect in one tap).
|
||||
'the pin handler adds no second toggle': (() => {
|
||||
const at = vue.indexOf("map.on('click', 'upg-pins-hit', (e) => {"); if (at < 0) return false
|
||||
const body = vue.slice(at, vue.indexOf('\n })', at))
|
||||
return !/toggleTerritory|toggleByProps|toggleZoneSelection/.test(body)
|
||||
})(),
|
||||
'the regroup warning on Copy': /const warn = regroupable\(sel\) \? '' : t\('upgMap\.copyNotRegroupable'\)/.test(vue),
|
||||
'India states detected from the served table': /import \{ loadIndiaStates \} from '@map\/data\/indiaStates\.js'/.test(vue)
|
||||
&& /india\.byShapeName\.get\(t\.name\)/.test(vue) && /t\.kind === 'adm1'\) indiaStates\(\)\.catch/.test(vue),
|
||||
}
|
||||
|
|
@ -133,7 +170,7 @@ ok(!missingWires.length, `the HUD is missing: ${missingWires.join(', ')}`, 'the
|
|||
// ── D. the strings, ×11 ──────────────────────────────────────────────────────────────────────────
|
||||
const LANGS = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
|
||||
const KEYS = {
|
||||
upgMap: ['copySelection', 'exportCsv', 'selectionColourGroup', 'selectionColour', 'customColour', 'copiedSelection', 'copiedTerritoriesOnly', 'csvSaved', 'csvFailed'] }
|
||||
upgMap: ['copySelection', 'exportCsv', 'copyNotRegroupable', 'selectionColourGroup', 'selectionColour', 'customColour', 'copiedSelection', 'copiedTerritoriesOnly', 'csvSaved', 'csvFailed'] }
|
||||
const cat = (l, f) => JSON.parse(readFileSync(P('library/i18n/locales', l, `${f}.json`), 'utf8'))
|
||||
const bad = []
|
||||
for (const [f, keys] of Object.entries(KEYS)) {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ const LINKS = [
|
|||
// Composites whose children deliberately exchange nothing. The reason is printed on every run.
|
||||
const NO_LINKS = {
|
||||
'stack-demo': 'a demonstration stack: page knobs only; dashboard-plus-grid is the linked version',
|
||||
'poster-design': 'two independent views of the poster templates (Styles, Sizes); neither pane drives the other',
|
||||
}
|
||||
|
||||
// ── D: late getCurrentInstance() sites already routed to an owner ─────────────────────────────
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@
|
|||
* owner; counting it as a pass would be false. It exits 75 NOT MEASURED instead, NAMING every
|
||||
* distinct blocked request path and the token's source, so the next reader can tell which
|
||||
* credential was in play without the gate ever printing it.
|
||||
* ✗ AN EARLIER VERSION CALLED THIS "TILESET ENTITLEMENT" AND THAT WAS WRONG. Measured 2026-09-29 by
|
||||
* rega on this gate's own token: /v4/ streets-v8 and country-boundaries both return 200, with no
|
||||
* Referer and from research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
|
||||
* ✗ AN EARLIER VERSION CALLED THIS "TILESET ENTITLEMENT" AND THAT WAS WRONG. Measured 2026-09-29 with
|
||||
* this gate's own token: /v4/ streets-v8 and country-boundaries both return 200, with no
|
||||
* Referer and from our research and maps-dev hosts, doxa.life, 127.0.0.1 and
|
||||
* example.com. The token is unrestricted and entitled. The old claim rested on three no-Referer
|
||||
* probes, which cannot distinguish a URL-restricted token from a missing entitlement. Whatever 403s
|
||||
* is a different request or a different token — which is exactly what the gate must now report
|
||||
|
|
@ -102,9 +102,9 @@ async function serve(dir) {
|
|||
// rather than FAILED.
|
||||
//
|
||||
// ✗ WHY THIS NO LONGER CLAIMS "ENTITLEMENT" (corrected 2026-09-29). It used to report a 403 here
|
||||
// as "tileset, not the embed", on the strength of three no-Referer probes. MEASURED since, by rega,
|
||||
// as "tileset, not the embed", on the strength of three no-Referer probes. MEASURED since,
|
||||
// with the very token this gate runs on: /v4/ streets-v8 AND country-boundaries return 200 — with
|
||||
// no Referer, and with research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
|
||||
// no Referer, and with our research and maps-dev hosts, doxa.life, 127.0.0.1 and
|
||||
// example.com. The token is unrestricted and entitled, so whatever 403s here is a DIFFERENT
|
||||
// request (a style, sprite, glyphs, a composite with terrain) or a different token than the one
|
||||
// tested. Three no-Referer cases cannot tell a URL-restricted token from a missing entitlement,
|
||||
|
|
@ -168,7 +168,7 @@ async function measure(page, url, provider) {
|
|||
: c.classList.contains('maplibregl-canvas') ? 'maplibre' : 'other'
|
||||
// ★ EVERY map, not the two this repo happens to publish. Reading only
|
||||
// window.__rcmMap/__rdashMap made 5 of the 7 production leaves report -1 and then FAIL as
|
||||
// "0 rendered features" even when they had drawn (measured by rega, 2026-09-29). The hook
|
||||
// "0 rendered features" even when they had drawn (measured 2026-09-29). The hook
|
||||
// installed before navigation collects every Map the page constructs, whatever it calls it.
|
||||
const maps = (window.__gateMaps || []).slice()
|
||||
const published = window.__rcmMap || window.__rdashMap || null
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
* H (no previewSrc, the stored webp fails) the first error retries the tree's own copy
|
||||
* (<tree>/assets/previews/<id>.webp) and only the second shows the placeholder.
|
||||
* I (previewSrc, the generated image fails) one error shows the placeholder: there is no second base.
|
||||
* J (section heads) a section with people (total > 0) reads "<n people groups> · <items>", as it always has;
|
||||
* a section with none (a template gallery) reads "<items>" alone: no 0 count, no " · ".
|
||||
* K (sectionItemCountKey) a section the callback names is counted in its own words and number ({ key, n });
|
||||
* a section it answers null for keeps the default count.
|
||||
* embedBase.js is served to the gate as if from a BUILT tree (<tree>/<bundle>/<bundle>.js), because
|
||||
* PosterWall's second base is derived from that URL; from source it is empty and H could not fail.
|
||||
* A vacuous run (no .tape-h found, or no items rendered) FAILS.
|
||||
|
|
@ -337,6 +341,39 @@ try {
|
|||
}
|
||||
w.app.unmount()
|
||||
}
|
||||
|
||||
// J: a section head names its people-group total only when there is one
|
||||
{
|
||||
const groups = { j1: 'People', j2: 'People', j3: 'Templates', j4: 'Templates' }
|
||||
const root = node('root')
|
||||
const app = createApp(PosterWall, { instanceId: 'gate-j', groupBy: (x) => groups[x.id],
|
||||
items: [{ id: 'j1', label: 'Jone', count: 5 }, { id: 'j2', label: 'Jtwo', count: 2 }, { id: 'j3', label: 'Jthree' }, { id: 'j4', label: 'Jfour' }] })
|
||||
app.mount(root); await rc.nextTick()
|
||||
const heads = []
|
||||
walk(root, (n) => { if (hasClass(n, 'sec-count')) heads.push(textOf(n).trim()) })
|
||||
if (heads.length !== 2) fail(`J: expected 2 section heads, found ${heads.length}`)
|
||||
const withPeople = heads.find((h) => /\b7\b/.test(h))
|
||||
const without = heads.find((h) => !/\b7\b/.test(h))
|
||||
if (!withPeople || !/·/.test(withPeople) || !/people/i.test(withPeople)) fail(`J: a section with people should read "<n> people groups · <items>", got ${JSON.stringify(withPeople)}`)
|
||||
if (!without || /·/.test(without) || /\b0\b/.test(without) || /people/i.test(without)) fail(`J: a section with no people should read its items alone, got ${JSON.stringify(without)}`)
|
||||
app.unmount()
|
||||
}
|
||||
|
||||
// K: one section counted in its own words (India: its states, not countries); the others unchanged
|
||||
{
|
||||
const groups = { k1: 'India', k2: 'India', k3: 'India', k4: 'Asia', k5: 'Asia' }
|
||||
const root = node('root')
|
||||
const app = createApp(PosterWall, { instanceId: 'gate-k', groupBy: (x) => groups[x.id],
|
||||
sectionItemCountKey: (sec) => (sec.name === 'India' ? { key: 'regions.countryCount', n: 5 } : null),
|
||||
items: Object.keys(groups).map((id, i) => ({ id, label: id, count: i + 1 })) })
|
||||
app.mount(root); await rc.nextTick()
|
||||
const heads = {}
|
||||
walk(root, (n) => { if (hasClass(n, 'sec-head')) heads[textOf(n).includes('India') ? 'India' : 'Asia'] = textOf(n) })
|
||||
if (!/\b5 countries\b/.test(heads.India || '')) fail(`K: the named section should count 5 in its own key, got ${JSON.stringify(heads.India)}`)
|
||||
if (/\b3 list/.test(heads.India || '')) fail('K: the named section still shows the default count')
|
||||
if (!/\b2 lists?\b/.test(heads.Asia || '')) fail(`K: an unnamed section should keep the default count, got ${JSON.stringify(heads.Asia)}`)
|
||||
app.unmount()
|
||||
}
|
||||
} catch (e) {
|
||||
fail(`threw: ${e?.stack || e}`)
|
||||
} finally {
|
||||
|
|
@ -348,5 +385,5 @@ if (failures.length) {
|
|||
console.error(`POSTER-WALL-HOST-SEAMS: FAIL (${failures.length})\n - ${failures.join('\n - ')}`)
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
console.log(`POSTER-WALL-HOST-SEAMS: PASS (9 arms, ${buttonsSeen} stickers rendered; defaults unchanged: the Show-on-map toggle and the stored webp; headerAction emits, previewSrc is lazy, one at a time, cached and bounded)`)
|
||||
console.log(`POSTER-WALL-HOST-SEAMS: PASS (11 arms, ${buttonsSeen} stickers rendered; defaults unchanged: the Show-on-map toggle and the stored webp; headerAction emits, previewSrc is lazy, one at a time, cached and bounded)`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,19 @@ const HAND_VOCAB = {
|
|||
'Project 700',
|
||||
'How this list was found',
|
||||
],
|
||||
// The phrases that NAME a series. Derived vocabulary drops any sentence two series share, so a verbatim
|
||||
// copy of one series' framing into another would go unseen without these (the copy becomes "shared").
|
||||
country: [
|
||||
'PRAY FOR THIS COUNTRY',
|
||||
'Country Prayer Map',
|
||||
'one sheet for each country',
|
||||
'which country God has put',
|
||||
],
|
||||
indiaState: [
|
||||
'PRAY FOR THIS STATE',
|
||||
'State Prayer Map',
|
||||
'one sheet for each state',
|
||||
'which state God has put',
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -100,13 +111,13 @@ function deriveVocab(series) {
|
|||
...strings(e.poster || {}),
|
||||
].map((s) => s.trim()).filter((s) => s.length >= 12))
|
||||
}
|
||||
// Drop anything two series share — it cannot identify either of them.
|
||||
for (const a of ids) {
|
||||
for (const b of ids) {
|
||||
if (a === b) continue
|
||||
for (const s of own[b]) own[a].delete(s)
|
||||
}
|
||||
}
|
||||
// Drop anything two series share — it cannot identify either of them. The shared set is found on the
|
||||
// ORIGINAL sets first: deleting while still comparing pairs removed a shared string from the earlier
|
||||
// series only, so the later one kept it as a "fingerprint" and the earlier one failed for carrying it.
|
||||
// Invisible with two series; a third (indiaState, sharing country's networks) exposed it.
|
||||
const shared = new Set()
|
||||
for (const a of ids) for (const b of ids) if (a !== b) for (const s of own[a]) if (own[b].has(s)) shared.add(s)
|
||||
for (const id of ids) for (const s of shared) own[id].delete(s)
|
||||
const out = {}
|
||||
for (const id of ids) out[id] = [...new Set([...(HAND_VOCAB[id] || []), ...own[id]])]
|
||||
return out
|
||||
|
|
@ -120,8 +131,10 @@ function* strings(v) {
|
|||
}
|
||||
|
||||
/** The text a series OWNS: its overrides and its poster furniture. Not its prose documentation. */
|
||||
// The poster header block too: that is where the country poster once printed the UPG-100 side box, and a scan
|
||||
// of strings + networks alone could never see a series' framing copied into another's poster text.
|
||||
function seriesText(entry) {
|
||||
return [...strings(entry.strings || {}), ...strings(entry.networks || [])]
|
||||
return [...strings(entry.strings || {}), ...strings(entry.networks || []), ...strings(entry.poster || {})]
|
||||
}
|
||||
|
||||
function checkDeclarations(series) {
|
||||
|
|
@ -208,6 +221,9 @@ function selfTest() {
|
|||
['upg100 loses its funnel', (s) => { s.upg100.funnel = false }],
|
||||
['a series loses a networks block', (s) => { s.country.networks.pop() }],
|
||||
['dataDir points nowhere', (s) => { s.country.dataDir = 'no/such/folder' }],
|
||||
['indiaState keeps the country side box', (s) => { s.indiaState.poster.sideBoxHeading = 'What is a Country Prayer Map?' }],
|
||||
['country keeps the state network line', (s) => { s.country.networks[0][1] = s.indiaState.networks[0][1] }],
|
||||
['indiaState keeps PRAY FOR THIS COUNTRY', (s) => { s.indiaState.strings.map_page_kicker = 'PRAY FOR THIS COUNTRY' }],
|
||||
]
|
||||
let pass = 0
|
||||
for (const [name, mutate] of cases) {
|
||||
|
|
@ -224,6 +240,19 @@ function selfTest() {
|
|||
fails.length = 0
|
||||
if (pass !== cases.length) fail(`self-test: caught ${pass}/${cases.length} known-bad inputs`)
|
||||
else ok(`self-test: caught all ${cases.length} known-bad inputs`)
|
||||
// Shared wording is nobody's fingerprint, in EITHER order: three series where the first and last share a
|
||||
// sentence must neither flag it nor lose their own phrases.
|
||||
const SHARED = 'A sentence two series both print, word for word.'
|
||||
const tri = {
|
||||
a: { strings: { k: 'Only series a says this phrase.' }, networks: [['h', SHARED]] },
|
||||
b: { strings: { k: 'Only series b says this phrase.' }, networks: [] },
|
||||
c: { strings: { k: 'Only series c says this phrase.' }, networks: [['h', SHARED]] },
|
||||
}
|
||||
const v = deriveVocab(tri)
|
||||
const leaked = Object.entries(v).filter(([, p]) => p.includes(SHARED)).map(([id]) => id)
|
||||
const lost = ['a', 'b', 'c'].filter((id) => !v[id].includes(`Only series ${id} says this phrase.`))
|
||||
if (leaked.length || lost.length) fail(`self-test: shared wording kept as vocabulary of [${leaked}] / own phrases lost for [${lost}]`)
|
||||
else ok('self-test: shared wording is dropped from every series, whatever their order')
|
||||
}
|
||||
|
||||
// ── run ──────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -167,13 +167,18 @@ const run = (rule, src) => rule.test ? rule.test(src) : [...src.matchAll(rule.re
|
|||
const lineOf = (src, idx) => src.slice(0, idx).split('\n').length
|
||||
|
||||
// ── self-test: every rule must see a planted violation ─────────────────────────────────────────
|
||||
// The TOK-LITERAL sample is ASSEMBLED HERE, never written as one literal: a committed string shaped like a
|
||||
// real Mapbox token is itself what that rule forbids, and reads as a leaked credential to any secret scan.
|
||||
// It is plainly fake ('example, not a token'), yet shaped like one: pk. + base64url JSON + . + a tail, which
|
||||
// is all the rule's regex looks for, so the self-test still proves the rule fires.
|
||||
const FAKE_TOKEN = 'pk.' + Buffer.from('{"u":"example","note":"not-a-token"}').toString('base64url') + '.' + 'x'.repeat(22)
|
||||
const PLANTED = `
|
||||
map.setStyle(isDark ? 'mapbox://styles/mapbox/dark-v11' : 'x')
|
||||
const m2 = new gl.Map({ container: el, style: 'mapbox://styles/mapbox/light-v11' })
|
||||
const p = new mapboxgl.Popup()
|
||||
useMapInstance({ container: el, provider: 'mapbox' })
|
||||
const tk = window.DOXA_TOKEN
|
||||
const t = 'pk.eyJ1IjoiZXhhbXBsZSIsImEiOiJjbGFudGVkIn0'
|
||||
const t = '${FAKE_TOKEN}'
|
||||
n.toLocaleString('en-US')
|
||||
const r = await fetch(base + '/api/people-groups/list?fields=slug')
|
||||
const dsm = new DataSourceManager({ locale: 'en' })
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ function readBuildConfig() {
|
|||
// EMBED_BASE_URL overrides the committed embedBaseUrl, the same way DOXA_SYNC_PATHS extends paths:
|
||||
// a host that builds its own tree (maps-dev's /cdn build inherits the container env) bakes absolute
|
||||
// snippet URLs without a committed config change. It is the base WITHOUT the tree name
|
||||
// (https://maps-dev.nuxtbase.app/cdn -> .../cdn/<name>/...). check-embed-targets-exist reads the same.
|
||||
// (https://<host>/cdn -> .../cdn/<name>/...). check-embed-targets-exist reads the same.
|
||||
const embedBaseUrl = ((process.env.EMBED_BASE_URL || '').trim()
|
||||
|| (typeof cfg.embedBaseUrl === 'string' ? cfg.embedBaseUrl.trim() : '')).replace(/\/+$/, '')
|
||||
const tokenUrl = typeof cfg.tokenUrl === 'string' ? cfg.tokenUrl.trim() : ''
|
||||
|
|
@ -857,9 +857,9 @@ const MAPLIBRE_STYLE_ORIGIN = 'https://tiles.openfreemap.org' // useMapInstanc
|
|||
* token; the hint follows the build default and the report names that miss case (one unused preconnect).
|
||||
*/
|
||||
function engineFor({ config }) {
|
||||
// CORRECTED 2026-09-29 (regions executed the shipping resolver; rega's A9NET confirmed on the wire):
|
||||
// CORRECTED 2026-09-29 (the shipping resolver was executed, and a network trace confirmed it on the wire):
|
||||
// a token no longer selects Mapbox. useMapProvider's default is MapLibre unless provider is EXPLICITLY
|
||||
// 'mapbox'. rega measured all 8 auto leaves — which DO carry a pray.doxa.life token endpoint — loading
|
||||
// 'mapbox'. A browser check measured all 8 auto leaves — which DO carry a pray.doxa.life token endpoint — loading
|
||||
// MapLibre from jsdelivr cold. So the hint's engine is Mapbox ONLY when provider:'mapbox' is set; every
|
||||
// other case (auto, or provider:'maplibre') is MapLibre, regardless of any baked token / tokenUrl.
|
||||
return String((config && config.provider) || '').toLowerCase().trim() === 'mapbox' ? 'mapbox' : 'maplibre'
|
||||
|
|
@ -886,7 +886,7 @@ function resourceHints({ isMapLibre, config, apiBaseUrl }) {
|
|||
const gl = engine === 'maplibre' ? CDN.maplibreGlJs : CDN.mapboxGlJs
|
||||
lines.push(` <link rel="preload" as="script" href="${gl.url}" integrity="${gl.integrity}" crossorigin="anonymous" />`)
|
||||
}
|
||||
return lines.length ? ` <!-- A9 hints: the map's origins, so DNS/TLS + the engine start with the bundle, not after it (dx-base/a9-leaf-hints; engine = provider or the MapLibre default). -->\n${lines.join('\n')}\n` : ''
|
||||
return lines.length ? ` <!-- A9 hints: the map's origins, so DNS/TLS + the engine start with the bundle, not after it (engine = provider or the MapLibre default). -->\n${lines.join('\n')}\n` : ''
|
||||
}
|
||||
|
||||
// Does this BUILT bundle .js reference a map engine? Read from public/<tree>/<bundle>/<bundle>.js with the
|
||||
|
|
@ -957,14 +957,14 @@ function mapPageHtml(opts) {
|
|||
// EVERY non-map leaf (cap-4 composites + cap-1/2/3 non-map leaves), not just cap-5 shells.
|
||||
const shellOnly = !!shellTokenFree || notAMap
|
||||
const tokenFree = isMapLibre || shellOnly
|
||||
// A9 leaf hints (dx-base/a9-leaf-hints): give the browser the map's origins BEFORE the bundle
|
||||
// A9 leaf hints: give the browser the map's origins BEFORE the bundle
|
||||
// parses. Engine-aware, derived from the SAME inputs the runtime ladder reads (below), never
|
||||
// hardcoded: preconnect the token-implied engine + the style host + the data origin, and — only
|
||||
// where the leaf emits no engine <script> today (the 'auto' leaf) — preload the engine, its URL,
|
||||
// integrity and crossorigin from library/constants/cdn.js so it cannot drift from the runtime tag.
|
||||
// WHO GETS HINTS: any page whose OWN bundle loads a map engine — the directly verifiable signal
|
||||
// (`bundleLoadsEngine`, an engine reference in the built .js), NOT the leaf's `isMap` flag. rega's A9
|
||||
// net run found regions-mfe (isMap:false, 56 engine refs in its bundle) AND research-map-plus-grid
|
||||
// (`bundleLoadsEngine`, an engine reference in the built .js), NOT the leaf's `isMap` flag. A network
|
||||
// run found regions-mfe (isMap:false, 56 engine refs in its bundle) AND research-map-plus-grid
|
||||
// (isMap:false, own bundle 0 refs but composes doxa-research-map, 74 refs) opening cdn.jsdelivr.net +
|
||||
// tiles.openfreemap.org from the top page and getting no hints — my first cut keyed on isMap and missed
|
||||
// both, the same trap check:leaf-engine-per-provider's isMap gate fell into. bundleLoadsEngine is true
|
||||
|
|
@ -1061,7 +1061,7 @@ ${hintBlock} ${peerBlock}
|
|||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (rega, RQ-RGPRINT on a035408e2). The leaf pins both the
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
|
|
@ -1310,7 +1310,7 @@ function instanceHtml(b, profile, inst, tokenUrlExpr, bakedToken) {
|
|||
return mapPageHtml({
|
||||
title: `${inst.title} · ${b.bundle}`,
|
||||
tag,
|
||||
// MERGE, never replace (go-int: a preset lost its profile's pinHint:false): the profile's
|
||||
// MERGE, never replace (a preset once lost its profile's pinHint:false): the profile's
|
||||
// <profile>.meta.json `config` first, then the instance's props over it. The rule is stated in
|
||||
// documentation/PRESET-PLUGIN-STANDARD.md and asserted by check:preset-config-merge.
|
||||
config: { assetsBase: assetsBaseFor(4), ...(profile.extraConfig || {}), ...mobileConfig(profile), ...inst.props },
|
||||
|
|
@ -1724,7 +1724,7 @@ function writeManifestAndHtml(root, bundles, tokenUrlExpr, bakedToken) {
|
|||
// THE FRONT PAGE IS THE STUDIO (see studioRootHtml). The staging-forge chooser that used to be
|
||||
// written here (rootHtml) is retired with it; nothing is written above the tree any more.
|
||||
// …with the studio bundle's integrity, written HERE: it used to be added only by fix:sri, so running
|
||||
// `bun run tree` after fix:sri stripped it again (rega, 2026-09-25).
|
||||
// `bun run tree` after fix:sri stripped it again (found 2026-09-25).
|
||||
writeFileSync(join(root, 'index.html'), studioRootHtml(bakedToken, (bundles.find((b) => b.bundle === 'plugin-studio') || {}).integrity || ''))
|
||||
|
||||
|
||||
|
|
@ -2019,7 +2019,7 @@ function main() {
|
|||
}
|
||||
// people-groups/lists/*.json — the ?list= / profile-config `list` prop fetches the chosen list by name
|
||||
// (frontier-500k, frontier, unreached, unengaged), so the whole folder ships. Missing, every non-default
|
||||
// list 404'd on the built tree and drew 0 pins (go-int / rega at a71007f1).
|
||||
// list 404'd on the built tree and drew 0 pins (measured at a71007f1).
|
||||
const listsSrc = join(pubAssets, 'people-groups', 'lists')
|
||||
if (existsSync(listsSrc)) {
|
||||
const listsDest = join(treeAssets, 'people-groups', 'lists')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* profile-meta.mjs — the per-profile fields a host reads from <profile>.meta.json, in ONE place.
|
||||
*
|
||||
* generate-tree (the built manifest.json) and the dev server's /__plugin-gallery/manifest.json both call
|
||||
* this, so the dev Studio and the built Studio cannot disagree (rega: the corner widget never mounted in
|
||||
* this, so the dev Studio and the built Studio cannot disagree (the corner widget once never mounted in
|
||||
* dev because only the built manifest carried "widget").
|
||||
* stage "production" | "experimental" default experimental
|
||||
* heavy true when the element holds live WebGL contexts
|
||||
|
|
@ -16,7 +16,7 @@ import { join } from 'node:path'
|
|||
const CORNERS = ['bottom-right', 'bottom-left', 'top-right', 'top-left']
|
||||
|
||||
/**
|
||||
* ★ WHO ELSE MAY LIST THIS PLUGIN (dx-base, 2026-09-28). The platform dashboard reads each team's
|
||||
* ★ WHO ELSE MAY LIST THIS PLUGIN (2026-09-28). The platform dashboard reads each team's
|
||||
* own /cdn/dx-maps/manifest.json instead of one shared index, so this field is what decides whether
|
||||
* a plugin appears in ANOTHER team's library (loaded read-only from the owner's CDN).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ const props = defineProps({
|
|||
itemCountKey: { type: String, default: '' },
|
||||
collapsible: { type: Boolean, default: false },
|
||||
collapsedBy: { type: Function, default: () => false },
|
||||
// A section's people-group total, shown in its header and used to order the sections (largest first).
|
||||
// Default: the sum of its cards' counts. A caller whose cards overlap overrides it, so a group is counted
|
||||
// once: the Country Posters India section holds India's own card AND one card per state, the same 855 groups.
|
||||
sectionTotal: { type: Function, default: null },
|
||||
// A card may declare itself unsupported (India: 855 groups do not fit a sheet).
|
||||
// The caller supplies the reason; the wall renders it instead of a poster.
|
||||
unsupported: { type: Function, default: () => null },
|
||||
|
|
@ -200,6 +204,11 @@ const props = defineProps({
|
|||
// null, a throw or no answer within PREVIEW_TIMEOUT_MS shows the "preview unavailable" placeholder.
|
||||
// The page keeps each returned URL and never revokes it (see GENERATED PREVIEWS below).
|
||||
previewSrc: { type: Function, default: null },
|
||||
// ★ ONE SECTION COUNTED IN ITS OWN WORDS. null (the default) counts every section's cards with item-count-key, as
|
||||
// always. A wall whose section holds a different kind of card passes (sec) => an i18n plural key, or { key, n }
|
||||
// to count n rather than the section's card count, or null for that section to keep the default. The Country
|
||||
// Posters India section holds India's own card and one per state and union territory, which are not countries.
|
||||
sectionItemCountKey: { type: Function, default: null },
|
||||
})
|
||||
const emit = defineEmits(['header-action'])
|
||||
|
||||
|
|
@ -310,6 +319,17 @@ const hud = (key, en, params = null, n = undefined) => {
|
|||
return v && v !== key ? v : String(en).replace(/\{(\w+)\}/g, (m, k) => (params && params[k] != null ? String(params[k]) : m))
|
||||
}
|
||||
const itemCount = (n) => (props.itemCountKey ? countOf(props.itemCountKey, n) : `${n} ${n === 1 ? props.itemNoun : plural.value}`)
|
||||
// A section head's count line. The people-group total and its " · " leave together when the total is 0 (a gallery
|
||||
// of poster templates counts no people), the rule the cards already follow with v-if="item.count". One string in
|
||||
// one interpolation, so a head with a total renders exactly the text it always did, whitespace included.
|
||||
const sectionItems = (sec) => {
|
||||
const own = props.sectionItemCountKey ? props.sectionItemCountKey(sec) : null
|
||||
if (!own) return itemCount(sec.items.length)
|
||||
return typeof own === 'string' ? countOf(own, sec.items.length) : countOf(own.key, own.n ?? sec.items.length)
|
||||
}
|
||||
const sectionCount = (sec) => (sec.total > 0
|
||||
? `${countOf('wall.peopleGroupCount', sec.total)} · ${sectionItems(sec)}`
|
||||
: sectionItems(sec))
|
||||
const base = computed(() => String(props.assetsBase || './assets').replace(/\/+$/, ''))
|
||||
|
||||
// ★ A SECOND PLACE TO LOOK, because "poster not generated yet" was being shown for posters that
|
||||
|
|
@ -351,7 +371,7 @@ const sections = computed(() => {
|
|||
.map(([name, items]) => ({
|
||||
name,
|
||||
items: items.slice().sort((a, b) => (b.count || 0) - (a.count || 0)),
|
||||
total: items.reduce((n, x) => n + (x.count || 0), 0),
|
||||
total: props.sectionTotal ? props.sectionTotal(name, items) : items.reduce((n, x) => n + (x.count || 0), 0),
|
||||
}))
|
||||
.sort((a, b) => b.total - a.total)
|
||||
})
|
||||
|
|
@ -1315,7 +1335,7 @@ useShadowStyles(
|
|||
<span v-if="collapsible" class="sec-caret" :class="{ 'is-open': !isCollapsed(sec.name) }">▶</span>
|
||||
<h3 class="sec-name">{{ sec.name }}</h3>
|
||||
<span class="sec-count">
|
||||
{{ countOf('wall.peopleGroupCount', sec.total) }} · {{ itemCount(sec.items.length) }}
|
||||
{{ sectionCount(sec) }}
|
||||
</span>
|
||||
</component>
|
||||
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ export function createAppI18n() {
|
|||
// dir on .profile-loader from effectiveLocale (0f6d5049), so the plugin mirrors correctly
|
||||
// whether or not it owns the document. This is only about the page around it.
|
||||
//
|
||||
// LANG TRAVELS WITH DIR (rega, research.nuxtbase.app/?lang=ar): an owned page flipped to dir=rtl but
|
||||
// LANG TRAVELS WITH DIR (measured on a research page with ?lang=ar): an owned page flipped to dir=rtl but
|
||||
// kept the generator's <html lang="en">, so a screen reader read the Arabic with an English voice (and
|
||||
// ?lang=fr left lang="en" too). The page is ours, so its lang is the locale in force.
|
||||
try {
|
||||
|
|
|
|||
5
library/i18n/locales/ar/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "للهند قسمها الخاص في المقدّمة: عدد الشعوب غير المُبشَّرة فيها {count}، وهو أكثر بكثير من أي بلد آخر، لذا لكل ولاية وإقليم اتحادي فيها ملصقه الخاص.",
|
||||
"indiaStateNoGroups": "لا توجد هنا شعوب غير مُبشَّرة مُدرَجة.",
|
||||
"indiaItemCount": "لا ولايات ولا أقاليم | ولاية أو إقليم اتحادي واحد | ولايتان أو إقليمان | {n} ولايات وأقاليم اتحادية | {n} ولاية وإقليمًا اتحاديًا | {n} ولاية وإقليم اتحادي"
|
||||
}
|
||||
51
library/i18n/locales/ar/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "عرض التفاصيل كاملة",
|
||||
"showFullDetailsAria": "عرض التفاصيل كاملة لـ {label}",
|
||||
"tab": {
|
||||
"styles": "الأنماط",
|
||||
"sizes": "المقاسات"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "أنماط الملصقات",
|
||||
"intro": "كل قوالب الملصقات في مكان واحد، مرسومة من المكتبة ببيانات نموذجية. تحمل كل بطاقة اسم إعدادها، لتطلب من الوكيل هذا القالب بعينه."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "مقاسات الملصقات",
|
||||
"waiting": "سيظهر سُلّم المقاسات (الورق القياسي، و3 × 3 أقدام، و3 × 5 أقدام، و5.5 × 5.5 أقدام، و5 × 8 أقدام) هنا بعد نشر جدول المقاسات المشترك. سيُعرض كل مقاس كمعاينة طباعة، بالاتجاه الطولي فقط."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "صفحات الخرائط",
|
||||
"packet": "حزمة الصلاة",
|
||||
"cards": "بطاقات الصلاة",
|
||||
"wall": "ألواح الجدار",
|
||||
"mapPrint": "طباعة الخريطة"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "صفحة خريطة الدولة",
|
||||
"mapSheetPacketPage": "صفحة الخريطة في الحزمة",
|
||||
"packetFull": "حزمة الصلاة، كاملة",
|
||||
"packetLightweight": "حزمة الصلاة، مختصرة",
|
||||
"cards": "بطاقات الصلاة",
|
||||
"blessPanels": "ألواح جدار البركة",
|
||||
"mapPrint": "طباعة الخريطة"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "لا يرسم المعرض الحزمة بعد. تذكر التفاصيل مولّدها ونسختها.",
|
||||
"cards": "لا يرسم المعرض البطاقات بعد. تذكر التفاصيل المولّد ومقاس البطاقة.",
|
||||
"bless": "محرّك هذا القالب ليس في المكتبة المشتركة بعد، لذا لا يستطيع المعرض رسمه.",
|
||||
"mapPrint": "يطبع هذا القالب الخريطة الحية التي ينظر إليها القارئ، لذا ليست له صورة نموذجية."
|
||||
},
|
||||
"details": {
|
||||
"configName": "اسم الإعداد",
|
||||
"copyName": "نسخ",
|
||||
"size": "المقاس",
|
||||
"fileSize": "حجم الملف",
|
||||
"fileSizeUnknown": "يُقاس عند رسم الملصق",
|
||||
"settings": "الإعدادات",
|
||||
"sample": "بيانات نموذجية",
|
||||
"renderer": "المولّد",
|
||||
"legacy": "مقاس قديم: ما زال يُطبع، لكن أداة اختيار الطباعة لم تعد تعرضه.",
|
||||
"forAgents": "لاستخدام هذا القالب، قل للوكيل: استخدم الإعداد {name}."
|
||||
},
|
||||
"templateCount": "لا قوالب | قالب واحد | قالبان | {n} قوالب | {n} قالبًا | {n} قالب"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "تم حفظ CSV — الشعوب: {count}",
|
||||
"csvFailed": "فشل تصدير CSV: {reason}",
|
||||
"copySelection": "نسخ",
|
||||
"exportCsv": "تصدير CSV"
|
||||
"exportCsv": "تصدير CSV",
|
||||
"copyNotRegroupable": "لا يمكن إعادة تجميعها معًا: انسخ ولايات أو مقاطعات بلد واحد، أو البلدان، كلًّا على حدة",
|
||||
"displayName": "ملصقات قائمة UPG 100",
|
||||
"wallHeading": "ملصقات قائمة UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/ar/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "هذا الملصق مخصّص للمطبعة: في نافذة الطباعة اختر «حفظ بتنسيق PDF»، ثم أرسل الملف إلى المطبعة."
|
||||
}
|
||||
5
library/i18n/locales/de/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "Indien hat einen eigenen Abschnitt, ganz oben: Mit {count} unerreichten Volksgruppen, weit mehr als jedes andere Land, hat jeder seiner Bundesstaaten und Unionsterritorien ein eigenes Plakat.",
|
||||
"indiaStateNoGroups": "Hier sind keine unerreichten Volksgruppen verzeichnet.",
|
||||
"indiaItemCount": "{n} Bundesstaat oder Unionsterritorium | {n} Bundesstaaten und Unionsterritorien"
|
||||
}
|
||||
51
library/i18n/locales/de/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Alle Details anzeigen",
|
||||
"showFullDetailsAria": "Alle Details zu {label} anzeigen",
|
||||
"tab": {
|
||||
"styles": "Stile",
|
||||
"sizes": "Größen"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Posterstile",
|
||||
"intro": "Alle Postervorlagen an einem Ort, aus der Bibliothek mit Beispieldaten gezeichnet. Jede Karte trägt den Namen ihrer Konfiguration, damit Sie einen Agenten um genau diese bitten können."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Postergrößen",
|
||||
"waiting": "Die Größenstufen (Standardpapier, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft und 5 × 8 ft) erscheinen hier, sobald die gemeinsame Größentabelle veröffentlicht ist. Jede Größe wird als Druckvorschau gezeigt, nur im Hochformat."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Kartenblätter",
|
||||
"packet": "Gebetsmappe",
|
||||
"cards": "Gebetskarten",
|
||||
"wall": "Wandtafeln",
|
||||
"mapPrint": "Kartendruck"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Länder-Kartenblatt",
|
||||
"mapSheetPacketPage": "Kartenseite der Mappe",
|
||||
"packetFull": "Gebetsmappe, vollständig",
|
||||
"packetLightweight": "Gebetsmappe, kompakt",
|
||||
"cards": "Gebetskarten",
|
||||
"blessPanels": "Tafeln der Segnen-Wand",
|
||||
"mapPrint": "Kartendruck"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "Die Galerie zeichnet die Mappe noch nicht. Die Details nennen ihren Generator und ihre Variante.",
|
||||
"cards": "Die Galerie zeichnet die Karten noch nicht. Die Details nennen den Generator und das Kartenformat.",
|
||||
"bless": "Die Engine dieser Vorlage ist noch nicht in der gemeinsamen Bibliothek, deshalb kann die Galerie sie nicht zeichnen.",
|
||||
"mapPrint": "Diese Vorlage druckt die Live-Karte, die der Leser gerade ansieht, und hat deshalb kein Beispielbild."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Name der Konfiguration",
|
||||
"copyName": "Kopieren",
|
||||
"size": "Größe",
|
||||
"fileSize": "Dateigröße",
|
||||
"fileSizeUnknown": "Wird gemessen, wenn das Poster gezeichnet wird",
|
||||
"settings": "Einstellungen",
|
||||
"sample": "Beispieldaten",
|
||||
"renderer": "Generator",
|
||||
"legacy": "Alte Größe: Sie wird noch gedruckt, aber die Druckauswahl bietet sie nicht mehr an.",
|
||||
"forAgents": "Um diese Vorlage zu verwenden, sagen Sie einem Agenten: verwende die Konfiguration {name}."
|
||||
},
|
||||
"templateCount": "{n} Vorlage | {n} Vorlagen"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV gespeichert — Volksgruppen: {count}",
|
||||
"csvFailed": "CSV-Export fehlgeschlagen: {reason}",
|
||||
"copySelection": "Kopieren",
|
||||
"exportCsv": "CSV exportieren"
|
||||
"exportCsv": "CSV exportieren",
|
||||
"copyNotRegroupable": "Nicht gemeinsam umgruppierbar: Bundesstaaten oder Provinzen eines Landes oder die Länder getrennt kopieren",
|
||||
"displayName": "Poster der UPG 100 Liste",
|
||||
"wallHeading": "Poster der UPG 100 Liste"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/de/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Dieses Poster ist für eine Druckerei gedacht: Wählen Sie im Druckdialog „Als PDF speichern“ und senden Sie die Datei dann an die Druckerei."
|
||||
}
|
||||
5
library/i18n/locales/en/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "India has its own section, first: it has {count} unreached people groups, far more than any other country, so each of its states and union territories has its own poster.",
|
||||
"indiaStateNoGroups": "No unreached people groups are listed here.",
|
||||
"indiaItemCount": "{n} states and union territories | {n} state or union territory | {n} states and union territories"
|
||||
}
|
||||
51
library/i18n/locales/en/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Show full details",
|
||||
"showFullDetailsAria": "Show full details for {label}",
|
||||
"tab": {
|
||||
"styles": "Styles",
|
||||
"sizes": "Sizes"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Poster styles",
|
||||
"intro": "Every poster template in one place, drawn from the library with sample data. Each card is named by its configuration, so you can ask an agent for exactly that one."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Poster sizes",
|
||||
"waiting": "The size ladder (standard paper, 3 × 3 ft, 3 × 5 ft, 5.5 × 5.5 ft and 5 × 8 ft) appears here once the shared size table is published. Each size will be shown as a print preview, portrait only."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Map sheets",
|
||||
"packet": "Prayer packet",
|
||||
"cards": "Prayer cards",
|
||||
"wall": "Wall panels",
|
||||
"mapPrint": "Map print"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Country map sheet",
|
||||
"mapSheetPacketPage": "Packet map page",
|
||||
"packetFull": "Prayer packet, full",
|
||||
"packetLightweight": "Prayer packet, lightweight",
|
||||
"cards": "Prayer cards",
|
||||
"blessPanels": "Bless wall panels",
|
||||
"mapPrint": "Map print"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "The gallery does not draw the packet yet. The details name its renderer and variant.",
|
||||
"cards": "The gallery does not draw the cards yet. The details name the renderer and the card stock.",
|
||||
"bless": "This template's engine is not in the shared library yet, so the gallery cannot draw it.",
|
||||
"mapPrint": "This template prints the live map the reader is looking at, so it has no sample picture."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Configuration name",
|
||||
"copyName": "Copy",
|
||||
"size": "Size",
|
||||
"fileSize": "File size",
|
||||
"fileSizeUnknown": "Measured when the poster is drawn",
|
||||
"settings": "Settings",
|
||||
"sample": "Sample data",
|
||||
"renderer": "Renderer",
|
||||
"legacy": "Legacy size: it still prints, but the print picker no longer offers it.",
|
||||
"forAgents": "To use this template, tell an agent: use configuration {name}."
|
||||
},
|
||||
"templateCount": "{n} templates | {n} template | {n} templates"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV saved — people groups: {count}",
|
||||
"csvFailed": "CSV failed: {reason}",
|
||||
"copySelection": "Copy",
|
||||
"exportCsv": "Export CSV"
|
||||
"exportCsv": "Export CSV",
|
||||
"copyNotRegroupable": "Can't be regrouped together: copy one country's states or provinces, or the countries, separately",
|
||||
"displayName": "UPG 100 List Posters",
|
||||
"wallHeading": "UPG 100 List Posters"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/en/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "This poster is for a print shop: in the print dialog, choose “Save as PDF”, then send the file to the print shop."
|
||||
}
|
||||
5
library/i18n/locales/es/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "India tiene su propia sección, la primera: tiene {count} pueblos no alcanzados, muchos más que cualquier otro país, así que cada uno de sus estados y territorios de la unión tiene su propio cartel.",
|
||||
"indiaStateNoGroups": "Aquí no figura ningún pueblo no alcanzado.",
|
||||
"indiaItemCount": "{n} estado o territorio de la unión | {n} de estados y territorios de la unión | {n} estados y territorios de la unión"
|
||||
}
|
||||
51
library/i18n/locales/es/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Ver todos los detalles",
|
||||
"showFullDetailsAria": "Ver todos los detalles de {label}",
|
||||
"tab": {
|
||||
"styles": "Estilos",
|
||||
"sizes": "Tamaños"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Estilos de póster",
|
||||
"intro": "Todas las plantillas de póster en un solo lugar, dibujadas desde la biblioteca con datos de ejemplo. Cada tarjeta lleva el nombre de su configuración, para que puedas pedirle a un agente exactamente esa."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Tamaños de póster",
|
||||
"waiting": "La escala de tamaños (papel estándar, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft y 5 × 8 ft) aparecerá aquí cuando se publique la tabla de tamaños compartida. Cada tamaño se mostrará como vista previa de impresión, solo en vertical."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Hojas de mapa",
|
||||
"packet": "Paquete de oración",
|
||||
"cards": "Tarjetas de oración",
|
||||
"wall": "Paneles de pared",
|
||||
"mapPrint": "Impresión del mapa"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Hoja de mapa del país",
|
||||
"mapSheetPacketPage": "Página de mapa del paquete",
|
||||
"packetFull": "Paquete de oración, completo",
|
||||
"packetLightweight": "Paquete de oración, ligero",
|
||||
"cards": "Tarjetas de oración",
|
||||
"blessPanels": "Paneles del muro Bendecir",
|
||||
"mapPrint": "Impresión del mapa"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "La galería todavía no dibuja el paquete. Los detalles indican su generador y su variante.",
|
||||
"cards": "La galería todavía no dibuja las tarjetas. Los detalles indican el generador y el tipo de tarjeta.",
|
||||
"bless": "El motor de esta plantilla aún no está en la biblioteca compartida, así que la galería no puede dibujarla.",
|
||||
"mapPrint": "Esta plantilla imprime el mapa en vivo que está mirando el lector, así que no tiene imagen de ejemplo."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Nombre de la configuración",
|
||||
"copyName": "Copiar",
|
||||
"size": "Tamaño",
|
||||
"fileSize": "Tamaño del archivo",
|
||||
"fileSizeUnknown": "Se mide al dibujar el póster",
|
||||
"settings": "Ajustes",
|
||||
"sample": "Datos de ejemplo",
|
||||
"renderer": "Generador",
|
||||
"legacy": "Tamaño heredado: todavía se imprime, pero el selector de impresión ya no lo ofrece.",
|
||||
"forAgents": "Para usar esta plantilla, dile a un agente: usa la configuración {name}."
|
||||
},
|
||||
"templateCount": "{n} plantilla | {n} de plantillas | {n} plantillas"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV guardado — pueblos: {count}",
|
||||
"csvFailed": "Error al exportar el CSV: {reason}",
|
||||
"copySelection": "Copiar",
|
||||
"exportCsv": "Exportar CSV"
|
||||
"exportCsv": "Exportar CSV",
|
||||
"copyNotRegroupable": "No se pueden reagrupar juntos: copia por separado los estados o provincias de un solo país, o los países",
|
||||
"displayName": "Carteles de la lista UPG 100",
|
||||
"wallHeading": "Carteles de la lista UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/es/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Este póster es para una imprenta: en el diálogo de impresión, elija «Guardar como PDF» y luego envíe el archivo a la imprenta."
|
||||
}
|
||||
5
library/i18n/locales/fr/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "L’Inde a sa propre section, en premier : elle compte {count} peuples non atteints, bien plus que tout autre pays ; chacun de ses États et territoires de l’Union a donc sa propre affiche.",
|
||||
"indiaStateNoGroups": "Aucun peuple non atteint n’est répertorié ici.",
|
||||
"indiaItemCount": "{n} État ou territoire de l’Union | {n} d’États et territoires de l’Union | {n} États et territoires de l’Union"
|
||||
}
|
||||
51
library/i18n/locales/fr/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Afficher tous les détails",
|
||||
"showFullDetailsAria": "Afficher tous les détails de {label}",
|
||||
"tab": {
|
||||
"styles": "Styles",
|
||||
"sizes": "Formats"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Styles d'affiche",
|
||||
"intro": "Tous les modèles d'affiche au même endroit, dessinés depuis la bibliothèque avec des données d'exemple. Chaque carte porte le nom de sa configuration, pour que vous puissiez demander exactement celle-ci à un agent."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Formats d'affiche",
|
||||
"waiting": "L'échelle des formats (papier standard, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft et 5 × 8 ft) apparaîtra ici dès que la table des formats partagée sera publiée. Chaque format sera présenté en aperçu avant impression, en portrait uniquement."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Feuilles de carte",
|
||||
"packet": "Dossier de prière",
|
||||
"cards": "Cartes de prière",
|
||||
"wall": "Panneaux muraux",
|
||||
"mapPrint": "Impression de la carte"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Feuille de carte du pays",
|
||||
"mapSheetPacketPage": "Page de carte du dossier",
|
||||
"packetFull": "Dossier de prière, complet",
|
||||
"packetLightweight": "Dossier de prière, allégé",
|
||||
"cards": "Cartes de prière",
|
||||
"blessPanels": "Panneaux du mur Bénir",
|
||||
"mapPrint": "Impression de la carte"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "La galerie ne dessine pas encore le dossier. Les détails indiquent son générateur et sa variante.",
|
||||
"cards": "La galerie ne dessine pas encore les cartes. Les détails indiquent le générateur et le format de carte.",
|
||||
"bless": "Le moteur de ce modèle n'est pas encore dans la bibliothèque partagée, la galerie ne peut donc pas le dessiner.",
|
||||
"mapPrint": "Ce modèle imprime la carte en direct que le lecteur regarde, il n'a donc pas d'image d'exemple."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Nom de la configuration",
|
||||
"copyName": "Copier",
|
||||
"size": "Format",
|
||||
"fileSize": "Taille du fichier",
|
||||
"fileSizeUnknown": "Mesurée quand l'affiche est dessinée",
|
||||
"settings": "Réglages",
|
||||
"sample": "Données d'exemple",
|
||||
"renderer": "Générateur",
|
||||
"legacy": "Format ancien : il s'imprime encore, mais le sélecteur d'impression ne le propose plus.",
|
||||
"forAgents": "Pour utiliser ce modèle, dites à un agent : utilise la configuration {name}."
|
||||
},
|
||||
"templateCount": "{n} modèle | {n} de modèles | {n} modèles"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV enregistré — peuples : {count}",
|
||||
"csvFailed": "Échec de l’export CSV : {reason}",
|
||||
"copySelection": "Copier",
|
||||
"exportCsv": "Exporter en CSV"
|
||||
"exportCsv": "Exporter en CSV",
|
||||
"copyNotRegroupable": "Impossible de les regrouper ensemble : copiez séparément les États ou provinces d’un seul pays, ou les pays",
|
||||
"displayName": "Affiches de la liste UPG 100",
|
||||
"wallHeading": "Affiches de la liste UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/fr/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Cette affiche est destinée à un imprimeur : dans la fenêtre d’impression, choisissez « Enregistrer au format PDF », puis envoyez le fichier à l’imprimeur."
|
||||
}
|
||||
5
library/i18n/locales/hi/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "भारत का अपना अलग खंड है, सबसे पहले: इसमें {count} अपहुँचे जनसमूह हैं, किसी भी अन्य देश से कहीं अधिक, इसलिए इसके हर राज्य और केंद्र शासित प्रदेश का अपना पोस्टर है।",
|
||||
"indiaStateNoGroups": "यहाँ कोई अपहुँचा जनसमूह सूचीबद्ध नहीं है।",
|
||||
"indiaItemCount": "{n} राज्य या केंद्र शासित प्रदेश | {n} राज्य और केंद्र शासित प्रदेश"
|
||||
}
|
||||
51
library/i18n/locales/hi/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "पूरा विवरण दिखाएँ",
|
||||
"showFullDetailsAria": "{label} का पूरा विवरण दिखाएँ",
|
||||
"tab": {
|
||||
"styles": "शैलियाँ",
|
||||
"sizes": "आकार"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "पोस्टर शैलियाँ",
|
||||
"intro": "सभी पोस्टर टेम्पलेट एक ही जगह, लाइब्रेरी से नमूना डेटा के साथ बनाए गए। हर कार्ड पर उसके कॉन्फ़िगरेशन का नाम है, ताकि आप किसी एजेंट से ठीक वही माँग सकें।"
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "पोस्टर आकार",
|
||||
"waiting": "साझा आकार तालिका प्रकाशित होते ही आकारों की सीढ़ी (मानक कागज़, 3 × 3 फ़ुट, 3 × 5 फ़ुट, 5.5 × 5.5 फ़ुट और 5 × 8 फ़ुट) यहाँ दिखेगी। हर आकार प्रिंट पूर्वावलोकन के रूप में दिखेगा, केवल खड़े (पोर्ट्रेट) रूप में।"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "मानचित्र पृष्ठ",
|
||||
"packet": "प्रार्थना पैकेट",
|
||||
"cards": "प्रार्थना कार्ड",
|
||||
"wall": "दीवार पैनल",
|
||||
"mapPrint": "मानचित्र प्रिंट"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "देश का मानचित्र पृष्ठ",
|
||||
"mapSheetPacketPage": "पैकेट का मानचित्र पृष्ठ",
|
||||
"packetFull": "प्रार्थना पैकेट, पूरा",
|
||||
"packetLightweight": "प्रार्थना पैकेट, हल्का",
|
||||
"cards": "प्रार्थना कार्ड",
|
||||
"blessPanels": "आशीष दीवार के पैनल",
|
||||
"mapPrint": "मानचित्र प्रिंट"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "गैलरी अभी पैकेट नहीं बनाती। विवरण में उसका जनरेटर और संस्करण बताया गया है।",
|
||||
"cards": "गैलरी अभी कार्ड नहीं बनाती। विवरण में जनरेटर और कार्ड का आकार बताया गया है।",
|
||||
"bless": "इस टेम्पलेट का इंजन अभी साझा लाइब्रेरी में नहीं है, इसलिए गैलरी इसे नहीं बना सकती।",
|
||||
"mapPrint": "यह टेम्पलेट वह लाइव मानचित्र प्रिंट करता है जिसे पाठक देख रहा है, इसलिए इसकी कोई नमूना तस्वीर नहीं है।"
|
||||
},
|
||||
"details": {
|
||||
"configName": "कॉन्फ़िगरेशन का नाम",
|
||||
"copyName": "कॉपी करें",
|
||||
"size": "आकार",
|
||||
"fileSize": "फ़ाइल का आकार",
|
||||
"fileSizeUnknown": "पोस्टर बनने पर मापा जाता है",
|
||||
"settings": "सेटिंग्स",
|
||||
"sample": "नमूना डेटा",
|
||||
"renderer": "जनरेटर",
|
||||
"legacy": "पुराना आकार: यह अब भी प्रिंट होता है, पर प्रिंट चयनकर्ता अब इसे नहीं दिखाता।",
|
||||
"forAgents": "यह टेम्पलेट इस्तेमाल करने के लिए किसी एजेंट से कहें: कॉन्फ़िगरेशन {name} इस्तेमाल करो।"
|
||||
},
|
||||
"templateCount": "{n} टेम्पलेट | {n} टेम्पलेट"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV सहेजा गया — जनसमूह: {count}",
|
||||
"csvFailed": "CSV निर्यात विफल: {reason}",
|
||||
"copySelection": "कॉपी करें",
|
||||
"exportCsv": "CSV निर्यात करें"
|
||||
"exportCsv": "CSV निर्यात करें",
|
||||
"copyNotRegroupable": "इन्हें एक साथ पुनर्समूहित नहीं किया जा सकता: एक देश के राज्य या प्रांत, या देश, अलग-अलग कॉपी करें",
|
||||
"displayName": "UPG 100 सूची पोस्टर",
|
||||
"wallHeading": "UPG 100 सूची पोस्टर"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/hi/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "यह पोस्टर प्रिंटिंग प्रेस के लिए है: प्रिंट विंडो में “PDF के रूप में सहेजें” चुनें, फिर फ़ाइल प्रिंटिंग प्रेस को भेजें।"
|
||||
}
|
||||
5
library/i18n/locales/it/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "L’India ha una sezione tutta sua, la prima: conta {count} popoli non raggiunti, molti più di qualsiasi altro paese, quindi ognuno dei suoi stati e territori dell’Unione ha il proprio manifesto.",
|
||||
"indiaStateNoGroups": "Qui non è elencato alcun popolo non raggiunto.",
|
||||
"indiaItemCount": "{n} stato o territorio dell’Unione | {n} di stati e territori dell’Unione | {n} stati e territori dell’Unione"
|
||||
}
|
||||
51
library/i18n/locales/it/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Mostra tutti i dettagli",
|
||||
"showFullDetailsAria": "Mostra tutti i dettagli di {label}",
|
||||
"tab": {
|
||||
"styles": "Stili",
|
||||
"sizes": "Formati"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Stili dei poster",
|
||||
"intro": "Tutti i modelli di poster in un unico posto, disegnati dalla libreria con dati di esempio. Ogni scheda porta il nome della sua configurazione, così puoi chiedere a un agente esattamente quella."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Formati dei poster",
|
||||
"waiting": "La scala dei formati (carta standard, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft e 5 × 8 ft) comparirà qui quando sarà pubblicata la tabella dei formati condivisa. Ogni formato sarà mostrato come anteprima di stampa, solo in verticale."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Fogli mappa",
|
||||
"packet": "Fascicolo di preghiera",
|
||||
"cards": "Schede di preghiera",
|
||||
"wall": "Pannelli da parete",
|
||||
"mapPrint": "Stampa della mappa"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Foglio mappa del paese",
|
||||
"mapSheetPacketPage": "Pagina mappa del fascicolo",
|
||||
"packetFull": "Fascicolo di preghiera, completo",
|
||||
"packetLightweight": "Fascicolo di preghiera, leggero",
|
||||
"cards": "Schede di preghiera",
|
||||
"blessPanels": "Pannelli del muro Benedici",
|
||||
"mapPrint": "Stampa della mappa"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "La galleria non disegna ancora il fascicolo. I dettagli indicano il suo generatore e la variante.",
|
||||
"cards": "La galleria non disegna ancora le schede. I dettagli indicano il generatore e il formato della scheda.",
|
||||
"bless": "Il motore di questo modello non è ancora nella libreria condivisa, quindi la galleria non può disegnarlo.",
|
||||
"mapPrint": "Questo modello stampa la mappa dal vivo che il lettore sta guardando, quindi non ha un'immagine di esempio."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Nome della configurazione",
|
||||
"copyName": "Copia",
|
||||
"size": "Formato",
|
||||
"fileSize": "Dimensione del file",
|
||||
"fileSizeUnknown": "Misurata quando il poster viene disegnato",
|
||||
"settings": "Impostazioni",
|
||||
"sample": "Dati di esempio",
|
||||
"renderer": "Generatore",
|
||||
"legacy": "Formato precedente: si stampa ancora, ma il selettore di stampa non lo offre più.",
|
||||
"forAgents": "Per usare questo modello, di' a un agente: usa la configurazione {name}."
|
||||
},
|
||||
"templateCount": "{n} modello | {n} di modelli | {n} modelli"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV salvato — popoli: {count}",
|
||||
"csvFailed": "Esportazione CSV non riuscita: {reason}",
|
||||
"copySelection": "Copia",
|
||||
"exportCsv": "Esporta CSV"
|
||||
"exportCsv": "Esporta CSV",
|
||||
"copyNotRegroupable": "Non si possono raggruppare insieme: copia separatamente gli stati o le province di un solo paese, o i paesi",
|
||||
"displayName": "Poster della lista UPG 100",
|
||||
"wallHeading": "Poster della lista UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/it/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Questo poster è pensato per una tipografia: nella finestra di stampa scegli «Salva come PDF», poi invia il file alla tipografia."
|
||||
}
|
||||
5
library/i18n/locales/pt/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "A Índia tem a sua própria seção, a primeira: tem {count} povos não alcançados, muito mais do que qualquer outro país, por isso cada um dos seus estados e territórios da união tem o seu próprio cartaz.",
|
||||
"indiaStateNoGroups": "Nenhum povo não alcançado está listado aqui.",
|
||||
"indiaItemCount": "{n} estado ou território da união | {n} de estados e territórios da união | {n} estados e territórios da união"
|
||||
}
|
||||
51
library/i18n/locales/pt/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Mostrar todos os detalhes",
|
||||
"showFullDetailsAria": "Mostrar todos os detalhes de {label}",
|
||||
"tab": {
|
||||
"styles": "Estilos",
|
||||
"sizes": "Tamanhos"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Estilos de pôster",
|
||||
"intro": "Todos os modelos de pôster num só lugar, desenhados a partir da biblioteca com dados de exemplo. Cada cartão tem o nome da sua configuração, para você pedir exatamente essa a um agente."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Tamanhos de pôster",
|
||||
"waiting": "A escala de tamanhos (papel padrão, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft e 5 × 8 ft) aparecerá aqui quando a tabela de tamanhos compartilhada for publicada. Cada tamanho será mostrado como pré-visualização de impressão, apenas na vertical."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Folhas de mapa",
|
||||
"packet": "Pacote de oração",
|
||||
"cards": "Cartões de oração",
|
||||
"wall": "Painéis de parede",
|
||||
"mapPrint": "Impressão do mapa"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Folha de mapa do país",
|
||||
"mapSheetPacketPage": "Página de mapa do pacote",
|
||||
"packetFull": "Pacote de oração, completo",
|
||||
"packetLightweight": "Pacote de oração, leve",
|
||||
"cards": "Cartões de oração",
|
||||
"blessPanels": "Painéis do mural Abençoar",
|
||||
"mapPrint": "Impressão do mapa"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "A galeria ainda não desenha o pacote. Os detalhes indicam o seu gerador e a variante.",
|
||||
"cards": "A galeria ainda não desenha os cartões. Os detalhes indicam o gerador e o formato do cartão.",
|
||||
"bless": "O motor deste modelo ainda não está na biblioteca compartilhada, por isso a galeria não consegue desenhá-lo.",
|
||||
"mapPrint": "Este modelo imprime o mapa ao vivo que o leitor está vendo, por isso não tem imagem de exemplo."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Nome da configuração",
|
||||
"copyName": "Copiar",
|
||||
"size": "Tamanho",
|
||||
"fileSize": "Tamanho do arquivo",
|
||||
"fileSizeUnknown": "Medido quando o pôster é desenhado",
|
||||
"settings": "Configurações",
|
||||
"sample": "Dados de exemplo",
|
||||
"renderer": "Gerador",
|
||||
"legacy": "Tamanho antigo: ainda imprime, mas o seletor de impressão já não o oferece.",
|
||||
"forAgents": "Para usar este modelo, diga a um agente: use a configuração {name}."
|
||||
},
|
||||
"templateCount": "{n} modelo | {n} de modelos | {n} modelos"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV salvo — povos: {count}",
|
||||
"csvFailed": "Falha ao exportar o CSV: {reason}",
|
||||
"copySelection": "Copiar",
|
||||
"exportCsv": "Exportar CSV"
|
||||
"exportCsv": "Exportar CSV",
|
||||
"copyNotRegroupable": "Não podem ser reagrupados juntos: copie separadamente os estados ou províncias de um só país, ou os países",
|
||||
"displayName": "Cartazes da lista UPG 100",
|
||||
"wallHeading": "Cartazes da lista UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/pt/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Este pôster é para uma gráfica: na janela de impressão, escolha “Salvar como PDF” e depois envie o arquivo para a gráfica."
|
||||
}
|
||||
5
library/i18n/locales/ro/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "India are propria secțiune, prima: are {count} popoare neatinse, mult mai multe decât orice altă țară, așa că fiecare dintre statele și teritoriile sale ale Uniunii are propriul afiș.",
|
||||
"indiaStateNoGroups": "Aici nu este listat niciun popor neatins.",
|
||||
"indiaItemCount": "un stat sau teritoriu al Uniunii | {n} state și teritorii ale Uniunii | {n} de state și teritorii ale Uniunii"
|
||||
}
|
||||
51
library/i18n/locales/ro/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Arată toate detaliile",
|
||||
"showFullDetailsAria": "Arată toate detaliile pentru {label}",
|
||||
"tab": {
|
||||
"styles": "Stiluri",
|
||||
"sizes": "Dimensiuni"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Stiluri de afiș",
|
||||
"intro": "Toate șabloanele de afiș într-un singur loc, desenate din bibliotecă cu date de exemplu. Fiecare card poartă numele configurației sale, ca să poți cere unui agent exact acel șablon."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Dimensiuni de afiș",
|
||||
"waiting": "Scara dimensiunilor (hârtie standard, 3 × 3 ft, 3 × 5 ft, 5,5 × 5,5 ft și 5 × 8 ft) va apărea aici după publicarea tabelului comun de dimensiuni. Fiecare dimensiune va fi afișată ca previzualizare de tipărire, doar pe verticală."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Foi de hartă",
|
||||
"packet": "Pachet de rugăciune",
|
||||
"cards": "Cartonașe de rugăciune",
|
||||
"wall": "Panouri de perete",
|
||||
"mapPrint": "Tipărirea hărții"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Foaie de hartă a țării",
|
||||
"mapSheetPacketPage": "Pagina de hartă a pachetului",
|
||||
"packetFull": "Pachet de rugăciune, complet",
|
||||
"packetLightweight": "Pachet de rugăciune, ușor",
|
||||
"cards": "Cartonașe de rugăciune",
|
||||
"blessPanels": "Panourile peretelui Binecuvântează",
|
||||
"mapPrint": "Tipărirea hărții"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "Galeria nu desenează încă pachetul. Detaliile numesc generatorul și varianta lui.",
|
||||
"cards": "Galeria nu desenează încă cartonașele. Detaliile numesc generatorul și formatul cartonașului.",
|
||||
"bless": "Motorul acestui șablon nu este încă în biblioteca comună, așa că galeria nu îl poate desena.",
|
||||
"mapPrint": "Acest șablon tipărește harta live pe care o privește cititorul, așa că nu are imagine de exemplu."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Numele configurației",
|
||||
"copyName": "Copiază",
|
||||
"size": "Dimensiune",
|
||||
"fileSize": "Dimensiunea fișierului",
|
||||
"fileSizeUnknown": "Se măsoară când afișul este desenat",
|
||||
"settings": "Setări",
|
||||
"sample": "Date de exemplu",
|
||||
"renderer": "Generator",
|
||||
"legacy": "Dimensiune veche: încă se tipărește, dar selectorul de tipărire nu o mai oferă.",
|
||||
"forAgents": "Ca să folosești acest șablon, spune-i unui agent: folosește configurația {name}."
|
||||
},
|
||||
"templateCount": "{n} șablon | {n} șabloane | {n} de șabloane"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV salvat — popoare: {count}",
|
||||
"csvFailed": "Exportul CSV a eșuat: {reason}",
|
||||
"copySelection": "Copiază",
|
||||
"exportCsv": "Exportă CSV"
|
||||
"exportCsv": "Exportă CSV",
|
||||
"copyNotRegroupable": "Nu pot fi regrupate împreună: copiază separat statele sau provinciile unei singure țări, sau țările",
|
||||
"displayName": "Afișe din lista UPG 100",
|
||||
"wallHeading": "Afișe din lista UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/ro/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Acest poster este pentru o tipografie: în fereastra de tipărire, alegeți „Salvați ca PDF”, apoi trimiteți fișierul la tipografie."
|
||||
}
|
||||
5
library/i18n/locales/ru/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "У Индии свой раздел, первый: число недостигнутых народов в ней — {count}, намного больше, чем в любой другой стране, поэтому у каждого её штата и союзной территории есть свой плакат.",
|
||||
"indiaStateNoGroups": "Здесь не указано ни одного недостигнутого народа.",
|
||||
"indiaItemCount": "{n} штат или союзная территория | {n} штата и союзные территории | {n} штатов и союзных территорий | {n} штата и союзной территории"
|
||||
}
|
||||
51
library/i18n/locales/ru/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "Показать все сведения",
|
||||
"showFullDetailsAria": "Показать все сведения: {label}",
|
||||
"tab": {
|
||||
"styles": "Стили",
|
||||
"sizes": "Размеры"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "Стили плакатов",
|
||||
"intro": "Все шаблоны плакатов в одном месте, нарисованные из библиотеки на примерных данных. Каждая карточка названа по своей конфигурации, чтобы можно было попросить агента именно о ней."
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "Размеры плакатов",
|
||||
"waiting": "Шкала размеров (стандартная бумага, 3 × 3 фута, 3 × 5 футов, 5,5 × 5,5 фута и 5 × 8 футов) появится здесь, когда будет опубликована общая таблица размеров. Каждый размер будет показан как предпросмотр печати, только в книжной ориентации."
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Листы карт",
|
||||
"packet": "Молитвенный пакет",
|
||||
"cards": "Молитвенные карточки",
|
||||
"wall": "Настенные панели",
|
||||
"mapPrint": "Печать карты"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Лист карты страны",
|
||||
"mapSheetPacketPage": "Страница карты в пакете",
|
||||
"packetFull": "Молитвенный пакет, полный",
|
||||
"packetLightweight": "Молитвенный пакет, облегчённый",
|
||||
"cards": "Молитвенные карточки",
|
||||
"blessPanels": "Панели стены «Благословить»",
|
||||
"mapPrint": "Печать карты"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "Галерея пока не рисует пакет. В сведениях указаны его генератор и вариант.",
|
||||
"cards": "Галерея пока не рисует карточки. В сведениях указаны генератор и формат карточки.",
|
||||
"bless": "Движка этого шаблона пока нет в общей библиотеке, поэтому галерея не может его нарисовать.",
|
||||
"mapPrint": "Этот шаблон печатает живую карту, на которую смотрит читатель, поэтому у него нет примерного изображения."
|
||||
},
|
||||
"details": {
|
||||
"configName": "Название конфигурации",
|
||||
"copyName": "Копировать",
|
||||
"size": "Размер",
|
||||
"fileSize": "Размер файла",
|
||||
"fileSizeUnknown": "Измеряется, когда плакат нарисован",
|
||||
"settings": "Параметры",
|
||||
"sample": "Примерные данные",
|
||||
"renderer": "Генератор",
|
||||
"legacy": "Устаревший размер: он ещё печатается, но в выборе печати его больше нет.",
|
||||
"forAgents": "Чтобы использовать этот шаблон, скажите агенту: используй конфигурацию {name}."
|
||||
},
|
||||
"templateCount": "{n} шаблон | {n} шаблона | {n} шаблонов | {n} шаблонов"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV сохранён — народы: {count}",
|
||||
"csvFailed": "Не удалось экспортировать CSV: {reason}",
|
||||
"copySelection": "Копировать",
|
||||
"exportCsv": "Экспорт в CSV"
|
||||
"exportCsv": "Экспорт в CSV",
|
||||
"copyNotRegroupable": "Нельзя перегруппировать вместе: копируйте отдельно штаты или провинции одной страны либо страны",
|
||||
"displayName": "Плакаты списка UPG 100",
|
||||
"wallHeading": "Плакаты списка UPG 100"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/ru/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "Этот плакат предназначен для типографии: в окне печати выберите «Сохранить как PDF», затем отправьте файл в типографию."
|
||||
}
|
||||
5
library/i18n/locales/zh/countryPosters.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"indiaNote": "印度有单独的一栏,排在最前:这里有 {count} 个未得之民族群,远多于任何其他国家,因此它的每个邦和中央直辖区都有自己的海报。",
|
||||
"indiaStateNoGroups": "此处未列出任何未得之民族群。",
|
||||
"indiaItemCount": "{n} 个邦和中央直辖区"
|
||||
}
|
||||
51
library/i18n/locales/zh/posterDesign.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"showFullDetails": "显示完整详情",
|
||||
"showFullDetailsAria": "显示{label}的完整详情",
|
||||
"tab": {
|
||||
"styles": "样式",
|
||||
"sizes": "尺寸"
|
||||
},
|
||||
"styles": {
|
||||
"heading": "海报样式",
|
||||
"intro": "所有海报模板集中在一处,均由组件库用示例数据绘制。每张卡片都以其配置命名,方便你请代理准确使用那一种。"
|
||||
},
|
||||
"sizes": {
|
||||
"heading": "海报尺寸",
|
||||
"waiting": "共享尺寸表发布后,尺寸阶梯(标准纸张、3 × 3 英尺、3 × 5 英尺、5.5 × 5.5 英尺和 5 × 8 英尺)将显示在这里。每种尺寸都会以打印预览呈现,仅限纵向。"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "地图页",
|
||||
"packet": "祷告资料包",
|
||||
"cards": "祷告卡",
|
||||
"wall": "墙面展板",
|
||||
"mapPrint": "地图打印"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "国家地图页",
|
||||
"mapSheetPacketPage": "资料包地图页",
|
||||
"packetFull": "祷告资料包(完整版)",
|
||||
"packetLightweight": "祷告资料包(精简版)",
|
||||
"cards": "祷告卡",
|
||||
"blessPanels": "祝福墙展板",
|
||||
"mapPrint": "地图打印"
|
||||
},
|
||||
"missing": {
|
||||
"packet": "图库暂不绘制资料包。详情中列出了它的生成器和版本。",
|
||||
"cards": "图库暂不绘制祷告卡。详情中列出了生成器和卡片规格。",
|
||||
"bless": "此模板的引擎尚未进入共享组件库,因此图库无法绘制。",
|
||||
"mapPrint": "此模板打印读者正在查看的实时地图,因此没有示例图片。"
|
||||
},
|
||||
"details": {
|
||||
"configName": "配置名称",
|
||||
"copyName": "复制",
|
||||
"size": "尺寸",
|
||||
"fileSize": "文件大小",
|
||||
"fileSizeUnknown": "海报绘制后测量",
|
||||
"settings": "设置",
|
||||
"sample": "示例数据",
|
||||
"renderer": "生成器",
|
||||
"legacy": "旧尺寸:仍可打印,但打印选择器已不再提供。",
|
||||
"forAgents": "要使用此模板,请告诉代理:使用配置 {name}。"
|
||||
},
|
||||
"templateCount": "{n} 个模板"
|
||||
}
|
||||
|
|
@ -14,5 +14,8 @@
|
|||
"csvSaved": "CSV 已保存 — 族群:{count}",
|
||||
"csvFailed": "CSV 导出失败:{reason}",
|
||||
"copySelection": "复制",
|
||||
"exportCsv": "导出 CSV"
|
||||
"exportCsv": "导出 CSV",
|
||||
"copyNotRegroupable": "无法一起重新分组:请分别复制同一国家的邦或省,或各个国家",
|
||||
"displayName": "UPG 100 名单海报",
|
||||
"wallHeading": "UPG 100 名单海报"
|
||||
}
|
||||
|
|
|
|||
3
library/i18n/locales/zh/wallPrint.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printShopHint": "此海报需由印刷店印制:在打印对话框中选择“另存为 PDF”,然后将文件发送给印刷店。"
|
||||
}
|
||||
|
|
@ -58,8 +58,9 @@
|
|||
* keeps the renderer's own default rather than a fabricated position.
|
||||
*
|
||||
* A null sheet is a tier too: India (sheet null, too many groups for any paper size) is its own
|
||||
* 1-of-1 series. Skipping it printed "map 1 of 0" on India's packet page 1 from the Regions tab
|
||||
* while the wall's own copy printed "map 1 of 1" -- the two paths disagreeing again.
|
||||
* 1-of-1 series. Skipping it gave India's sheet "map 1 of 0" on the Regions path and "map 1 of 1" on the
|
||||
* wall's old copy. Neither was visible then (the map-only packet page prints no series line, and India's
|
||||
* poster is gated), but any India sheet with a header would have shown it.
|
||||
* Every null-sheet country shares that ONE oversized series: a second would make India "map 1 of 2".
|
||||
* check:country-sheet-one-path fails when that happens, so it is decided, not drifted into.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -143,5 +143,49 @@
|
|||
"emit-countries.mjs), so the SHAPE was replaced: not 'split the set' but 'ask which one,",
|
||||
"then give them that poster'. The numbers are gone from the headings for the same reason."
|
||||
]
|
||||
},
|
||||
"indiaState": {
|
||||
"label": "India states and union territories — one poster for each of the 34 with people groups listed",
|
||||
"dataDir": "plugin-designs/my-upg-100-list/data",
|
||||
"countryMode": true,
|
||||
"funnel": false,
|
||||
"poster": {
|
||||
"kicker": "Unreached People Groups",
|
||||
"titleQualifier": "Pray for",
|
||||
"seriesName": "India State Maps",
|
||||
"sideBoxHeading": "What is a State Prayer Map?",
|
||||
"sideBoxBody": "This map is one of a {approxLists}-map set called the {seriesName} — one sheet for each state and union territory of India, naming the unreached people groups who live there so a reader can pray for them by name."
|
||||
},
|
||||
"_poster_why": [
|
||||
"A state poster is a country poster one level down: the Country Posters wall prints one for each of India's",
|
||||
"states and union territories that has people groups listed (34 of 36; data/people-groups/india-states.json).",
|
||||
"The country series' text cannot be reused: it would call Gujarāt's poster one of the ~170 'Country Maps',",
|
||||
"one sheet for each country. The term is 'state and union territory', never 'province'. Approved as the",
|
||||
"default wording 2026-09-30 and shown as editable; a change is a strings-only follow-up."
|
||||
],
|
||||
"strings": {
|
||||
"map_page_kicker": "PRAY FOR THIS STATE",
|
||||
"map_page_sub": "A prayer list of {count} unreached people groups who live here",
|
||||
"points_intro": "One hour with God, five minutes each. Pray these twelve for the peoples of this state.",
|
||||
"christian_lede": "This is the same grid — one human life in weeks. Now each box is a share of the people in this state. The shaded boxes are the evangelical share of their population; the rest are waiting."
|
||||
},
|
||||
"_strings_why": [
|
||||
"The country series' four keys with 'country' read as 'state'. Used by the Node generator; the browser",
|
||||
"packet reads the localized packet-strings.json, which is series-invariant."
|
||||
],
|
||||
"networks": [
|
||||
[
|
||||
"1 · Disciples.",
|
||||
"Ask a disciple which state God has put on their heart, then give them that state's poster to print and prepare."
|
||||
],
|
||||
[
|
||||
"2 · Churches.",
|
||||
"List the churches you know and ask each the same question, so every church has a poster to pray over together."
|
||||
],
|
||||
[
|
||||
"3 · Indigenous leaders.",
|
||||
"Ask which leaders you know who live in one of these states, and give them their own state's map to pray for it specifically."
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
package.json
|
|
@ -251,6 +251,7 @@
|
|||
"check:window-globals-released": "node internal/check-window-globals-released.mjs",
|
||||
"check:poster-card-fits": "node internal/check-poster-card-fits.mjs",
|
||||
"check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs",
|
||||
"check:poster-design-catalogue": "node plugin-designs/poster-design/scripts/check-catalogue.mjs",
|
||||
"check:poster-fetch-guard": "node internal/check-poster-fetch-guard.mjs",
|
||||
"check:poster-print-fallback": "node internal/check-poster-print-fallback.mjs",
|
||||
"check:posters-exportable": "node internal/check-posters-exportable.mjs",
|
||||
|
|
@ -347,7 +348,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:wall-photo-weight check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -402,6 +403,7 @@
|
|||
"check:wall-print-watch": "node plugin-designs/wall-of-people-groups/scripts/check-wall-print-watch.mjs",
|
||||
"check:wall-no-bg-photos": "node plugin-designs/wall-of-people-groups/scripts/check-wall-no-bg-photos.mjs",
|
||||
"check:wall-pan": "node plugin-designs/wall-of-people-groups/scripts/check-wall-pan.mjs",
|
||||
"check:wall-photo-weight": "node plugin-designs/wall-of-people-groups/scripts/check-wall-photo-weight.mjs",
|
||||
"check:template-i18n": "node plugin-designs/template-bundle/scripts/check-template-i18n.mjs",
|
||||
"check:snippets-draw": "node internal/check-snippets-draw.mjs",
|
||||
"check:snippets-draw:mapbox": "node internal/check-snippets-draw.mjs --mapbox",
|
||||
|
|
@ -427,6 +429,7 @@
|
|||
"check:wall-print-watch": "the Wall's HD poster print never prints silently incomplete: with fake timers and a fake frame, the watch counts photos as they load, counts the ones that could not, and reports both. Two-sided on planted printWatch copies. Headless.",
|
||||
"check:wall-no-bg-photos": "the Wall's store downloads no photos on its own (it used to test all 932, 132 MB, on every visit): the REAL store runs through Vite against the committed fixtures/pray-list-sample.json with fetch stubbed and every Image request counted. Two-sided with the old background test planted. Headless, no network.",
|
||||
"check:wall-pan": "the Wall pans and zooms with the pointer 1:1: the REAL WallCanvas methods (through Vite) with d3-zoom's transform arithmetic, over 12 fits and zooms: a pan moves every card exactly as far as the pointer, a zoom keeps the point under the pointer, toWorld inverts toScreen, and a fling never loses the wall. Two-sided: the old transform-inside-the-fit formulas are planted and caught. Headless.",
|
||||
"check:wall-photo-weight": "the Wall's poster loads its photos at print size: the 77 IMB photos (1024 px, ~1.5 MB each, 114 MB of the print's 133 MB) map to their host's 320 px copies on either host, with the original as the onerror fallback, every other photo untouched, and the print HTML and the screen canvas both go through photoUrl.js. Two-sided (the old full-size URLs are caught). Headless.",
|
||||
"check:template-i18n": "the starter template every new plugin copies renders its text from the catalogue: profile B, server-rendered with the REAL template.json in en, ar and zh, takes its heading, note and sentence from the page's language. Two-sided with the English catalogue served as Arabic. Headless (SSR).",
|
||||
"check:coverage-consistent:chained": "every clause of check:coverage-consistent EXCEPT the link-verification age (VERIFY_MAX_AGE_DAYS set out of reach): the chains run what depends on the code and data, and leave what depends on the calendar to the on-demand run, so the gates cannot turn red on a date with no change.",
|
||||
"check:viewport-meta": "every generated page declares <meta name=\"viewport\"> with width=device-width. Without it a browser lays the page out at the 980px desktop fallback even when the viewport is set to 390, so a mobile probe silently measures a viewport no phone has and still passes (measured: innerWidth 980 without the tag, 390 with it). width=device-width is required specifically, because a meta carrying only initial-scale still lays out at 980. Checked on the BUILT tree, not the generator template. Fixed-size print sheets are exempt by their stated shape (a .stage with a three-digit px width), and a page that must render at a fixed desktop width carries a viewport-meta-ok: reason. 8 self-tests. Source-only, headless.",
|
||||
|
|
@ -451,7 +454,7 @@
|
|||
"check:composite-token-url": "a composite embedded BARE (no outer ProfileLoader) must honour a tokenUrl-only config: it reads tokenUrl, forwards it, and asks the token service only when no token is given. The expectation is derived from ProfileLoader, not restated. Headless; in gates:pre.",
|
||||
"check:composites-mount": "every capability-4/5 composite's OWN setup() and mounted hooks run without throwing, in en and ar, headless (no browser): vite loads each composite as its bundle entry defines it (configureApp, and ProfileLoader's provides where the entry mounts ProfileLoader), and @vue/runtime-core mounts it on a no-op renderer. Exists because 08f89f55 shipped two composites calling getRuntimeLocale/detectLocale/RTL_LOCALES with no import: the build was green, setup threw a ReferenceError in every locale, and Big Picture and Simple Research rendered nothing. Proves setup runs; cannot prove the children render.",
|
||||
"check:country-names": "every ISO 3166-1 alpha-3 our data uses must resolve to a localized country name. Guards the JOIN, not Intl: library/data/iso3-to-alpha2.json drifting from library/data/countries.json makes a country render as a bare code, which still looks like a value. Checks en/ar/zh (ar is our only RTL locale; zh makes an untranslated Latin code unmistakable) and asserts BOTH directions of drift. Carries two negative arms — an unassigned code and a malformed one — and FAILS if either resolves, because a coverage count cannot otherwise tell a hit from a lookup that returns its input.",
|
||||
"check:country-sheet-one-path": "a country sheet is drawn through ONE path, @map/packet/countrySheet.js, so one country cannot print two posters. Parses (comments cannot count) every plugin source naming generateCountrySheet/makeCountrySheet: a direct generateCountrySheet in code FAILS, every makeCountrySheet call must pass `countries` (the series list), and both known callers (the Country Posters wall, regions-list) must be found. Also runs seriesFor over the real country-posters.json with named answers: India 1 of 1, every 3x3 country N of the 3x3 tier with no repeat, a code absent from the data no series.",
|
||||
"check:country-sheet-one-path": "a country sheet, and an Indian state or union territory sheet, each draw through ONE path (@map/packet/countrySheet.js; country-posters/src/indiaStateSheet.js), so one place cannot print two posters. Parses (comments cannot count) plugin sources: a direct generateCountrySheet FAILS anywhere, a direct generateSheet FAILS inside country-posters outside the state helper, every makeCountrySheet call must pass `countries`, and the known callers must be found. Named data answers: seriesFor over country-posters.json (India 1 of 1, each 3x3 country N of 173, an absent code no series, exactly one no-sheet country); over the served india-states.json + gb-IND-ADM1.geojson: 36 divisions, the 34 with groups numbered 1..34 each with an outline, the rest named, the shared ADM1 never mutated.",
|
||||
"check:country-sheet-one-path:self-test": "plants each defect check:country-sheet-one-path guards (a direct generateCountrySheet call, makeCountrySheet without countries, the old guard that skipped India, a position given to an absent code) and requires each to FAIL; a comment naming generateCountrySheet must NOT.",
|
||||
"check:coverage-consistent": "the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.",
|
||||
"check:csv-shape": "the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) — the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.",
|
||||
|
|
@ -579,11 +582,12 @@
|
|||
"check:print-frame-reclaim": "a print frame is kept until it has printed: openPrintFrame reclaims it 60 s after its load (not after its creation), at afterprint, or at a 10-minute ceiling, and printImageBlob revokes its object URL on the same clock. Fake timers; two-sided (the pre-fix copy fails). The Wall poster loads 132 MB of photos, and the old 60 s-from-creation clock removed the frame before it could print.",
|
||||
"check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.",
|
||||
"check:poster-degrades": "with NO stored poster, NO source and NO generator (forced in the browser: every posters/*.webp and *.html answers 404, lib/poster-render.js is aborted), nothing must print and the reader must be told why. Measures any served tree; NOT MEASURED without a wall url.",
|
||||
"check:poster-design-catalogue": "Poster Design's configuration catalogue (plugin-designs/poster-design/src/templates.js) says only true things about the library: its sheet formats are exactly poster-render.js FORMATS at the same pixel sizes (the generator is a pinned asset, so the table cannot be imported); every configuration name is unique and well formed; the card entries are exactly card-render STOCK; every catalogue key it names exists in en/posterDesign.json; the sample country can be drawn. Six mutants (px drift, a missing format, a duplicate name, a bad key, an undrawable sample, a dropped stock) each FAIL.",
|
||||
"check:poster-fetch-guard": "a host's 200 fallback page is never taken for a poster. On a drop-in dev host a missing posters/<id>.webp answered 200 text/html, and export handed that page to the print dialog as the image. Asserts library/packet/poster-fetch.js accepts a raster only if it is an image (by type or by its first bytes) and a source only if it is a poster document (class=\"stage\"), and that neither usePosterPrints nor PosterWall fetches posters/ directly. Node only, no server.",
|
||||
"check:poster-layout-optin": "the composed poster sheet (header, map, legend beside it, footer) is OPT-IN: spec.layout === 'poster' asks for it, and every caller that does not gets the full-page map exactly as before, asserted by object identity, not just by value. Drives the real layout decisions with real spec objects. Headless, no browser.",
|
||||
"check:poster-print-fallback": "clicks the REAL poster print control with the served poster generator and every posters/*.webp aborted, and asserts the preview RASTERISES from posters/<id>.html instead of opening on \"Poster not available yet\". Needs a served tree built with posterSources:true; NOT MEASURED otherwise.",
|
||||
"check:posters-exportable": "every poster the tree offers can be printed. With posterSources on, the tree once shipped only the 28 zone sources, so 350 of 378 posters said \"poster not found (404)\". Each id, from data/posters and the wall's previews, must ship a real WebP (RIFF..WEBP) or a real poster source (its class=\"stage\" element), so a placeholder or a host's fallback page cannot pass. SKIPs when posters are off by config, which check:poster-degrades covers. Reads the built tree; no server.",
|
||||
"check:posterverse-shelf": "Posterverse (the capability-5 poster shell) shows exactly its allow-list and never a gated plugin. Asserts the rail rows per section (Posters: upg-100, country-posters, poster-design; Experimental: wall-of-people-groups, all-people-groups, upg-neighbor, bible-posters), that no row names key-cities or a shell (a key-cities bundle is planted into the built manifest, because the real one withholds gated profiles), that every present row opens the profile it names (no silent fallback to a leaf), that posterverse.meta.json composes equals the rows whose bundle exists, and 11-locale parity of the shell strings. --self-test plants one defect per arm. Headless. Exit 0 pass, 1 fail, 75 no built manifest.",
|
||||
"check:posterverse-shelf": "Posterverse (the capability-5 poster shell) shows exactly its allow-list and never a gated plugin. Asserts the rail rows per section (Posters: upg-100, country-posters, poster-design; Experimental: wall-of-people-groups, bible-posters), that no row names key-cities or a shell (a key-cities bundle is planted into the built manifest, because the real one withholds gated profiles), that every present row opens the profile it names (no silent fallback to a leaf), that posterverse.meta.json composes equals the rows whose bundle exists, and 11-locale parity of the shell strings. --self-test plants one defect per arm. Headless. Exit 0 pass, 1 fail, 75 no built manifest.",
|
||||
"check:prayer-walk-planner": "the Prayer Walk Planner is a full-page tool that prints a real walking map: the map canvas fills the page below the toolbar in its own leaf AND inside the Research Gallery at 1280x800 and 390x844 (the gallery once collapsed it to 76px / 0px); a real zone (Balat, Istanbul) renders a >=2000px print map that is not blank, saved to OUT=<dir> so a human can read it; ?lang=es gives a Spanish toolbar; 0 page errors. Run inside the shared browser locks. Exit 2 = SKIP.",
|
||||
"check:preset-config-merge": "a preset page (<bundle>/<profile>/<instance>/index.html) keeps every default in its profile's .meta.json config, with the instance's props merged over them (go-int's upg-neighbor presets lost pinHint:false when props replaced the config). Reads the built tree; SKIP without one.",
|
||||
"check:profile-table": "the prayer packet's profile TABLE (20 full-width rows: picture, generated description, three boxes; PER_PAGE stays 20): a row stays tall enough for a name plus a two-line description at the smallest fit size, and truncation happens only at a whole word. Character-count, not DOM, so Node and the browser agree. Cannot see the RENDERED row height: that is check:packet-render, a browser gate. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.",
|
||||
|
|
@ -668,7 +672,7 @@
|
|||
"check:popup-pan-controls": "the popup auto-pan reserves the map's bottom control band, so a popup that clears the CLIP is also clear of the things that take the click. Measured at 390 on a 366x732 map: the pan fitted the popup inside the container, cut 0, and a real click on Full Profile still did nothing because the top element at the button's centre was div.maplibregl-ctrl-attrib-inner (and span.pin-hint-pill on the sibling pin) - a cut-height check cannot see this, because the cut really was zero. Attribution is a licence term of the basemap, so the popup may not be raised over it; the usable frame is shorter than the container and the fit test says so. Runs the SHIPPED panPlanFor, read out of the SFC with new Function (it is not exported), never a re-typed copy, and asserts where the popup ENDS UP rather than re-deriving the expected number from the same formula. Sweeps band heights 0-120px and every width the component renders at. Source-only, headless.",
|
||||
"check:control-inset": "bottomControlInset MEASURES the map's real bottom control band, so the pan reserves the band the controls actually occupy. check:popup-pan-controls proves the ARITHMETIC; this proves the band is real. If the inset silently returned 0 -- a selector typo, an engine renaming its control classes, a control moving outside the container -- panPlanFor would get bottomInset 0, behave exactly as it did before the fix, and check:popup-pan-controls would still pass every case, because its arithmetic is still correct: the bug back with both gates green. 8 cases against a real DOM, running the SHIPPED bottomControlInset read out of the SFC (it is not exported), never a copy: the band equals what the DOM independently says (not merely non-zero); no controls reserves 0; a zero-sized control reserves nothing; the taller of two corners wins; a wrapped attribution reserves more than one line; a control outside the container is ignored; a null container returns 0; and the MAPBOX corners count too, since the component takes a provider prop and every other case renders MapLibre names. Needs a browser: every assertion rests on getBoundingClientRect. 75 = no slot, never a pass.",
|
||||
"check:panel-sheets": "the shared panel geometry (library/components/poster/panelSheets.js) for posters printed on several sheets: 60x96 = 3 × 60×36 stacked and 66x66 = 2 × 36×66 side by side, 6 in joins, and a single sheet; the sheets tile the finished poster, glue slices tile and bleed slices repeat exactly the join, a bleed join prints a guide not a solid band, a screen preview always shows the red band, and the Bless 5x8ft takes its bands and marks from the module. Two-sided (4 planted defects). Headless.",
|
||||
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 15 mutants each FAIL."
|
||||
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Section heads: a section with people reads \"<n> people groups · <items>\" exactly as before; one with none reads its items alone. sectionItemCountKey lets a wall count one section in its own words ({ key, n }); unnamed sections keep the default. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 17 mutants each FAIL."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/android": "^7.6.9",
|
||||
|
|
|
|||
BIN
plugin-designs/country-posters/assets/previews/india-an.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
plugin-designs/country-posters/assets/previews/india-ap.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |