Merge remote-tracking branch 'origin/main' into rega/research-gallery-embed

# Conflicts:
#	public/doxa-maps/index.html
This commit is contained in:
doxa-maps-88655c70 2026-09-24 19:57:53 +00:00
commit dd2443b7de
50 changed files with 3592 additions and 1862 deletions

540
README.md
View file

@ -1,165 +1,80 @@
# dx-maps — one build, maps that embed everywhere
> **The map cruncher.** Many hand-built maps go in; one shared library, one repo and one build
> come out — and every map fans out to five deployment targets from the same artifacts.
>
> ```text
> map · map · map · map · map · map · map (7 production maps, once 7 codebases)
> \ \ | | | / /
> +----- plugin-designs/ ------+ one folder per plugin, free zone
> |
> library/ (@map) shared, parameterized, high-bar zone
> |
> ONE bun run build
> |
> doxa-maps/ (drop-anywhere tree: bundles + leaf pages + manifest.json)
> / | \ \ \
> iframe JS-embed mobile WordPress desktop
> (proven) (proven) (built) (built) (built)
> ```
**Write a map once in `plugin-designs/<your-map>/`, run one build, and drop it on any site** —
iframe, script embed, mobile app — or open every map at once as one app. Five plugin
capabilities, five deployment methods, one shared library, one build.
**A Vite bundler that turns each map into a self-contained web component.** Write a map in
`plugin-designs/<your-map>/`, run one build, and get a folder you can drop on any host — iframe it,
script-embed it, wrap it as a mobile app, or open every map at once as one app.
| What dx-maps set out to do | Proof |
|---|---|
| **One build → one folder** you can drop on any host | `bun run check:drop` |
| **Every map embeddable** from a copy-paste snippet | [deployment methods](#the-5-deployment-methods) · `bun run check:embed-targets` |
| **Every map interoperable** through props and events | `bun run verify:interop` |
| **One app nests every map** — and each stays embeddable on its own | [capability 5](#capability-5--app-shell-the-research-gallery) · `bun run check:composite-reg` |
| **5 capabilities × 5 deployment methods** | `bun run verify:5-caps` |
| **Shared library, private plugins** — reuse without breaking each other | `bun run check:library-index` |
| **Many agents, one green `main`** | [agent swarm](#build-it-with-an-agent-swarm) |
---
## What dx-maps is for — and how each objective is met
## The 5 plugin capabilities
Maps multiply a prayer movement only when they can live on *every* partner's site, not just one.
dx-maps exists to make that cheap. It set out to meet seven objectives; each one is met by something
you can open or run, not by a claim.
A capability is **how much a plugin nests**, from one element (1) to an app of apps (5). Every
screenshot is a real bundle from this repo's build.
| # | Objective | How it is met | See it / check it |
|---|-----------|---------------|-------------------|
| 1 | **One build → one folder.** Seven maps that were seven codebases become one repo and one build. | `bun run build` writes a single drop-anywhere tree, `public/doxa-maps/`, with a `manifest.json` describing every bundle. | `bun run check:drop` · `bun run check:settled` |
| 2 | **Every map is embeddable.** Any map, on any site, with a copy-paste snippet. | Each bundle is one custom element; `manifest.json` ships an iframe snippet and an SRI-pinned `<script>` snippet for it. | [Deployment methods](#3-the-5-deployment-methods) · `bun run check:embed-targets` |
| 3 | **Every map is interoperable.** Maps talk to each other through props and events. | Composites wire their children with props, events and shared layouts — no copy-pasted code. | `bun run verify:interop` |
| 4 | **One app nests every map — and each stays independently embeddable.** | The **Research Gallery** is a single capability-5 plugin that nests all seven production maps. | [The Research Gallery](#1-the-research-gallery--five-capabilities-in-one-plugin) · `bun run check:composite-reg` |
| 5 | **5 capabilities × 5 deployment methods.** Anything you build, at any tier, ships everywhere. | A plugin's tier is read from its files; every tier builds to the same tree, and the tree feeds all five methods. | [Capabilities](#2-the-5-plugin-capabilities--most-advanced-first) · `bun run verify:5-caps` |
| 6 | **A clean shared/private boundary.** Reuse without breaking each other. | `library/` holds shared, parameterized pieces (generated index); anything map-specific stays in its own `plugin-designs/` folder. | [reference-library-index.md](./documentation/reference-library-index.md) · `bun run check:library-index` |
| 7 | **Many builders, one green `main`.** The repo is built to be worked by a swarm of agents at once. | One agent per plugin, one worktree each, and every change landed through a merge queue. | [Build it with an agent swarm](#5-build-it-with-an-agent-swarm) |
### Capability 5 — app shell: the Research Gallery
Plus one practical objective: **a token-free option.** Mapbox is the default engine; setting a single
prop, `provider: "maplibre"`, swaps in MapLibre so a partner running many embeds needs no Mapbox key.
`bun run check:provider-switch` proves the switch really changes the engine.
## Contents
1. **[The Research Gallery](#1-the-research-gallery--five-capabilities-in-one-plugin)** — the showcase: five capabilities in one plugin.
2. **[The 5 plugin capabilities](#2-the-5-plugin-capabilities--most-advanced-first)** — what you can build, most advanced first.
3. **[The 5 deployment methods](#3-the-5-deployment-methods)** — where a plugin can run.
4. **[The Plugin Studio](#4-the-plugin-studio)** — ★ where you *work*, and every command to get it running. Start here if you are new.
5. **[Build it with an agent swarm](#5-build-it-with-an-agent-swarm)** — how to run many agents on one laptop without collisions.
---
## 1. The Research Gallery — five capabilities in one plugin
The Research Gallery (`plugin-designs/doxa-maps-spa/`) is the product a user opens. It is **one
capability-5 plugin** — one custom element, `<doxa-maps-spa>` — and every tab inside it is a
**real, independently embeddable plugin** from a lower tier. Nothing is copied into it and nothing is
framed: each tab loads its own bundle and mounts that bundle's own custom element.
One plugin, one element (`<doxa-maps-spa>`), and every tab inside it is a real plugin from a lower
tier that you can still embed on its own. `plugin-designs/doxa-maps-spa/`
![Capability 5 — the Research Gallery app shell (doxa-maps-spa)](documentation/readme-images/cap5-doxa-maps-spa.png)
This is how five different capabilities compose into one:
<details>
<summary><b>What the Research Gallery is made of — all five capabilities in one plugin</b></summary>
```text
Research Gallery <doxa-maps-spa> capability 5 — app shell
│
Research Gallery <doxa-maps-spa> capability 5
├─ Dashboards
│ ├─ Big Picture dashboard-plus-grid capability 4 — composite
│ │ ├─ research dashboard research-dashboard capability 3 — monolithic
│ │ └─ people-groups grid people-groups-grid capability 2 — prop-table
│ ├─ Simple Research research-map-plus-grid capability 4 — composite
│ │ ├─ research map doxa-research-map capability 3 — monolithic
│ │ └─ people-groups grid people-groups-grid capability 2 — prop-table
│ └─ Regions regions-mfe capability 4 — composite
│ ├─ regions list regions-list capability 1 — single
│ └─ region dashboards region-dashboards capability 1 — single
│ ├─ Big Picture dashboard-plus-grid capability 4
│ │ ├─ research dashboard research-dashboard capability 3
│ │ └─ people-groups grid people-groups-grid capability 2
│ ├─ Simple Research research-map-plus-grid capability 4
│ │ ├─ research map doxa-research-map capability 3
│ │ └─ people-groups grid people-groups-grid capability 2
│ └─ Regions regions-mfe capability 4
│ ├─ regions list regions-list capability 1
│ └─ region dashboards region-dashboards capability 1
├─ Prayer Mobilization
│ ├─ UPG-100 List upg-100-mfe capability 4 — composite
│ │ ├─ list upg-100-list capability 1 — single
│ │ └─ poster wall poster-wall capability 1 — single
│ └─ Prayer Progress doxa-simple-map (prayer) capability 2 — prop-table preset
│ ├─ UPG-100 List upg-100-mfe capability 4
│ │ ├─ list upg-100-list capability 1
│ │ └─ poster wall poster-wall capability 1
│ └─ Prayer Progress doxa-simple-map capability 2
├─ Gospel Research
│ ├─ Cities with Internet connectivity-map capability 1 — single
│ └─ Gospel Resources resources-map capability 1 — single
└─ Experimental (folder) every other bundle, discovered automatically from manifest.json
│ ├─ Cities with Internet connectivity-map capability 1
│ └─ Gospel Resources resources-map capability 1
└─ Experimental every other bundle, discovered automatically
```
What that buys you:
</details>
- **Seven production maps, one URL.** The three sections above are the seven curated tabs.
- **Every tab is still a standalone embed.** Want only the UPG-100 list on a partner site? Embed
`upg-100-mfe` — or just its `upg-100-list` leaf. Taking the whole app apart costs nothing.
- **Proving the whole proves the parts.** When the Research Gallery was embedded cross-origin on a
foreign host page (see [deployment](#3-the-5-deployment-methods)), every nested plugin rode along.
- **New plugins appear on their own.** Any bundle not placed in a named section lands in
*Experimental*, read from the built `manifest.json`. A new folder in `plugin-designs/` shows up with
zero edits to the shell.
### Capability 4 — composite: plugins wired together
The tab order and grouping live in one place, `NAV_GROUPS` in
[`doxa-maps-spa.vue`](./plugin-designs/doxa-maps-spa/doxa-maps-spa.vue); `bun run check:spa-tab-order`
guards it.
Nests lower-tier plugins and wires them with props and events. `plugin-designs/my-upg-100-list/`
---
![Capability 4 — the UPG-100 composite (my-upg-100-list)](documentation/readme-images/cap4-upg100-mfe.png)
## 2. The 5 plugin capabilities — most advanced first
| List (cap 1, embeddable alone) | Poster wall (cap 1, embeddable alone) |
|---|---|
| ![UPG-100 list leaf](documentation/readme-images/cap4-upg100-list.png) | ![UPG-100 poster-wall leaf](documentation/readme-images/cap4-upg100-poster-wall.png) |
A "capability" is **how much a plugin nests** — not how hard it is to use. The tour below starts from
the most advanced thing this repo produces (an app of apps) and walks down to the simplest (one
element), because the top of the ladder is the point: every rung below it stays independently
embeddable. Every screenshot is a real bundle from this repo's build output.
### Capability 3 — multi-view: many views, one element
| # | Capability | What it is | Example bundle |
|---|------------|------------|----------------|
| 5 | **Nested-SPA / app shell** | Many SPAs nested into one deep-linkable app. | `doxa-maps-spa` |
| 4 | **Nested-element SPA** | A composite that nests *other plugins* and wires them with props/events. | `big-picture` |
| 3 | **Monolithic element** | One `root.vue` nests many views internally → one element. | `doxa-research-map` |
| 2 | **Prop-table element** | One `root.vue` + a preset table → many maps from one plugin. | `doxa-simple-map` |
| 1 | **Single element** | One folder → one bundle → one custom element. | `all-people-groups` |
> The tier is decided by the files, not by how simple a folder looks: an `*.instances.json` makes a
> bundle **cap-2** however light it is, and a `meta.json` `type` declares tiers 3/4/5. The authoritative,
> generated answer lives in
> **[documentation/reference-capability-exemplars.md](./documentation/reference-capability-exemplars.md)**
> — cite that file rather than remembering.
### Capability 5 — nested-SPA / app shell
Every plugin in one deep-linkable page. Two cap-5 apps ship, and the difference matters when you embed
one: **`doxa-maps-spa` (the Research Gallery, [section 1](#1-the-research-gallery--five-capabilities-in-one-plugin))
is what an end user gets; `plugin-studio` is what a developer works in.**
*`plugin-studio` — the developer studio*, grouping every built plugin by capability (5 → 1). It is
auto-generated: the build discovers every folder in `plugin-designs/`, so a new plugin appears in the
studio without any wiring. This is [section 4](#4-the-plugin-studio).
![Capability 5 — the developer studio (plugin-studio)](documentation/readme-images/cap5-plugin-studio.png)
### Capability 4 — nested-element SPA
A composite that nests **lower-tier plugins** and wires them with props and events. Each inner plugin
stays independently embeddable, and the composite is embeddable too. `plugin-designs/my-upg-100-list/`
ships three leaves:
| Composite (`upg-100-mfe`) | List (`upg-100-list`) | Poster wall (`poster-wall`) |
|---------------------------|-----------------------|-----------------------------|
| ![Capability 4 — the UPG-100 composite (my-upg-100-list)](documentation/readme-images/cap4-upg100-mfe.png) | ![UPG-100 list leaf](documentation/readme-images/cap4-upg100-list.png) | ![UPG-100 poster-wall leaf](documentation/readme-images/cap4-upg100-poster-wall.png) |
### Capability 3 — monolithic element
A whole multi-view map from one `root.vue`. The trade-off: you cannot embed just one of its tabs.
`plugin-designs/doxa-research-map/`
![Capability 3 — the monolithic research map (doxa-research-map)](documentation/readme-images/cap3-monolithic-research.png)
### Capability 2 — prop-table element
### Capability 2 — presets: one plugin, many maps
One `root.vue`, many maps. `plugin-designs/doxa-simple-map/` ships a preset prop-table
(`simple-map.instances.json`) that renders it three ways — each a standalone embeddable page from the
**same** code.
One `root.vue` plus a preset table. `plugin-designs/doxa-simple-map/`
| Prayer | Engagement | Adoption |
|--------|------------|----------|
@ -167,319 +82,130 @@ One `root.vue`, many maps. `plugin-designs/doxa-simple-map/` ships a preset prop
### Capability 1 — single element
One folder, one `.vue`, one embeddable element. `plugin-designs/all-people-groups/` shows every people
group on one map.
One folder, one `.vue`, one element. `plugin-designs/all-people-groups/`
![Capability 1 — a single-element plugin (all-people-groups)](documentation/readme-images/cap1-single-element.png)
---
## 3. The 5 deployment methods
Ordered easiest to hardest. **Status is stated honestly** — "proven" means it has been run and seen, and
nothing below is upgraded past what the files support.
| # | Method | Status | Command / install |
|---|--------|--------|-------------------|
| 1 | **iframe** on any site | ✅ **proven** | `bun run build`, then paste the `embed.iframe` snippet from `manifest.json` |
| 2 | **Mobile app** (Capacitor, Android) | 🛠️ built, not shot | `bun run check:mobile` (no toolchain) · `bun run phone` needs Android SDK + JDK 17 + `adb` |
| 3 | **JavaScript embed** (`<script src>` + SRI) | ✅ **proven** | `bun run build`, then paste the `embed.script` + `integrity` snippet |
| 4 | **WordPress plugin** (shortcode + block) | 🛠️ built, contract-proven, not shot | `bun run check:wp-embed`, then install `deployment/wordpress/doxa-maps-embed/` |
| 5 | **Desktop app** (Tauri) | 🛠️ built, not shot | `bun run check:tauri` (no toolchain) · `cargo tauri build` needs Rust + webview libs |
**What "not shot" means:** the code exists and its contract test passes, but nobody has captured it
running on a real device or install. Each is blocked by a missing local toolchain, not by missing code —
Android SDK for method 2, a live WordPress for method 4, and a GUI for method 5.
Methods **1 and 3 are the two proven-live paths.** If you need a map on a site today, use one of those.
Both were **certified cross-origin on 2026-09-24** — the entire Research Gallery, embedded on a
foreign host page, every inner plugin riding along (prove the whole = prove the parts):
| Method 1 — iframe on a host site | Method 3 — JS embed on a host site |
|----------------------------------|-------------------------------------|
| ![The cap-5 app inside a host page iframe](documentation/readme-images/deploy1-iframe-hostsite.png) | ![The cap-5 app JS-embedded on a host page](documentation/readme-images/deploy3-js-embed-hostsite.png) |
> For a JS embed on another site the host page sets `window.MAP_TOKEN` (nested elements read it)
> and the serving layer must send `Access-Control-Allow-Origin: *` — the tree is fetched
> cross-origin by `manifest.json`-composed surfaces.
The full per-method command matrix, with every prerequisite, is in
**[documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md)**.
### The Mapbox token (read this before you file a bug)
The maps use Mapbox by default, which needs a `pk.` token. **The token works differently in dev than in
the built tree**, and diagnosing one from the other is the most common way to lose an hour.
| Where | How it gets a token |
|---|---|
| **Dev server** (`bun run dev`) | Baked into the page for you — from `.env` (`VITE_MAPBOX_TOKEN`) if present, otherwise from `token` in `vite.doxa-maps-build-config.json`. |
| **Built pages** (`bun run serve`, or hosted) | **Not baked.** Each page resolves a token at runtime: `?tk=<TOKEN>` on the URL, a token you paste into the page's own input box (kept in `localStorage`), or a token endpoint (`tokenUrl`). |
So on a built leaf page you typically need:
```
http://localhost:4173/doxa-maps/demo-map/demo-map/index.html?tk=pk.your_token_here
```
To use your own token in dev, create a `.env` — it is **gitignored**, so it never reaches the repo:
```bash
printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env
```
**If a basemap is an empty grey void while the pins still draw, that is the token failing** — not a
broken app.
**Or skip the token entirely.** A public `pk.` token can be restricted to specific URLs in the Mapbox
dashboard, but a JAMstack embed still has nowhere to hide one. So the engine is one prop: set
`provider: "maplibre"` on an instance (or add `?provider=maplibre` to a leaf page) and the map renders
on MapLibre with no Mapbox key. `plugin-designs/doxa-research-map/research-map.instances.json` ships a
ready-made MapLibre instance. The trade-off is deliberate: under MapLibre the place-search bar is
hidden, because the geocoder would still bill the Mapbox key the instance exists to avoid.
> Which bundle sits at which tier is generated, not remembered:
> [documentation/reference-capability-exemplars.md](./documentation/reference-capability-exemplars.md).
---
## 4. The Plugin Studio
## The 5 deployment methods
★ **This is where you work — and the keyword is INTEROPERABILITY.**
### 1. iframe — the prayer map on any site ✅ production
The Plugin Studio is one place to **view all of your plugins at once** and **edit all of them in one
place using your agent**. Every plugin appears in a single view, sorted **highest capability down to
lowest**, so you can see the whole catalogue instead of hunting through folders.
One `<iframe>` tag, no build step. Copy the `embed.iframe` snippet from `manifest.json`.
That sorting is the point. Once you can see every plugin side by side, you can tell your agent **which
ones to group together** — any number of them — and compose them into **higher-capability plugins**
using **props, events and layouts**. Group cap-1 and cap-2 elements and you get a cap-4 nested-element
SPA; group those and you get a cap-5 app shell. That is exactly how the Research Gallery was built.
![Method 1 — the prayer map embedded in a host page's iframe](documentation/readme-images/deploy1-iframe.png)
### Get it running
### 2. Mobile app — Simple Research on a phone ✅ built
Four steps from nothing to every map in one place. You need **git**, **[Bun](https://bun.sh)** and
**Node ≥ 18**.
The Simple Research dashboard (research map + people-groups grid), shipped on its own as a
Capacitor app. `bun run check:mobile` · `bun run phone` (needs Android SDK + JDK 17)
```bash
# 1. Clone (the repo root IS the studio — there is no subfolder to cd into)
git clone https://git.nuxtbase.app/doxa-life/doxa-maps.git
cd doxa-maps
<p align="center"><img src="documentation/readme-images/deploy2-mobile-simple-research.png" alt="Method 2 — the Simple Research dashboard at phone size" width="320"></p>
# 2. Install dependencies
bun install --frozen-lockfile
### 3. JavaScript embed — the Research Gallery on dx-life ✅ production
# 3. Optional — your own Mapbox token for dev (see section 3)
printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env
The whole capability-5 app on the dx-life research page, from one `<script src>` and one element —
replacing the separate research map, list and regions dashboards the page used to carry. Copy the
`embed.script` snippet (SRI-pinned) from `manifest.json`.
# 4. Open the Plugin Studio
bun run dev
```
![Method 3 — the Research Gallery JS-embedded on the dx-life research page](documentation/readme-images/deploy3-js-embed-dxlife.png)
Then open **http://localhost:5173/** — that root URL *is* the Plugin Studio, with every plugin in one
spot. (If port 5173 is busy Vite picks another one and prints it; read the line it logs.)
### 4. WordPress / Disciple Tools plugin — 🧪 proof of concept
> **You do not need to build first.** The build output in `public/` is committed, so a fresh clone has
> plugins to show the moment the dev server starts.
A shortcode + block plugin in `deployment/wordpress/`; its contract test passes
(`bun run check:wp-embed`). Proven, not production-ready.
Two things worth knowing:
### 5. Desktop app (Tauri) — 🧪 proof of concept
- **`/` is the studio; `/__gallery` is the old staging gallery**, kept but demoted to an explicit URL.
- **Edits hot-reload.** The studio mounts the real `plugin-studio` custom element through its own
`index.js` — the same path the built bundle takes — so what you see is the real element, not a preview.
A desktop binary and `.deb` build from `deployment/src-tauri/` (`bun run check:tauri`). Proven,
not production-ready.
### Every command you need
| Command | What it does |
|---|---|
| `bun run dev` | The Plugin Studio with hot reload, at http://localhost:5173 |
| `bun x vite --port 5299 --strictPort` | A dev server on your own port (see the swarm rules below) |
| `BUNDLE=<name> bun x vite build` | Build **one** bundle, without cleaning anyone else's output |
| `BUILD_ALLOW=1 bun run build` | The full production tree in `public/doxa-maps/` (~4 minutes, takes a lock) |
| `bun run serve` | Serve the built tree at http://localhost:4173, the way production does |
| `bun run check:settled` | Exit 0 = the built tree is safe to measure or publish right now |
| `bun run gates:pre` / `bun run gates:post` | The static and post-build verification gates |
| `bun run verify:5-caps` · `bun run verify:interop` | Prove the five capabilities, and cap-4 interoperability, in a real browser |
| `bun run shots` | Regenerate the README screenshots |
The full list — every deployment command with its prerequisites — is in
[documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md).
### Work with your agent
The studio's companion is your coding agent. The loop:
1. Open the studio (`bun run dev`) and look at the catalogue, sorted cap-5 → cap-1.
2. Tell your agent which plugins to **group** — e.g. *"combine the research map and the people-groups
grid into one composite."*
3. The agent writes a new folder in `plugin-designs/<name>/`, wiring the children with props, events and
a layout from `library/layouts/`.
4. Refresh the studio. The new, higher-capability plugin is there.
**The whole contract is: write your app inside `plugin-designs/<your-map>/`.** There is no registration
step and no per-plugin wiring — the build discovers every folder under `plugin-designs/`. Copy
`plugin-designs/template-bundle/` to start a new one.
Two shared layouts do the composing, both in `library/layouts/`:
- **`VerticalTabsLayout`** — one element visible at a time, lazy-mounted on first open.
- **`StackLayout`** — the opposite: elements stacked top-to-bottom at natural height, so the page
scrolls through them.
### The production build
The dev server is for working; **you do not need a build to see a change.** When you want the
deployable tree:
```bash
BUILD_ALLOW=1 bun run build # ~4 minutes; writes public/doxa-maps/
bun run serve # serve the BUILT tree at http://localhost:4173
```
**`BUILD_ALLOW=1` is required on purpose.** Without it the build refuses (exit 75) and prints cheaper
alternatives, because a full build wipes the output tree that every other agent in a swarm is
reading. On your own machine, with no one else working, set it.
The build also takes a **lock**: if another build is already running it fails fast rather than
corrupting the shared output. Set `BUILD_LOCK_WAIT=600` to queue behind it instead.
**The lock protects a second WRITER. It does not protect a READER.** Every build begins by deleting
`public/doxa-maps/`, so while a build runs the tree is transiently near-empty — and anything reading
it then gets a confident, wrong answer ("that bundle is missing"). Run `bun run check:settled` before
any browser check, any publish, and any claim about what the tree contains, and **grade it by exit
code**: like every gate here, it prints explanatory prose after its verdict, so `| tail -1` will
mislead you. Do not substitute a `pgrep` check — a process list samples an instant, while the lock
knows the queue.
> **Mapbox token:** the dev server bakes one in; built pages take `?tk=pk.…`, a pasted token, or a
> token endpoint. Or skip Mapbox entirely: `provider: "maplibre"` on any instance renders on MapLibre
> with no key. Every command and prerequisite:
> [documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md).
---
## 5. Build it with an agent swarm
## Build it with an agent swarm
This repo is designed for **many builders working in parallel without stepping on each other** — and
the builders can be coding agents on your own laptop. It is **trunk-based development with a merge
queue**, the same model as GitHub's merge queue, GitLab's merge trains and Rust's bors, arranged as
concentric rings:
Trunk-based development with a **merge queue** — the model behind GitHub's merge queue and GitLab's
merge trains. Many agents build in parallel, each in its own worktree; changes land on `main` one
at a time, rebased and gated, so `main` stays green.
![The delegation model: agents in their own worktrees send changes through one merge queue into main](documentation/readme-images/delegation-model.png)
- **`main` — the trunk.** The one shared branch. Everything converges here, and it is always green.
- **Merge queue.** Changes land **one at a time, in order.** Each change is rebased onto the latest
`main` — its author resolves any conflicts — and lands only if the gates pass. A change that fails
leaves the queue; the next one goes. Nothing writes to `main` except the queue.
- **Plugin Studio.** One repo and one shared `library/`, with every plugin visible in one place, so
each builder can see what already exists and reuse it instead of rebuilding it.
- **Agents.** **One agent per plugin, each in its own git worktree.** Each owns its plugin end to end;
its private components stay inside its own `plugin-designs/` folder.
- **Maintainer.** One reviewer — human or agent — keeps everyone honoring the model: plugins built in
the studio, shared components genuinely shared, and every change going through the queue.
The effect is cooperative: every agent races to land working features, but the only way to land is
through the queue, one change at a time, **leaving `main` green.**
The diagram's source is [`documentation/readme-images/src/delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
### Spin one up on your laptop
**1. One worktree per agent**, as sibling folders, so no two agents share a working directory:
```bash
git worktree add -b regions/work ../dx-maps-regions main
git worktree add -b upg100/work ../dx-maps-upg100 main
git worktree add -b maintainer/work ../dx-maps-maintainer main
git worktree list
```
# one worktree + one agent per plugin
git worktree add -b regions/work ../dx-maps-regions main
git worktree add -b upg100/work ../dx-maps-upg100 main
**2. One agent per worktree, each owning one plugin.** Start your coding agent in each folder and give
it a single `plugin-designs/<name>/` folder to own. The maintainer gets no plugin; it reviews what
lands and pushes back when shared code is duplicated or a plugin reaches into another's folder.
# one dev server per agent, each on its own port
bun x vite --port 5301 --strictPort
**3. One dev server per agent, each on its own port, always with `--strictPort`:**
```bash
bun install --frozen-lockfile # once per worktree
bun x vite --port 5301 --strictPort # regions agent
bun x vite --port 5302 --strictPort # upg100 agent
```
Without `--strictPort`, Vite silently moves a busy port to the next one — and several gates have
`5173` written into them, so a drifted server leaves those gates reporting a confident wrong answer
about somebody else's page. With `--strictPort` the duplicate exits non-zero instead.
**4. One owner for the full build.** Agents edit source and check their own bundle with
`BUNDLE=<name> bun x vite build`. Only one agent runs `BUILD_ALLOW=1 bun run build`, because every full build wipes
the shared output tree first.
**5. Land through the merge queue.** On one machine, a file lock is a complete merge queue — agents
wait their turn on it (`flock` ships with Linux; on macOS, `brew install flock`):
```bash
# land through the queue: one at a time, rebased, gated
flock /tmp/dx-maps-main.lock sh -c '
git fetch origin &&
git rebase origin/main &&
bun run gates:pre &&
git push origin HEAD:main
'
git fetch origin && git rebase origin/main &&
bun run gates:pre && git push origin HEAD:main'
```
The lock guarantees one landing at a time; the rebase makes the lander resolve every conflict; the
gates keep `main` green. When several machines share one remote, use the hosting platform's merge
queue instead (GitHub merge queue, GitLab merge trains), or protect `main` so only one builder at a
time may push.
One agent owns the full build (it wipes the shared output first); everyone else checks their own
bundle with `BUNDLE=<name> bun x vite build`. A **maintainer** reviews what lands and keeps shared
code shared. Agents joining this repo: read [AGENT-BRIEF.md](./AGENT-BRIEF.md).
Diagram source: [`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
Agents joining this repo should also read [AGENT-BRIEF.md](./AGENT-BRIEF.md).
---
## The Plugin Studio — ★ where you work
Every plugin in one place, sorted by capability. Pick any plugin to run it live, see what it is
composed of, and copy its embed code. Tell your agent which plugins to **group**, and it composes
them into a higher-capability plugin with props, events and a layout — that is how the Research
Gallery was built. *(The studio is being redesigned; this screenshot tracks it.)*
![Capability 5 — the developer studio (plugin-studio)](documentation/readme-images/cap5-plugin-studio.png)
### Get it running
You need **git**, **[Bun](https://bun.sh)** and **Node ≥ 18**.
```bash
git clone https://git.nuxtbase.app/doxa-life/doxa-maps.git
cd doxa-maps
bun install --frozen-lockfile
bun run dev # → http://localhost:5173/ is the Plugin Studio
```
No build needed first — the built tree is committed. To start a plugin, copy
`plugin-designs/template-bundle/` to `plugin-designs/<your-map>/`; the studio finds it automatically.
| Command | What it does |
|---|---|
| `bun run dev` | The Plugin Studio, hot-reloading, at http://localhost:5173 |
| `BUNDLE=<name> bun x vite build` | Build one bundle only |
| `BUILD_ALLOW=1 bun run build` | The full deployable tree in `public/doxa-maps/` (~4 min; refuses without the flag, to protect a shared tree) |
| `bun run serve` | Serve the built tree at http://localhost:4173 |
| `bun run check:settled` | Exit 0 = the built tree is safe to measure right now |
| `bun run gates:pre` · `bun run gates:post` | The verification gates |
| `bun run shots` | Regenerate these screenshots (`ONLY=<name>` for one) |
---
## Repository layout
```text
doxa-maps/
├── plugin-designs/ YOUR maps live here — one folder per plugin (the FREE zone).
│ ├── doxa-maps-spa/ the Research Gallery, capability 5 (nests the production maps)
│ ├── plugin-studio/ the developer studio, capability 5 (nests every plugin)
│ ├── dashboard-plus-grid/ capability-4 composite: research dashboard + people-groups grid
│ ├── my-upg-100-list/ capability-4: UPG-100 list + poster wall
│ ├── doxa-simple-map/ capability-2: one root.vue, three preset maps
│ └── template-bundle/ copy this to start a new plugin
├── library/ the SHARED zone (@map) — reusable, parameterized only.
│ ├── layouts/ VerticalTabsLayout, StackLayout — how composites compose
│ ├── components/ map controls, legends, search, poster, detail panels
│ ├── composables/ map instance, shadow styles, print-in-place, retry
│ └── styles/ colors/ data/ style registry, palettes, lookup tables
├── data/ the public datasets the maps ship with
├── deployment/ Capacitor (mobile), WordPress plugin, Tauri (desktop)
├── documentation/ tutorial · how-to · reference · explanation + architecture
├── internal/ the build generator + the verification gates
└── public/doxa-maps/ the BUILD OUTPUT — the drop-anywhere tree (bundles, leaf pages,
manifest.json). What gets served and embedded.
plugin-designs/ YOUR maps — one folder per plugin, a free zone
library/ the SHARED toolkit (layouts, components, composables) — a high bar
public/doxa-maps/ the build output: the drop-anywhere tree + manifest.json
deployment/ Capacitor · WordPress · Tauri
internal/ the build generator and the gates
documentation/ tutorial · how-to · reference · architecture — start at documentation/README.md
```
**`plugin-designs/` is a free zone.** Your plugin is its own application in its own folder, building to
its own output. Nothing you do there can break another author's map.
**`library/` is a HIGH BAR.** It is multi-tenant — reused by many maps and authors — so every edit is a
breaking-change risk to everyone. Only contribute here when a component is genuinely reusable *and*
parameterized, so other maps can use it. Keep map-specific, non-parameterized code in your own
plugin-design. See [documentation/contributing/](./documentation/contributing/).
---
## More docs
Start at **[documentation/README.md](./documentation/README.md)** — the index of everything below.
| Read this | When |
|---|---|
| **[documentation/develop-a-plugin-and-see-it-live.md](./documentation/develop-a-plugin-and-see-it-live.md)** | ★ the golden path — zero to a plugin live, in 6 steps |
| **[documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md)** | the full per-method CLI + install matrix |
| **[documentation/reference-capability-exemplars.md](./documentation/reference-capability-exemplars.md)** | which bundle is the capability-N example (generated) |
| **[documentation/reference-library-index.md](./documentation/reference-library-index.md)** | the menu of reusable `library/` pieces you can import |
| **[documentation/why.md](./documentation/why.md)** | 2 min — the problem, the idea, one diagram |
| **[documentation/tutorial.md](./documentation/tutorial.md)** | build your first map, step by step |
| **[documentation/how-to.md](./documentation/how-to.md)** | task recipes (embed, data, mobile) |
| **[documentation/architecture/](./documentation/architecture/)** | the deep end — embeddability, app-wrappers, file map |
---
> **Bottom line: maps should be open source, run everywhere, and multiply.** This bundler makes each map
> a self-contained file that embeds on your site, your partners' sites, WordPress, and mobile — from one
> build. The Plugin Studio is where you compose them into something bigger; the Research Gallery is
> what that looks like when you do.
> **Maps should be open source, run everywhere, and multiply.** One build makes each map a
> self-contained file for your site, your partners' sites and a phone. The Plugin Studio is where
> you compose them into something bigger.

File diff suppressed because it is too large Load diff

View file

@ -14,14 +14,14 @@
"country": "People's Republic of China",
"region": "Asia",
"groups": 391,
"no_resource": 111,
"pop_no_resource": 128103100,
"no_resource": 107,
"pop_no_resource": 127734000,
"scripture": 118,
"film": 227,
"growing_unreached": 0,
"deaths_day": 274.2,
"growth": -0.12,
"pct_no_resource": 28.4,
"pct_no_resource": 27.4,
"band": "partial"
},
{
@ -29,14 +29,14 @@
"country": "Lao People's Democratic Republic",
"region": "Asia",
"groups": 158,
"no_resource": 72,
"pop_no_resource": 982785,
"no_resource": 67,
"pop_no_resource": 841085,
"scripture": 55,
"film": 58,
"growing_unreached": 0,
"deaths_day": 15.5,
"growth": null,
"pct_no_resource": 45.6,
"pct_no_resource": 42.4,
"band": "high"
},
{
@ -44,14 +44,14 @@
"country": "India",
"region": "Asia",
"groups": 2146,
"no_resource": 55,
"pop_no_resource": 1320695,
"no_resource": 42,
"pop_no_resource": 768235,
"scripture": 1757,
"film": 2044,
"growing_unreached": 38,
"deaths_day": 721.2,
"growth": 0.89,
"pct_no_resource": 2.6,
"pct_no_resource": 2,
"band": "partial"
},
{
@ -74,14 +74,14 @@
"country": "Chad",
"region": "Africa",
"groups": 150,
"no_resource": 69,
"pop_no_resource": 2565700,
"no_resource": 58,
"pop_no_resource": 1615300,
"scripture": 46,
"film": 49,
"growing_unreached": 37,
"deaths_day": 68.4,
"growth": 4.95,
"pct_no_resource": 46,
"pct_no_resource": 38.7,
"band": "high"
},
{
@ -89,29 +89,29 @@
"country": "Sudan",
"region": "Africa",
"groups": 185,
"no_resource": 58,
"pop_no_resource": 2809900,
"no_resource": 54,
"pop_no_resource": 2682300,
"scripture": 96,
"film": 117,
"growing_unreached": 36,
"deaths_day": 359.6,
"growth": 0.81,
"pct_no_resource": 31.4,
"band": "high"
"pct_no_resource": 29.2,
"band": "partial"
},
{
"iso3": "NGA",
"country": "Nigeria",
"region": "Africa",
"groups": 433,
"no_resource": 180,
"pop_no_resource": 6759525,
"no_resource": 163,
"pop_no_resource": 5264025,
"scripture": 105,
"film": 188,
"growing_unreached": 0,
"deaths_day": 44,
"growth": null,
"pct_no_resource": 41.6,
"pct_no_resource": 37.6,
"band": "high"
},
{
@ -119,14 +119,14 @@
"country": "Indonesia",
"region": "Asia",
"groups": 669,
"no_resource": 320,
"pop_no_resource": 4968960,
"no_resource": 319,
"pop_no_resource": 4883960,
"scripture": 222,
"film": 265,
"growing_unreached": 23,
"deaths_day": 47.2,
"growth": 0.81,
"pct_no_resource": 47.8,
"pct_no_resource": 47.7,
"band": "high"
},
{
@ -134,14 +134,14 @@
"country": "Afghanistan",
"region": "Asia",
"groups": 45,
"no_resource": 19,
"pop_no_resource": 1159300,
"no_resource": 17,
"pop_no_resource": 908800,
"scripture": 15,
"film": 25,
"growing_unreached": 17,
"deaths_day": 17.6,
"growth": 2.84,
"pct_no_resource": 42.2,
"pct_no_resource": 37.8,
"band": "high"
},
{
@ -179,14 +179,14 @@
"country": "Pakistan",
"region": "Asia",
"groups": 86,
"no_resource": 16,
"pop_no_resource": 10446875,
"no_resource": 15,
"pop_no_resource": 10437875,
"scripture": 22,
"film": 66,
"growing_unreached": 11,
"deaths_day": 362.3,
"growth": 1.51,
"pct_no_resource": 18.6,
"pct_no_resource": 17.4,
"band": "partial"
},
{
@ -194,14 +194,14 @@
"country": "Vietnam",
"region": "Asia",
"groups": 74,
"no_resource": 27,
"pop_no_resource": 1540175,
"no_resource": 25,
"pop_no_resource": 1312675,
"scripture": 28,
"film": 38,
"growing_unreached": 11,
"deaths_day": 5.7,
"growth": 0.63,
"pct_no_resource": 36.5,
"pct_no_resource": 33.8,
"band": "high"
},
{
@ -209,14 +209,14 @@
"country": "Islamic Republic of Iran",
"region": "Asia",
"groups": 47,
"no_resource": 10,
"pop_no_resource": 445400,
"no_resource": 8,
"pop_no_resource": 198400,
"scripture": 27,
"film": 32,
"growing_unreached": 10,
"deaths_day": 55.3,
"growth": 1.05,
"pct_no_resource": 21.3,
"pct_no_resource": 17,
"band": "partial"
},
{
@ -224,14 +224,14 @@
"country": "Ethiopia",
"region": "Africa",
"groups": 119,
"no_resource": 18,
"pop_no_resource": 1789100,
"no_resource": 17,
"pop_no_resource": 1672100,
"scripture": 59,
"film": 95,
"growing_unreached": 8,
"deaths_day": 130,
"growth": 2.58,
"pct_no_resource": 15.1,
"pct_no_resource": 14.3,
"band": "partial"
},
{
@ -239,14 +239,14 @@
"country": "South Sudan",
"region": "Africa",
"groups": 80,
"no_resource": 20,
"pop_no_resource": 546600,
"no_resource": 19,
"pop_no_resource": 460100,
"scripture": 39,
"film": 52,
"growing_unreached": 8,
"deaths_day": 59,
"growth": 3.93,
"pct_no_resource": 25,
"pct_no_resource": 23.8,
"band": "partial"
},
{
@ -254,14 +254,14 @@
"country": "Mali",
"region": "Africa",
"groups": 40,
"no_resource": 10,
"pop_no_resource": 1000800,
"no_resource": 8,
"pop_no_resource": 836400,
"scripture": 20,
"film": 26,
"growing_unreached": 8,
"deaths_day": 54.8,
"growth": 2.94,
"pct_no_resource": 25,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -269,14 +269,14 @@
"country": "Malaysia",
"region": "Asia",
"groups": 160,
"no_resource": 65,
"pop_no_resource": 1918550,
"no_resource": 64,
"pop_no_resource": 1832050,
"scripture": 75,
"film": 68,
"growing_unreached": 8,
"deaths_day": 8.2,
"growth": 1.22,
"pct_no_resource": 40.6,
"pct_no_resource": 40,
"band": "high"
},
{
@ -284,14 +284,14 @@
"country": "Cameroon",
"region": "Africa",
"groups": 260,
"no_resource": 126,
"pop_no_resource": 3210925,
"no_resource": 119,
"pop_no_resource": 2700925,
"scripture": 72,
"film": 84,
"growing_unreached": 8,
"deaths_day": 3.4,
"growth": 2.61,
"pct_no_resource": 48.5,
"pct_no_resource": 45.8,
"band": "high"
},
{
@ -299,14 +299,14 @@
"country": "Thailand",
"region": "Asia",
"groups": 62,
"no_resource": 15,
"pop_no_resource": 1069800,
"no_resource": 13,
"pop_no_resource": 980800,
"scripture": 34,
"film": 37,
"growing_unreached": 0,
"deaths_day": 12.6,
"growth": -0.05,
"pct_no_resource": 24.2,
"pct_no_resource": 21,
"band": "partial"
},
{
@ -314,15 +314,15 @@
"country": "Guinea",
"region": "Africa",
"groups": 41,
"no_resource": 14,
"pop_no_resource": 484600,
"no_resource": 12,
"pop_no_resource": 311100,
"scripture": 19,
"film": 24,
"growing_unreached": 7,
"deaths_day": 14.5,
"growth": 2.4,
"pct_no_resource": 34.1,
"band": "high"
"pct_no_resource": 29.3,
"band": "partial"
},
{
"iso3": "YEM",
@ -359,14 +359,14 @@
"country": "Nepal",
"region": "Asia",
"groups": 171,
"no_resource": 46,
"pop_no_resource": 1035050,
"no_resource": 44,
"pop_no_resource": 822550,
"scripture": 97,
"film": 111,
"growing_unreached": 0,
"deaths_day": 34,
"growth": null,
"pct_no_resource": 26.9,
"pct_no_resource": 25.7,
"band": "partial"
},
{
@ -374,14 +374,14 @@
"country": "Algeria",
"region": "Africa",
"groups": 33,
"no_resource": 5,
"pop_no_resource": 625200,
"no_resource": 4,
"pop_no_resource": 622800,
"scripture": 23,
"film": 23,
"growing_unreached": 6,
"deaths_day": 21.1,
"growth": 1.4,
"pct_no_resource": 15.2,
"pct_no_resource": 12.1,
"band": "partial"
},
{
@ -389,14 +389,14 @@
"country": "Burkina Faso",
"region": "Africa",
"groups": 76,
"no_resource": 17,
"pop_no_resource": 727700,
"no_resource": 16,
"pop_no_resource": 574700,
"scripture": 41,
"film": 37,
"growing_unreached": 6,
"deaths_day": 13.4,
"growth": 2.25,
"pct_no_resource": 22.4,
"pct_no_resource": 21.1,
"band": "partial"
},
{
@ -404,14 +404,14 @@
"country": "Angola",
"region": "Africa",
"groups": 43,
"no_resource": 10,
"pop_no_resource": 1123400,
"no_resource": 8,
"pop_no_resource": 949900,
"scripture": 13,
"film": 26,
"growing_unreached": 6,
"deaths_day": 13.2,
"growth": 3.04,
"pct_no_resource": 23.3,
"pct_no_resource": 18.6,
"band": "partial"
},
{
@ -449,14 +449,14 @@
"country": "Democratic Republic of the Congo",
"region": "Africa",
"groups": 215,
"no_resource": 89,
"pop_no_resource": 7568000,
"no_resource": 77,
"pop_no_resource": 6386400,
"scripture": 50,
"film": 95,
"growing_unreached": 5,
"deaths_day": 10.9,
"growth": 3.24,
"pct_no_resource": 41.4,
"pct_no_resource": 35.8,
"band": "high"
},
{
@ -479,14 +479,14 @@
"country": "United Republic of Tanzania",
"region": "Africa",
"groups": 132,
"no_resource": 18,
"pop_no_resource": 3289400,
"no_resource": 16,
"pop_no_resource": 3029900,
"scripture": 81,
"film": 101,
"growing_unreached": 4,
"deaths_day": 126.9,
"growth": 2.87,
"pct_no_resource": 13.6,
"pct_no_resource": 12.1,
"band": "partial"
},
{
@ -494,14 +494,14 @@
"country": "Myanmar",
"region": "Asia",
"groups": 110,
"no_resource": 25,
"pop_no_resource": 1274400,
"no_resource": 22,
"pop_no_resource": 999400,
"scripture": 63,
"film": 64,
"growing_unreached": 4,
"deaths_day": 25.8,
"growth": 0.67,
"pct_no_resource": 22.7,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -509,14 +509,14 @@
"country": "Benin",
"region": "Africa",
"groups": 57,
"no_resource": 3,
"pop_no_resource": 277000,
"no_resource": 2,
"pop_no_resource": 178500,
"scripture": 29,
"film": 38,
"growing_unreached": 4,
"deaths_day": 21.9,
"growth": 2.46,
"pct_no_resource": 5.3,
"pct_no_resource": 3.5,
"band": "partial"
},
{
@ -524,14 +524,14 @@
"country": "Libya",
"region": "Africa",
"groups": 27,
"no_resource": 5,
"pop_no_resource": 317800,
"no_resource": 4,
"pop_no_resource": 315000,
"scripture": 16,
"film": 20,
"growing_unreached": 4,
"deaths_day": 0,
"growth": 1.03,
"pct_no_resource": 18.5,
"pct_no_resource": 14.8,
"band": "partial"
},
{
@ -539,14 +539,14 @@
"country": "Iraq",
"region": "Asia",
"groups": 25,
"no_resource": 3,
"pop_no_resource": 774004,
"no_resource": 2,
"pop_no_resource": 774000,
"scripture": 16,
"film": 21,
"growing_unreached": 3,
"deaths_day": 42.3,
"growth": 2.12,
"pct_no_resource": 12,
"pct_no_resource": 8,
"band": "partial"
},
{
@ -554,14 +554,14 @@
"country": "Togo",
"region": "Africa",
"groups": 46,
"no_resource": 5,
"pop_no_resource": 248000,
"no_resource": 3,
"pop_no_resource": 143500,
"scripture": 32,
"film": 32,
"growing_unreached": 3,
"deaths_day": 27.9,
"growth": 2.2,
"pct_no_resource": 10.9,
"pct_no_resource": 6.5,
"band": "partial"
},
{
@ -689,14 +689,14 @@
"country": "Israel",
"region": "Asia",
"groups": 32,
"no_resource": 5,
"pop_no_resource": 807000,
"no_resource": 4,
"pop_no_resource": 670000,
"scripture": 23,
"film": 27,
"growing_unreached": 2,
"deaths_day": 2.8,
"growth": 1.54,
"pct_no_resource": 15.6,
"pct_no_resource": 12.5,
"band": "partial"
},
{
@ -764,14 +764,14 @@
"country": "Republic of The Gambia",
"region": "Africa",
"groups": 17,
"no_resource": 3,
"pop_no_resource": 35000,
"no_resource": 2,
"pop_no_resource": 29500,
"scripture": 6,
"film": 13,
"growing_unreached": 2,
"deaths_day": 0.6,
"growth": 2.28,
"pct_no_resource": 17.6,
"pct_no_resource": 11.8,
"band": "partial"
},
{
@ -779,14 +779,14 @@
"country": "Republic of the Congo",
"region": "Africa",
"groups": 64,
"no_resource": 27,
"pop_no_resource": 907200,
"no_resource": 24,
"pop_no_resource": 644700,
"scripture": 16,
"film": 27,
"growing_unreached": 2,
"deaths_day": 1,
"growth": 2.4,
"pct_no_resource": 42.2,
"pct_no_resource": 37.5,
"band": "high"
},
{
@ -884,14 +884,14 @@
"country": "Kenya",
"region": "Africa",
"groups": 69,
"no_resource": 5,
"pop_no_resource": 300300,
"no_resource": 4,
"pop_no_resource": 268800,
"scripture": 52,
"film": 62,
"growing_unreached": 1,
"deaths_day": 8.6,
"growth": 1.96,
"pct_no_resource": 7.2,
"pct_no_resource": 5.8,
"band": "partial"
},
{
@ -959,14 +959,14 @@
"country": "Cote d'Ivoire",
"region": "Africa",
"groups": 83,
"no_resource": 19,
"pop_no_resource": 541500,
"no_resource": 18,
"pop_no_resource": 459500,
"scripture": 44,
"film": 53,
"growing_unreached": 1,
"deaths_day": 3.7,
"growth": 2.44,
"pct_no_resource": 22.9,
"pct_no_resource": 21.7,
"band": "partial"
},
{
@ -1049,14 +1049,14 @@
"country": "Papua New Guinea",
"region": "Oceania",
"groups": 831,
"no_resource": 468,
"pop_no_resource": 2179750,
"no_resource": 463,
"pop_no_resource": 1882350,
"scripture": 332,
"film": 143,
"growing_unreached": 1,
"deaths_day": 0.9,
"growth": 1.78,
"pct_no_resource": 56.3,
"pct_no_resource": 55.7,
"band": "high"
},
{
@ -1094,14 +1094,14 @@
"country": "Central African Republic",
"region": "Africa",
"groups": 69,
"no_resource": 31,
"pop_no_resource": 1025250,
"no_resource": 30,
"pop_no_resource": 948750,
"scripture": 16,
"film": 28,
"growing_unreached": 1,
"deaths_day": 0.6,
"growth": 3.4,
"pct_no_resource": 44.9,
"pct_no_resource": 43.5,
"band": "high"
},
{
@ -1109,14 +1109,14 @@
"country": "Guinea-Bissau",
"region": "Africa",
"groups": 25,
"no_resource": 6,
"pop_no_resource": 55400,
"no_resource": 5,
"pop_no_resource": 43400,
"scripture": 10,
"film": 19,
"growing_unreached": 1,
"deaths_day": 0.7,
"growth": 2.21,
"pct_no_resource": 24,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -1139,14 +1139,14 @@
"country": "Egypt",
"region": "Africa",
"groups": 23,
"no_resource": 4,
"pop_no_resource": 1688000,
"no_resource": 3,
"pop_no_resource": 1638000,
"scripture": 10,
"film": 19,
"growing_unreached": 1,
"deaths_day": 0.4,
"growth": 1.73,
"pct_no_resource": 17.4,
"pct_no_resource": 13,
"band": "partial"
},
{
@ -1259,14 +1259,14 @@
"country": "Fiji",
"region": "Oceania",
"groups": 21,
"no_resource": 9,
"pop_no_resource": 136450,
"no_resource": 8,
"pop_no_resource": 45450,
"scripture": 12,
"film": 12,
"growing_unreached": 1,
"deaths_day": 0.1,
"growth": 0.5,
"pct_no_resource": 42.9,
"pct_no_resource": 38.1,
"band": "high"
},
{
@ -1649,14 +1649,14 @@
"country": "French Guiana",
"region": "Americas",
"groups": 25,
"no_resource": 4,
"pop_no_resource": 124550,
"no_resource": 3,
"pop_no_resource": 4550,
"scripture": 18,
"film": 19,
"growing_unreached": 0,
"deaths_day": 0,
"growth": null,
"pct_no_resource": 16,
"pct_no_resource": 12,
"band": "partial"
},
{
@ -2009,14 +2009,14 @@
"country": "Namibia",
"region": "Africa",
"groups": 29,
"no_resource": 5,
"pop_no_resource": 113225,
"no_resource": 4,
"pop_no_resource": 25725,
"scripture": 4,
"film": 19,
"growing_unreached": 0,
"deaths_day": 3.3,
"growth": 2.24,
"pct_no_resource": 17.2,
"pct_no_resource": 13.8,
"band": "partial"
},
{
@ -2129,14 +2129,14 @@
"country": "Senegal",
"region": "Africa",
"groups": 28,
"no_resource": 4,
"pop_no_resource": 311800,
"no_resource": 3,
"pop_no_resource": 255800,
"scripture": 15,
"film": 23,
"growing_unreached": 0,
"deaths_day": 0.3,
"growth": 2.32,
"pct_no_resource": 14.3,
"pct_no_resource": 10.7,
"band": "partial"
},
{
@ -2144,14 +2144,14 @@
"country": "Philippines",
"region": "Asia",
"groups": 156,
"no_resource": 26,
"pop_no_resource": 3398275,
"no_resource": 25,
"pop_no_resource": 3279275,
"scripture": 108,
"film": 90,
"growing_unreached": 0,
"deaths_day": 0.2,
"growth": 0.83,
"pct_no_resource": 16.7,
"pct_no_resource": 16,
"band": "partial"
},
{
@ -2264,14 +2264,14 @@
"country": "United States of America",
"region": "Americas",
"groups": 623,
"no_resource": 38,
"pop_no_resource": 462510,
"no_resource": 35,
"pop_no_resource": 447105,
"scripture": 517,
"film": 549,
"growing_unreached": 0,
"deaths_day": 0.1,
"growth": 0.96,
"pct_no_resource": 6.1,
"pct_no_resource": 5.6,
"band": "partial"
},
{
@ -2504,14 +2504,14 @@
"country": "Liberia",
"region": "Africa",
"groups": 33,
"no_resource": 7,
"pop_no_resource": 431300,
"no_resource": 6,
"pop_no_resource": 292300,
"scripture": 19,
"film": 24,
"growing_unreached": 0,
"deaths_day": 0,
"growth": 2.16,
"pct_no_resource": 21.2,
"pct_no_resource": 18.2,
"band": "partial"
},
{
@ -2692,14 +2692,14 @@
"country": "United Kingdom",
"region": "Europe",
"groups": 74,
"no_resource": 3,
"pop_no_resource": 682800,
"no_resource": 2,
"pop_no_resource": 676800,
"scripture": 62,
"film": 71,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 4.1,
"pct_no_resource": 2.7,
"band": "partial",
"from_pins_only": true
},
@ -2836,14 +2836,14 @@
"country": "Ghana",
"region": "Africa",
"groups": 96,
"no_resource": 14,
"pop_no_resource": 637300,
"no_resource": 13,
"pop_no_resource": 514300,
"scripture": 71,
"film": 70,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 14.6,
"pct_no_resource": 13.5,
"band": "partial",
"from_pins_only": true
},
@ -2948,14 +2948,14 @@
"country": "Taiwan",
"region": "Asia",
"groups": 21,
"no_resource": 5,
"pop_no_resource": 306300,
"no_resource": 3,
"pop_no_resource": 96300,
"scripture": 16,
"film": 12,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 23.8,
"pct_no_resource": 14.3,
"band": "partial",
"from_pins_only": true
},
@ -3156,15 +3156,15 @@
"country": "Timor-Leste",
"region": "Asia",
"groups": 17,
"no_resource": 12,
"pop_no_resource": 448500,
"no_resource": 10,
"pop_no_resource": 203500,
"scripture": 2,
"film": 1,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 70.6,
"band": "critical",
"pct_no_resource": 58.8,
"band": "high",
"from_pins_only": true
},
{

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
{
"meta": {
"title": "Gospel resource link verification",
"generated": "2026-09-24T18:33:00.458Z",
"generated": "2026-09-24T19:37:21.540Z",
"method": "HEAD, redirects followed. verified = HTTP 200. broken = 404/410. unknown = unreachable, an ambiguous status, or a URL shape that cannot be checked by fetch.",
"scope": "Every URL in data/tables/people-group-pins.json, deduplicated.",
"states": {
@ -86,12 +86,12 @@
]
},
"summary": {
"records": 7565,
"checkedThisPass": 74,
"records": 7658,
"checkedThisPass": 93,
"carriedForward": 74,
"pruned": 0,
"broken": 74,
"verified": 7486,
"verified": 7579,
"unknown": 5
},
"byHost": {
@ -132,7 +132,7 @@
"verified": 921
},
"fivefish.org": {
"verified": 74
"verified": 167
}
}
},
@ -37961,6 +37961,471 @@
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/13367?language=Exirima)",
"at": "2026-09-24T18:33:00.244Z"
},
"https://fivefish.org/kfo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/11208?language=Koro)",
"at": "2026-09-24T19:37:14.390Z"
},
"https://fivefish.org/tyi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2839?language=Kiteke%3A%20Zanaga)",
"at": "2026-09-24T19:37:14.418Z"
},
"https://fivefish.org/les": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1328?language=Walese)",
"at": "2026-09-24T19:37:14.433Z"
},
"https://fivefish.org/ekp": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2375?language=Ekpeye)",
"at": "2026-09-24T19:37:14.471Z"
},
"https://fivefish.org/fay": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/19022?language=Fars%2C%20Southwestern)",
"at": "2026-09-24T19:37:14.500Z"
},
"https://fivefish.org/hed": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2974?language=Kado)",
"at": "2026-09-24T19:37:14.544Z"
},
"https://fivefish.org/bmi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2921?language=Baguirmi)",
"at": "2026-09-24T19:37:14.572Z"
},
"https://fivefish.org/muo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4771?language=Nyongnepa)",
"at": "2026-09-24T19:37:14.714Z"
},
"https://fivefish.org/zmq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1529?language=Kiwenye%20Mituku)",
"at": "2026-09-24T19:37:15.004Z"
},
"https://fivefish.org/byc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2963?language=Ubaghara)",
"at": "2026-09-24T19:37:15.011Z"
},
"https://fivefish.org/sen": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6015?language=Senoufo%2C%20Nanerige)",
"at": "2026-09-24T19:37:15.040Z"
},
"https://fivefish.org/kem": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4613?language=Kemak)",
"at": "2026-09-24T19:37:15.115Z"
},
"https://fivefish.org/luj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1367?language=Luna%20Inkongo)",
"at": "2026-09-24T19:37:15.161Z"
},
"https://fivefish.org/pwn": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1993?language=Paiwan)",
"at": "2026-09-24T19:37:15.195Z"
},
"https://fivefish.org/lse": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1574?language=Lolenge)",
"at": "2026-09-24T19:37:15.202Z"
},
"https://fivefish.org/lar": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4881?language=Larteh)",
"at": "2026-09-24T19:37:15.231Z"
},
"https://fivefish.org/cde": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3063?language=Chenchu)",
"at": "2026-09-24T19:37:15.515Z"
},
"https://fivefish.org/nre": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3232?language=Naga%2C%20Southern%20Rengma)",
"at": "2026-09-24T19:37:15.736Z"
},
"https://fivefish.org/pmm": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2854?language=Pomo)",
"at": "2026-09-24T19:37:15.788Z"
},
"https://fivefish.org/xub": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4070?language=Kurumba%2C%20Vatta)",
"at": "2026-09-24T19:37:15.818Z"
},
"https://fivefish.org/wyy": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4303?language=Fijian%2C%20Western)",
"at": "2026-09-24T19:37:16.016Z"
},
"https://fivefish.org/yiz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4806?language=Azhe)",
"at": "2026-09-24T19:37:16.037Z"
},
"https://fivefish.org/lgm": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4517?language=Lega-Mwenga)",
"at": "2026-09-24T19:37:16.052Z"
},
"https://fivefish.org/ged": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/393?language=Gade)",
"at": "2026-09-24T19:37:16.127Z"
},
"https://fivefish.org/kep": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3908?language=Kaikadi)",
"at": "2026-09-24T19:37:16.315Z"
},
"https://fivefish.org/bws": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1548?language=Bomboma)",
"at": "2026-09-24T19:37:16.347Z"
},
"https://fivefish.org/brt": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1758?language=Njwande)",
"at": "2026-09-24T19:37:16.387Z"
},
"https://fivefish.org/laa": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/296?language=Subanon%2C%20Margosatubig)",
"at": "2026-09-24T19:37:16.438Z"
},
"https://fivefish.org/bji": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1439?language=Burji)",
"at": "2026-09-24T19:37:16.554Z"
},
"https://fivefish.org/bip": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4583?language=Kibila)",
"at": "2026-09-24T19:37:16.662Z"
},
"https://fivefish.org/jjr": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1960?language=Bankal)",
"at": "2026-09-24T19:37:16.708Z"
},
"https://fivefish.org/orx": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2374?language=Oron)",
"at": "2026-09-24T19:37:16.716Z"
},
"https://fivefish.org/lel": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1518?language=Bushilele)",
"at": "2026-09-24T19:37:16.788Z"
},
"https://fivefish.org/mrh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5027?language=Chin%2C%20Mara%3A%20Myanmar)",
"at": "2026-09-24T19:37:16.971Z"
},
"https://fivefish.org/dgh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1770?language=Dghwede)",
"at": "2026-09-24T19:37:17.102Z"
},
"https://fivefish.org/ymk": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5101?language=Makwe)",
"at": "2026-09-24T19:37:17.207Z"
},
"https://fivefish.org/ala": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2139?language=Alago)",
"at": "2026-09-24T19:37:17.228Z"
},
"https://fivefish.org/nlj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1361?language=Kinyali)",
"at": "2026-09-24T19:37:17.242Z"
},
"https://fivefish.org/kra": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4165?language=Kumhali)",
"at": "2026-09-24T19:37:17.265Z"
},
"https://fivefish.org/ngn": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2627?language=Ngwaw)",
"at": "2026-09-24T19:37:17.463Z"
},
"https://fivefish.org/lum": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2774?language=Luimbi)",
"at": "2026-09-24T19:37:17.471Z"
},
"https://fivefish.org/vaj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4261?language=%21Kung)",
"at": "2026-09-24T19:37:17.547Z"
},
"https://fivefish.org/mkz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5068?language=Makasae%20Uatolari%20Kilikai)",
"at": "2026-09-24T19:37:17.737Z"
},
"https://fivefish.org/gim": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1488?language=Gimi%20Group)",
"at": "2026-09-24T19:37:17.922Z"
},
"https://fivefish.org/int": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/10906?language=Intha)",
"at": "2026-09-24T19:37:17.951Z"
},
"https://fivefish.org/sed": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/789?language=Sedang)",
"at": "2026-09-24T19:37:17.993Z"
},
"https://fivefish.org/jaf": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2558?language=Jarra)",
"at": "2026-09-24T19:37:18.068Z"
},
"https://fivefish.org/lrl": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/12770?language=Lari)",
"at": "2026-09-24T19:37:18.080Z"
},
"https://fivefish.org/tii": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1555?language=Kitiene)",
"at": "2026-09-24T19:37:18.123Z"
},
"https://fivefish.org/cov": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6333?language=Cao%20Miao%2C%20Sanjiang)",
"at": "2026-09-24T19:37:18.240Z"
},
"https://fivefish.org/sct": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/22852?language=Katang%2C%20Southern)",
"at": "2026-09-24T19:37:18.387Z"
},
"https://fivefish.org/kwc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6396?language=Likwala)",
"at": "2026-09-24T19:37:18.419Z"
},
"https://fivefish.org/atu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/15963?language=Reel)",
"at": "2026-09-24T19:37:18.537Z"
},
"https://fivefish.org/grd": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1966?language=Guruntum-Mbaaru)",
"at": "2026-09-24T19:37:18.604Z"
},
"https://fivefish.org/ngq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1075?language=Ngurime)",
"at": "2026-09-24T19:37:18.646Z"
},
"https://fivefish.org/rin": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2145?language=Nungu)",
"at": "2026-09-24T19:37:18.774Z"
},
"https://fivefish.org/tuq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3098?language=Toubbou)",
"at": "2026-09-24T19:37:18.810Z"
},
"https://fivefish.org/raa": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3227?language=Dungmali)",
"at": "2026-09-24T19:37:18.908Z"
},
"https://fivefish.org/tay": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1998?language=Tayal)",
"at": "2026-09-24T19:37:18.917Z"
},
"https://fivefish.org/dai": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4790?language=Day)",
"at": "2026-09-24T19:37:18.943Z"
},
"https://fivefish.org/gec": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1600?language=Grebo%2C%20Gboloo)",
"at": "2026-09-24T19:37:19.113Z"
},
"https://fivefish.org/lla": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1791?language=Lalla)",
"at": "2026-09-24T19:37:19.214Z"
},
"https://fivefish.org/nfu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6234?language=Mfumte)",
"at": "2026-09-24T19:37:19.221Z"
},
"https://fivefish.org/hol": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1406?language=Kiholo)",
"at": "2026-09-24T19:37:19.453Z"
},
"https://fivefish.org/wom": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4755?language=Wom%20%5BNigeria%5D)",
"at": "2026-09-24T19:37:19.466Z"
},
"https://fivefish.org/wgi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/358?language=Wahgi%20Group)",
"at": "2026-09-24T19:37:19.471Z"
},
"https://fivefish.org/jad": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4998?language=Jahanka)",
"at": "2026-09-24T19:37:19.602Z"
},
"https://fivefish.org/xnz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1027?language=Kena)",
"at": "2026-09-24T19:37:19.645Z"
},
"https://fivefish.org/hia": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4429?language=Lamang)",
"at": "2026-09-24T19:37:19.672Z"
},
"https://fivefish.org/lna": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1422?language=Langbasi)",
"at": "2026-09-24T19:37:19.765Z"
},
"https://fivefish.org/erh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6383?language=Erohwa)",
"at": "2026-09-24T19:37:19.835Z"
},
"https://fivefish.org/rir": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4644?language=Ribun)",
"at": "2026-09-24T19:37:19.971Z"
},
"https://fivefish.org/gcr": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2362?language=Guianese%20French%20Creole)",
"at": "2026-09-24T19:37:20.083Z"
},
"https://fivefish.org/bag": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/17738?language=Tuki)",
"at": "2026-09-24T19:37:20.145Z"
},
"https://fivefish.org/hml": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6246?language=Miao%3A%20Laohu)",
"at": "2026-09-24T19:37:20.154Z"
},
"https://fivefish.org/yhd": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4966?language=Arabic%2C%20Judeo-Iraqi)",
"at": "2026-09-24T19:37:20.212Z"
},
"https://fivefish.org/tou": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/792?language=Tho)",
"at": "2026-09-24T19:37:20.239Z"
},
"https://fivefish.org/dau": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2957?language=Dadjo)",
"at": "2026-09-24T19:37:20.332Z"
},
"https://fivefish.org/aoe": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3262?language=Anen%20Anga)",
"at": "2026-09-24T19:37:20.400Z"
},
"https://fivefish.org/hca": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/19048?language=Andaman%20Hindi%20Creole)",
"at": "2026-09-24T19:37:20.449Z"
},
"https://fivefish.org/mgb": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/13709?language=Mararit%3A%20Abou%20Charib)",
"at": "2026-09-24T19:37:20.603Z"
},
"https://fivefish.org/bsy": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/409?language=Bisaya%2C%20Sabah)",
"at": "2026-09-24T19:37:20.749Z"
},
"https://fivefish.org/aks": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4786?language=Akaselem)",
"at": "2026-09-24T19:37:20.766Z"
},
"https://fivefish.org/nyw": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/15183?language=Nyaw)",
"at": "2026-09-24T19:37:20.863Z"
},
"https://fivefish.org/blo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4811?language=Anii)",
"at": "2026-09-24T19:37:20.896Z"
},
"https://fivefish.org/djc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3099?language=Daju%2C%20Dar%20Daju)",
"at": "2026-09-24T19:37:20.994Z"
},
"https://fivefish.org/psh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3317?language=Pashai%3A%20Kohistani%20%26%20Shahi)",
"at": "2026-09-24T19:37:20.999Z"
},
"https://fivefish.org/kie": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3089?language=Kibet%3A%20Mouro)",
"at": "2026-09-24T19:37:21.053Z"
},
"https://fivefish.org/bid": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2984?language=Bidio)",
"at": "2026-09-24T19:37:21.082Z"
},
"https://fivefish.org/kkh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1152?language=Khun)",
"at": "2026-09-24T19:37:21.201Z"
},
"https://fivefish.org/knu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2530?language=Kono)",
"at": "2026-09-24T19:37:21.234Z"
},
"https://fivefish.org/dsq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6313?language=Dausahaq)",
"at": "2026-09-24T19:37:21.253Z"
},
"https://fivefish.org/bsh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3341?language=Kate)",
"at": "2026-09-24T19:37:21.390Z"
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -2,8 +2,8 @@
"source": "https://fivefish.org/<iso639-3> (5fish, Global Recordings Network)",
"fetched": "2026-09-24",
"_note": "GRN gospel AUDIO, typed as Gospel audio and never as Scripture — \"Words of Life\" is gospel messages and Bible stories, not a translation of the canon. The host never 404s: a nonsense path returns a geo-located page with HTTP 200, so existence is read from the page title, with a negative and a positive control run in band every time. Their robots.txt asks for Crawl-delay: 10 and this fetcher obeys it, sequentially.",
"codes": 74,
"decidedNone": 106,
"codes": 167,
"decidedNone": 183,
"byIso": {
"mnp": {
"u": "https://fivefish.org/mnp",
@ -300,6 +300,378 @@
"khc": {
"u": "https://fivefish.org/khc",
"n": "Tukangbesi North: Wanci"
},
"orx": {
"u": "https://fivefish.org/orx",
"n": "Oron"
},
"luj": {
"u": "https://fivefish.org/luj",
"n": "Luna Inkongo"
},
"dsq": {
"u": "https://fivefish.org/dsq",
"n": "Dausahaq"
},
"bsh": {
"u": "https://fivefish.org/bsh",
"n": "Kate"
},
"dai": {
"u": "https://fivefish.org/dai",
"n": "Day"
},
"mkz": {
"u": "https://fivefish.org/mkz",
"n": "Makasae Uatolari Kilikai"
},
"kep": {
"u": "https://fivefish.org/kep",
"n": "Kaikadi"
},
"ymk": {
"u": "https://fivefish.org/ymk",
"n": "Makwe"
},
"tyi": {
"u": "https://fivefish.org/tyi",
"n": "Kiteke: Zanaga"
},
"sed": {
"u": "https://fivefish.org/sed",
"n": "Sedang"
},
"sen": {
"u": "https://fivefish.org/sen",
"n": "Senoufo, Nanerige"
},
"int": {
"u": "https://fivefish.org/int",
"n": "Intha"
},
"bji": {
"u": "https://fivefish.org/bji",
"n": "Burji"
},
"les": {
"u": "https://fivefish.org/les",
"n": "Walese"
},
"bmi": {
"u": "https://fivefish.org/bmi",
"n": "Baguirmi"
},
"yhd": {
"u": "https://fivefish.org/yhd",
"n": "Arabic, Judeo-Iraqi"
},
"ged": {
"u": "https://fivefish.org/ged",
"n": "Gade"
},
"gec": {
"u": "https://fivefish.org/gec",
"n": "Grebo, Gboloo"
},
"lel": {
"u": "https://fivefish.org/lel",
"n": "Bushilele"
},
"hml": {
"u": "https://fivefish.org/hml",
"n": "Miao: Laohu"
},
"cov": {
"u": "https://fivefish.org/cov",
"n": "Cao Miao, Sanjiang"
},
"lrl": {
"u": "https://fivefish.org/lrl",
"n": "Lari"
},
"kra": {
"u": "https://fivefish.org/kra",
"n": "Kumhali"
},
"jad": {
"u": "https://fivefish.org/jad",
"n": "Jahanka"
},
"ala": {
"u": "https://fivefish.org/ala",
"n": "Alago"
},
"lar": {
"u": "https://fivefish.org/lar",
"n": "Larteh"
},
"knu": {
"u": "https://fivefish.org/knu",
"n": "Kono"
},
"gcr": {
"u": "https://fivefish.org/gcr",
"n": "Guianese French Creole"
},
"blo": {
"u": "https://fivefish.org/blo",
"n": "Anii"
},
"laa": {
"u": "https://fivefish.org/laa",
"n": "Subanen, Southern"
},
"sct": {
"u": "https://fivefish.org/sct",
"n": "Katang, Southern"
},
"pwn": {
"u": "https://fivefish.org/pwn",
"n": "Paiwan"
},
"fay": {
"u": "https://fivefish.org/fay",
"n": "Fars, Southwestern"
},
"hia": {
"u": "https://fivefish.org/hia",
"n": "Lamang"
},
"zmq": {
"u": "https://fivefish.org/zmq",
"n": "Kiwenye Mituku"
},
"xnz": {
"u": "https://fivefish.org/xnz",
"n": "Kena"
},
"nlj": {
"u": "https://fivefish.org/nlj",
"n": "Kinyali"
},
"erh": {
"u": "https://fivefish.org/erh",
"n": "Erohwa"
},
"tii": {
"u": "https://fivefish.org/tii",
"n": "Kitiene"
},
"aoe": {
"u": "https://fivefish.org/aoe",
"n": "Anen Anga"
},
"rin": {
"u": "https://fivefish.org/rin",
"n": "Nungu"
},
"ekp": {
"u": "https://fivefish.org/ekp",
"n": "Ekpeye"
},
"kie": {
"u": "https://fivefish.org/kie",
"n": "Kibet: Mouro"
},
"wgi": {
"u": "https://fivefish.org/wgi",
"n": "Wahgi Group"
},
"jjr": {
"u": "https://fivefish.org/jjr",
"n": "Bankal"
},
"tuq": {
"u": "https://fivefish.org/tuq",
"n": "Toubbou"
},
"kkh": {
"u": "https://fivefish.org/kkh",
"n": "Khun"
},
"hca": {
"u": "https://fivefish.org/hca",
"n": "Andaman Hindi Creole"
},
"hed": {
"u": "https://fivefish.org/hed",
"n": "Kado"
},
"dau": {
"u": "https://fivefish.org/dau",
"n": "Dadjo"
},
"lum": {
"u": "https://fivefish.org/lum",
"n": "Luimbi"
},
"ngn": {
"u": "https://fivefish.org/ngn",
"n": "Ngwaw"
},
"bip": {
"u": "https://fivefish.org/bip",
"n": "Kibila"
},
"nyw": {
"u": "https://fivefish.org/nyw",
"n": "Nyaw"
},
"bsy": {
"u": "https://fivefish.org/bsy",
"n": "Bisaya, Sabah"
},
"lla": {
"u": "https://fivefish.org/lla",
"n": "Lalla"
},
"ngq": {
"u": "https://fivefish.org/ngq",
"n": "Ngurime"
},
"yiz": {
"u": "https://fivefish.org/yiz",
"n": "Azhe"
},
"bid": {
"u": "https://fivefish.org/bid",
"n": "Bidio"
},
"psh": {
"u": "https://fivefish.org/psh",
"n": "Pashai: Kohistani & Shahi"
},
"mgb": {
"u": "https://fivefish.org/mgb",
"n": "Mararit: Abou Charib"
},
"lse": {
"u": "https://fivefish.org/lse",
"n": "Lolenge"
},
"kwc": {
"u": "https://fivefish.org/kwc",
"n": "Likwala"
},
"lgm": {
"u": "https://fivefish.org/lgm",
"n": "Lega-Mwenga"
},
"jaf": {
"u": "https://fivefish.org/jaf",
"n": "Jarra"
},
"tay": {
"u": "https://fivefish.org/tay",
"n": "Tayal"
},
"djc": {
"u": "https://fivefish.org/djc",
"n": "Daju, Dar Daju"
},
"pmm": {
"u": "https://fivefish.org/pmm",
"n": "Pomo"
},
"wyy": {
"u": "https://fivefish.org/wyy",
"n": "Fijian, Western"
},
"muo": {
"u": "https://fivefish.org/muo",
"n": "Nyongnepa"
},
"bag": {
"u": "https://fivefish.org/bag",
"n": "Tuki"
},
"vaj": {
"u": "https://fivefish.org/vaj",
"n": "!Kung"
},
"atu": {
"u": "https://fivefish.org/atu",
"n": "Reel"
},
"bws": {
"u": "https://fivefish.org/bws",
"n": "Bomboma"
},
"dgh": {
"u": "https://fivefish.org/dgh",
"n": "Dghwede"
},
"rir": {
"u": "https://fivefish.org/rir",
"n": "Ribun"
},
"aks": {
"u": "https://fivefish.org/aks",
"n": "Akaselem"
},
"kem": {
"u": "https://fivefish.org/kem",
"n": "Kemak"
},
"raa": {
"u": "https://fivefish.org/raa",
"n": "Dungmali"
},
"kfo": {
"u": "https://fivefish.org/kfo",
"n": "Koro"
},
"lna": {
"u": "https://fivefish.org/lna",
"n": "Langbasi"
},
"xub": {
"u": "https://fivefish.org/xub",
"n": "Kurumba, Vatta"
},
"hol": {
"u": "https://fivefish.org/hol",
"n": "Kiholo"
},
"gim": {
"u": "https://fivefish.org/gim",
"n": "Gimi Group"
},
"nre": {
"u": "https://fivefish.org/nre",
"n": "Naga, Southern Rengma"
},
"nfu": {
"u": "https://fivefish.org/nfu",
"n": "Mfumte"
},
"mrh": {
"u": "https://fivefish.org/mrh",
"n": "Chin, Mara: Myanmar"
},
"wom": {
"u": "https://fivefish.org/wom",
"n": "Wom [Nigeria]"
},
"grd": {
"u": "https://fivefish.org/grd",
"n": "Guruntum-Mbaaru"
},
"tou": {
"u": "https://fivefish.org/tou",
"n": "Tho"
},
"byc": {
"u": "https://fivefish.org/byc",
"n": "Ubaghara"
},
"brt": {
"u": "https://fivefish.org/brt",
"n": "Njwande"
},
"cde": {
"u": "https://fivefish.org/cde",
"n": "Chenchu"
}
},
"none": [
@ -408,6 +780,83 @@
"dux",
"ajz",
"mdi",
"psi"
"psi",
"bxu",
"nrb",
"cda",
"sqs",
"daj",
"csx",
"zhd",
"gcl",
"ecs",
"qcm",
"lgz",
"mzy",
"yel",
"nwe",
"cng",
"gbs",
"aig",
"adf",
"nzu",
"dpp",
"muq",
"tbi",
"jle",
"buc",
"qfa",
"tse",
"thi",
"bvl",
"sfb",
"okb",
"ysl",
"lwa",
"doq",
"rou",
"mub",
"cnu",
"mxf",
"csf",
"beb",
"mmz",
"cse",
"psr",
"qht",
"asf",
"tco",
"pnt",
"hsh",
"qae",
"shv",
"sqm",
"bdr",
"bct",
"lbo",
"mei",
"bfl",
"sgg",
"mhd",
"ero",
"sgx",
"asq",
"gwx",
"mgy",
"mes",
"kyp",
"bqk",
"kkw",
"tct",
"sqt",
"pmb",
"brm",
"nru",
"bmg",
"esn",
"nzy",
"ryn",
"cml",
"tda"
]
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,015 KiB

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -90,6 +90,7 @@ if (POISON) {
codes.zzj = { l: 'Both at once', z: 'https://zume.training/en', za: { u: 'https://zume.training/pus', l: 'Pashto', n: 'x' } } // J
codes.zzk = { l: 'Ships a known-dead link', f: 'https://jesusfilm.org/watch/jesus/4101' } // K
poisonStale = true // L
codes.zzm = { l: 'Claims a pool nobody swept for it', ck: 'ebible, grn', s: 'https://ebible.org/zzm/' } // M
}
const SPECIAL = (c) => c === 'und' || c === 'mul' || c === 'mis' || c === 'zxx' || (c >= 'qaa' && c <= 'qtz')
@ -266,13 +267,37 @@ if (verif && verif.links) {
}
}
/* ── M: a named pool was really searched FOR THAT CODE ───────────────────────────────────
The `checked` line on a group's sheet is what tells a reader whether "nothing found"
means anything. GRN is swept a batch at a time at the crawl delay their robots.txt asks
for, so most codes have not been reached yet — and the build briefly claimed grn for 110
codes it had never looked at, because the pass that normalises provenance ran before the
fetchers that create those codes. A pool named here must be one this code was actually
searched in. */
{
let grnSeen = null
try {
const j = read('data/tables/grn-index.json')
grnSeen = new Set([...Object.keys(j.byIso || {}), ...(j.none || [])])
} catch { /* no snapshot yet: the claim cannot be made either, checked below */ }
const liars = []
for (const [c, v] of Object.entries(codes)) {
if (!v || v.sg === 1) continue
if (!/\bgrn\b/.test(String(v.ck || ''))) continue
if (!grnSeen || !grnSeen.has(c)) liars.push(c)
}
if (liars.length) {
add('M', `${liars.length} code(s) name grn as checked though the sweep never decided them (e.g. ${liars.slice(0, 3).join(', ')}) — the provenance pass must run AFTER every fetcher attaches`)
}
}
const clauses = new Set(fails.map((f) => f.slice(3, 4)))
if (POISON) {
// D is included deliberately: it is the clause that would have caught the 506
// resources dropped from a table titled "every link", and the most valuable clause is
// the worst one to leave unproven. The injected codes above (und, zzq) are absent from
// the table, so they poison D as well as A and B.
const want = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L']
const want = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M']
const caught = want.filter((c) => clauses.has(c))
console.log(`COVERAGE-CONSISTENT --poison: ${caught.length}/${want.length} clause(s) caught their injected defect (${caught.join(',')})`)
if (caught.length !== want.length) {
@ -298,3 +323,4 @@ console.log(' G no plain-http URL H no story set typed as S
console.log(' I row type agrees with its host J approximate never reads as confirmed')
console.log(' K every shipped link is verified, and none known-broken ships')
console.log(' L the verification record is current, not merely consistent')
console.log(' M a pool named as checked was really searched for that code')

View file

@ -25,7 +25,12 @@ import { chromium } from 'playwright'
const DEFAULT_LEAF = (process.env.BASE_URL || 'http://127.0.0.1:4173')
+ '/doxa-maps/my-upg-100-list/upg-100-mfe/index.html'
const LEAF = process.argv[2] || DEFAULT_LEAF
const WIDTH = Number(process.env.CARD_FIT_WIDTH || 1440)
// ⚠ SWEEP WIDTHS. The report named 1440px, so this first guarded 1440px — but the defect is a
// FLEX-SHRINK failure, and those are width-dependent: a narrower viewport gives the controls less
// room, which is where the next regression appears. Guarding one width encodes the reporter's
// sampling choice, not the property.
const WIDTHS = (process.env.CARD_FIT_WIDTH || '390,768,1024,1440,1920')
.split(',').map((n) => Number(n.trim())).filter(Boolean)
let reachable = false
try { const r = await fetch(LEAF, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ }
@ -38,17 +43,18 @@ if (!reachable) {
const WALK = `(root, out) => { out = out || []; const rec = (r) => { for (const e of r.querySelectorAll('*')) { out.push(e); if (e.shadowRoot) rec(e.shadowRoot) } }; rec(root); return out }`
const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader'] })
let r
try {
const page = await browser.newPage({ viewport: { width: WIDTH, height: 1000 } })
async function measure(width) {
const mobile = width < 500
const page = await browser.newPage({ viewport: { width, height: 1000 }, isMobile: mobile, hasTouch: mobile })
await page.goto(LEAF, { waitUntil: 'load', timeout: 45000 })
await page.waitForTimeout(10000)
r = await page.evaluate(`(() => {
const out = await page.evaluate(`(() => {
const all = (${WALK})(document)
const vis = (e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 }
const cards = all.filter((e) => (e.className||'').toString().split(' ').includes('card')).filter(vis)
const boxes = cards.map((c) => ({ c, b: c.getBoundingClientRect() }))
let intrusions = 0, overflowing = 0, worst = 0
let intrusions = 0, overflowing = 0, worst = 0, coveringPreview = 0, worstY = 0
for (const { c, b } of boxes) {
const row = c.querySelector('.pfs')
if (!row) continue
@ -57,6 +63,19 @@ try {
overflowing++
worst = Math.max(worst, Math.round(Math.max(rb.right - b.right, b.left - rb.left)))
}
// ⚠ THE VERTICAL AXIS TOO — this measured only left/right, which is exactly how it passed a
// driver-visible regression. Fixing the horizontal overflow with flex-wrap grew the row
// 35px -> 78px, and because it sits at bottom:-14px the extra height pushed its TOP from
// 216px up to 173px in a 238px card: over the preview image and the card's outline.
// Horizontal clean, card visibly broken, gate green.
const img = c.querySelector('img')
if (img) {
const ib = img.getBoundingClientRect()
if (ib.height > 2 && rb.top < ib.bottom - 1) {
coveringPreview++
worstY = Math.max(worstY, Math.round(ib.bottom - rb.top))
}
}
for (const o of boxes) {
if (o.c === c) continue
const ox = Math.min(rb.right, o.b.right) - Math.max(rb.left, o.b.left)
@ -67,34 +86,50 @@ try {
const ctrls = all.filter((e) => /\\b(pbtn|pstock)\\b/.test((e.className||'').toString())).filter(vis)
const tiny = ctrls.filter((e) => { const b = e.getBoundingClientRect(); return b.width < 18 || b.height < 10 })
return { cards: cards.length, rows: cards.filter((c) => c.querySelector('.pfs')).length,
overflowing, worst, intrusions, controls: ctrls.length, tiny: tiny.length }
overflowing, worst, intrusions, coveringPreview, worstY,
controls: ctrls.length, tiny: tiny.length }
})()`)
await page.close()
return { width, ...out }
}
const results = []
try {
for (const w of WIDTHS) results.push(await measure(w))
} finally {
await browser.close()
}
console.log('POSTER-CARD-FITS')
console.log(` leaf : ${LEAF} @ ${WIDTH}px`)
console.log(` cards : ${r.cards} (${r.rows} with a print-options row)`)
console.log(` overflow : ${r.overflowing} card(s)${r.worst ? `, worst ${r.worst}px past the edge` : ''}`)
console.log(` neighbour : ${r.intrusions} intrusion(s) into another card's box`)
console.log(` controls : ${r.controls} print control(s), ${r.tiny} too small to use`)
console.log(` leaf : ${LEAF}`)
for (const r of results) {
console.log(` ${String(r.width).padStart(4)}px cards ${String(r.cards).padStart(2)}`
+ ` · overflow ${r.overflowing}${r.worst ? ` (${r.worst}px)` : ''}`
+ ` · neighbour ${r.intrusions}`
+ ` · over-preview ${r.coveringPreview}${r.worstY ? ` (${r.worstY}px)` : ''}`
+ ` · ${r.controls} control(s), ${r.tiny} too small`)
}
const fails = []
// PRECONDITION: no cards means nothing was tested — do not read that as a pass.
if (!r.cards) fails.push('no poster cards rendered at all — wrong url, or the wall did not mount')
else if (!r.rows) fails.push(`${r.cards} card(s) but none carry a .pfs print-options row`)
if (r.intrusions) {
fails.push(`${r.intrusions} print-options row(s) overlap a NEIGHBOURING card — the reported symptom`)
}
if (r.overflowing) {
fails.push(`${r.overflowing} print-options row(s) extend past their own card (worst ${r.worst}px)`)
}
// ⚠ AND THE CONTROLS MUST STILL BE USABLE. "Nothing overflows" is trivially satisfied by a row
// whose buttons collapsed to nothing, which is a worse product than the overflow it replaced.
if (r.tiny) {
fails.push(`${r.tiny} print control(s) shrank below a usable size — fitting the row by crushing `
+ 'the buttons is not a fix')
for (const r of results) {
const at = `${r.width}px`
// PRECONDITION: no cards means nothing was tested at this width — not a pass.
if (!r.cards) { fails.push(`${at}: no poster cards rendered — wrong url, or the wall did not mount`); continue }
if (!r.rows) { fails.push(`${at}: ${r.cards} card(s) but none carry a .pfs print-options row`); continue }
if (r.intrusions) fails.push(`${at}: ${r.intrusions} print row(s) overlap a NEIGHBOURING card`)
if (r.overflowing) fails.push(`${at}: ${r.overflowing} print row(s) extend past their own card (worst ${r.worst}px)`)
// ⚠ "Nothing overflows" is trivially satisfied by a row whose buttons collapsed to nothing.
if (r.tiny) {
fails.push(`${at}: ${r.tiny} print control(s) shrank below a usable size — fitting the row by `
+ 'crushing the buttons is not a fix')
}
// The row belongs BELOW the preview. Driver-reported 2026-09-24: "the download print options are
// not showing up under the blue outline, they are overlapping where the blue outline would be".
if (r.coveringPreview) {
fails.push(`${at}: ${r.coveringPreview} print row(s) COVER the preview image `
+ `(top sits ${r.worstY}px above the image's bottom edge) — growing the row taller to fit it `
+ 'horizontally is not a fix')
}
}
if (fails.length) {
@ -102,5 +137,6 @@ if (fails.length) {
for (const f of fails) console.error(' - ' + f)
process.exit(1)
}
console.log('\nPOSTER-CARD-FITS: PASS — every print-options row stays inside its own card, and every '
+ 'control is still big enough to press.')
console.log(`\nPOSTER-CARD-FITS: PASS — at ${results.length} width(s) (${WIDTHS.join(', ')}px) every `
+ 'print-options row sits inside its own card and below its preview, with every control still '
+ 'big enough to press.')

View file

@ -153,7 +153,7 @@ async function shot(name, path, {
// frame and loads no tiles of its own).
if (mapboxReferer) await ctx.setExtraHTTPHeaders({ Referer: 'https://doxa.life/' })
const page = await ctx.newPage()
const url = BASE + path + (tokenFree ? '' : (path.includes('?') ? '&' : '?') + 'tk=' + encodeURIComponent(TK))
const url = (/^https?:/.test(path) ? path : BASE + path) + (tokenFree ? '' : (path.includes('?') ? '&' : '?') + 'tk=' + encodeURIComponent(TK))
try {
await page.goto(url, { waitUntil: 'load', timeout: 45000 })
if (waitFor) await page.waitForSelector(waitFor, { timeout: 20000 })
@ -330,39 +330,24 @@ virtualPages.set('/__readme-iframe-host.html', `<!doctype html>
await shot('deploy1-iframe', '/__readme-iframe-host.html',
{ vp: { width: 1000, height: 700 }, wait: 11000, countFrames: true, mapboxReferer: false })
// ---- DEPLOY2: mobile — the SPA at phone viewport ----
// Same correction as cap5: shoot the cap-5 ELEMENT at phone viewport, not the gallery page.
// mapboxReferer:false for the same reason as the other two cap-5 shots — this IS the cap-5 shell,
// just at a phone viewport, and it nests every tab in an iframe.
await shot('deploy2-mobile-spa', '/doxa-maps/doxa-maps-spa/doxa-maps-spa/index.html',
{ vp: { width: 390, height: 844 }, wait: 10000, dsf: 3, mapboxReferer: false })
// ---- DEPLOY2: mobile — the Simple Research dashboard as the phone app ----
// The mobile app ships ONE dashboard, not the whole gallery rail: research-map-plus-grid (the cap-4
// "Simple Research" composite — research map stacked over the people-groups grid), shot at a phone
// viewport. It mounts no iframe, so it keeps the Referer the map tiles need.
await shot('deploy2-mobile-simple-research', '/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html',
{ vp: { width: 390, height: 844 }, wait: 12000, dsf: 3 })
// ---- DEPLOY3: research dashboard ----
// ★ THE EARLIER NOTE HERE WAS WRONG, AND THE WAY IT WAS WRONG IS WORTH KEEPING (corrected
// 2026-09-21). It read: "research-dashboard OWNS NO MAP — it is an SVG-charts surface (measured:
// 1083 elements, 10 <svg>, ZERO <canvas>)". The MEASUREMENT was real; the INFERENCE inverted it.
// research-dashboard absolutely owns a Mapbox map — src/components/dashboards/DashboardMapPanel.vue
// imports useMapInstance, injects mapboxToken, and adds a vector source. Re-measured live, the page
// DOES build a `mapboxgl-canvas` at 878x445.
//
// ★ "ZERO CANVAS" MEANT THE MAP FAILED TO INITIALISE, NOT THAT NO MAP EXISTS. Reading absence as a
// property of the surface turned a symptom into an architectural "fact", and that fact then
// justified relaxing the readiness check — so the shot stopped waiting for the very thing whose
// absence was the bug, and the README's deploy-3 proof image has shown a BLANK GREY map pane since.
// A weakened assertion that makes a failing check pass is how a defect gets promoted to a design note.
//
// WHY THE PANE IS STILL GREY (a real defect, tracked separately — not this file's to fix):
// the panel sources `mapbox://mapbox.country-boundaries-v1`, and the shared pk. token is URL-
// restricted to doxa.life. Measured: Referer doxa.life -> 200 (3/3), 127.0.0.1 -> 403, none -> 403.
// The STYLE endpoint returns 200 from every origin, which is why other maps paint normally and only
// the tileset-backed choropleth blanks. So the shot machine cannot paint this pane at all today.
//
// `waitFor: CANVAS` is therefore still wrong HERE — not because there is no map, but because the
// canvas genuinely cannot be produced from this origin, and a gate that can never pass gets
// weakened again by the next reader. minElements proves the surface rendered; check:shot-fidelity
// is where "this machine cannot render what the shot must show" belongs.
await shot('deploy3-research-dashboard', '/doxa-maps/research-dashboard/research-dashboard/index.html',
{ waitFor: 'svg', wait: 10000, minElements: 200 })
// ---- DEPLOY3: JS embed — the Research Gallery on a real host site ----
// The whole cap-5 gallery JS-embedded on dx-life's research page (a <script src> plus one element,
// the snippet manifest.json ships). The host is a SEPARATE app, so this shot runs only when its dev
// server is up: DXLIFE_URL=http://127.0.0.1:<port> bun run shots. Without it the shot is skipped
// and the committed image stays as it is.
if (process.env.DXLIFE_URL) {
await shot('deploy3-js-embed-dxlife', process.env.DXLIFE_URL.replace(/\/$/, '') + '/research',
{ vp: { width: 1440, height: 1100 }, wait: 16000, tokenFree: true, minElements: 300 })
} else if (!ONLY || ONLY.includes('deploy3-js-embed-dxlife')) {
console.log(' SKIP deploy3-js-embed-dxlife (set DXLIFE_URL to a running dx-life dev server)')
}
await browser.close()
server.close()

View file

@ -433,20 +433,35 @@ useShadowStyles(
'.hlabel{font-weight:600}' +
'.card.is-last{box-shadow:0 0 0 2px #10b981,0 6px 18px rgba(0,0,0,.10)}' +
'.card.is-last .tape-h{background:#10b981;color:#04241a;border-color:#10b981}' +
// ⚠ flex-wrap + min-width:0 ON THE CHILDREN, not just width on the box. width:82% bounds
// the fieldset, but flex items default to min-width:auto and REFUSE to shrink below their
// intrinsic content width — so with four controls (Prayer List · Card Deck · stock select ·
// Poster Map) the row burst its card and overlapped the neighbours. MEASURED 2026-09-24 at
// 1440px: all 27 cards overflowing, worst 110px past a 212px card. A source read of this
// rule says 'it has a width, looks fine', which is why a screenshot caught it and nothing
// else did.
// ⚠ min-width:0 ON THE CHILDREN — and NOT flex-wrap. width:82% bounds the fieldset, but flex
// items default to min-width:auto and REFUSE to shrink below their intrinsic content width, so
// four controls (Prayer List · Card Deck · stock select · Poster Map) burst the box regardless.
// MEASURED 2026-09-24 at 1440px: all 27 cards overflowing, worst 110px past a 212px card.
//
// ★ flex-wrap:wrap FIXED THAT AND BROKE THE OTHER AXIS — driver-reported, same day. The row is
// absolutely positioned at bottom:-14px, so wrapping it to three lines grew it 35px -> 78px and
// pushed its TOP up from 216px to 173px into a 238px card: straight over the preview image and
// the card's outline. Horizontal overflow traded for vertical. min-width:0 alone lets the
// controls compress on ONE line, which is what the row was always meant to be.
// The gate that passed this only measured horizontal edges; it now measures the row's top too.
'.pfs{position:absolute;bottom:-14px;left:50%;transform:translateX(-50%);' +
'width:82%;max-width:82%;box-sizing:border-box;margin:0;padding:0 4px 3px;' +
// ⚠ min-width:0 ON THE FIELDSET ITSELF. A <fieldset> gets `min-width:min-content` from the
// UA stylesheet, which OVERRIDES width and max-width — measured: computed width 431px
// inside a 212px card while the rule said 82%. That is the real reason `width:82%` read as
// correct in source for so long and the row still burst the card.
'width:82%;max-width:82%;min-width:0;box-sizing:border-box;margin:0;padding:0 4px 3px;' +
'border:1px solid rgba(0,0,0,.14);' +
'border-radius:2px;background:#fff;display:flex;flex-wrap:wrap;gap:2px;justify-content:center;' +
'border-radius:2px;background:#fff;display:flex;flex-wrap:nowrap;gap:2px;justify-content:center;' +
'box-shadow:0 1px 4px rgba(0,0,0,.10)}' +
// Let every control shrink inside the row rather than push it open.
'.pfs > .pbtn,.pfs > .pstock{min-width:0;max-width:100%}' +
// Let every control shrink inside the row rather than push it open. overflow:hidden +
// text-overflow keeps a squeezed label readable instead of spilling.
'.pfs > .pbtn,.pfs > .pstock{min-width:0;max-width:100%;overflow:hidden;' +
'text-overflow:ellipsis;white-space:nowrap}' +
// ⚠ THE <select> IS THE ONE THAT WILL NOT SHRINK. Measured: with min-width:0 applied to
// every child, .pstock still rendered 188px inside a 198px row because a <select> sizes to
// its WIDEST OPTION and `flex:0 1 auto` keeps that as its basis. flex-basis:0 makes it share
// the row like the buttons do; a cap keeps it from dominating the three text controls.
'.pfs > .pstock{flex:1 1 0;max-width:38%}' +
'.is-dark .pfs{background:#132019;border-color:rgba(110,231,183,.24)}' +
'.pfs > legend{margin:0 auto;padding:0 5px;font-size:8px;text-transform:uppercase;' +
'letter-spacing:.6px;font-weight:700;color:#8a93a0}' +

View file

@ -68,7 +68,7 @@
"check:poster-prints": "node internal/check-poster-prints.mjs",
"check:provider-switch": "node internal/check-provider-switch.mjs # the provider switch must actually SWITCH: counts requests BY HOST (not window globals, which stay defined after a swap) and asserts the maplibre run stays within the leaf HTML's own declared api.mapbox.com preloads. Defaults to the upg-100 leaf; pass a URL or set BASE_URL for another. CONDITIONAL: SKIPs when nothing is served.",
"check:legend-export-reachable": "node internal/check-legend-export-reachable.mjs # the Export affordance must be reachable at BOTH widths. Selects a leaf row first (the control is gated on selectedNode) and opens the mobile sheet first (it starts closed). Asserts a REAL BOX, not CSS visibility — on mobile the elements report visibility:visible at 0x0. PASSES at both widths since the mobile sheet was given :export-enabled and :tabs. SKIPs when nothing is served.",
"check:poster-card-fits": "node internal/check-poster-card-fits.mjs # 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\". CARD_FIT_WIDTH to test another width. SKIPs when nothing is served.",
"check:poster-card-fits": "node internal/check-poster-card-fits.mjs # 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:mfe-two-tab": "node internal/check-mfe-two-tab.mjs",
"check:share-policy": "node internal/check-share-policy.mjs",
"check:zone-census": "node internal/check-zone-census.mjs # every zone a USER CAN SELECT must have a poster (asks the LEGEND, not clusters.json — two collapsed deaf lists are unreachable and not a gap). CONDITIONAL: SKIPs without out/posters/, which is gitignored — run `bun run posters` first. It cannot be fixtured like check:packets-all, because it asserts COVERAGE of real generated output and a fixture would only assert the fixture is complete.",

View file

@ -0,0 +1,67 @@
/**
* gospel-resource-rows.js — what a language's codes[] entry offers, in ONE place.
*
* PRIVATE TO THIS PLUGIN, deliberately. Nothing outside gospel-resources has a codes[]
* entry, so this is not a shared component and does not belong in library/ — putting it
* there would publish a signature only one bundle can call and invite a second consumer to
* bend it.
*
* WHY IT EXISTS. Each surface used to list the resource fields for itself. The map grew
* chips per type, the charts computed "has nothing" by hand, and the list built its links
* from `c.s` and `c.d` only. So every new source had to be added in four places, and it
* never was: when Scripture Earth and GRN landed, the list profile showed neither — a
* language whose ONLY resource was a Words of Life recording rendered with no links at all,
* while the map offered a working one for the same language. The charts had the same drift
* and it took a hand count to find. The fix that lasts is not remembering harder.
*
* This mirrors scripts/build-resource-links-table.mjs, which types the shipped table. The
* two must agree, because one is what the table says and the other is what the page shows,
* and a reader comparing them is entitled to see the same answer. Types are kept honest in
* the same way the table keeps them:
* - Open Bible Stories is Bible Stories, never Scripture — 50 illustrated narratives are
* not a translation.
* - Words of Life is Gospel audio, never Scripture — gospel messages with songs.
* - Zume is Discipleship training, a course.
* - A bible.is fileset is typed by what it actually carries, not by assumption.
*/
/** Scripture Earth directory names, as a reader would say them. */
const SE_KIND = { PDF: 'PDF', audio: 'audio', sab: 'app', study: 'study notes', viewer: 'read online' }
/**
* Every resource a language offers, flattened for a list or a table.
* @param {object} c one entry of the pin index's codes[]
* @returns {{url:string,title:string,type:string,scope:string}[]}
*/
export function resourceRows(c) {
if (!c) return []
const out = []
const add = (title, type, url, scope = '') => {
if (url) out.push({ url, title, type, scope })
}
add(c.sv || 'Scripture in this language', 'Scripture', c.s, c.ss || '')
add('JESUS Film', 'Film', c.f)
for (const d of c.d || []) {
add(d.s, /^Open Bible Stories/.test(d.s) ? 'Bible Stories' : 'Scripture', d.u, d.sc || '')
}
if (c.au) {
const media = String(c.au.m || 'audio')
add(
c.au.n || 'Scripture',
/audio/.test(media) ? 'Audio Scripture' : 'Scripture',
c.au.u,
media === 'audio' ? 'Listen in a browser' : `In a browser — ${media}`,
)
}
if (c.bv) add(c.bv.n || 'Bible video', 'Bible video', c.bv.u, c.bv.sc || '')
if (c.gp) add(c.gp.n || 'Gospel presentation', 'Gospel presentation', c.gp.u, 'Read in this language')
if (c.se) {
add('Scripture Earth', 'Scripture', c.se.u, (c.se.k || []).map((k) => SE_KIND[k] || k).join(', '))
}
if (c.ga) {
add(c.ga.n ? `Words of Life — ${c.ga.n}` : 'Gospel audio', 'Gospel audio', c.ga.u, 'Listen in this language')
}
add('Zume discipleship training', 'Discipleship training', c.z)
return out
}

View file

@ -1,4 +1,5 @@
<script setup>
import { resourceRows } from './gospel-resource-rows.js'
/**
* resources-list.vue — GOSPEL RESOURCES, by language.
*
@ -88,9 +89,11 @@ const languages = computed(() => {
const scope = c.ss || ''
// resources[] is what the UI turns into links; Door43 entries keep their own
// subject so Open Bible Stories is never presented as a translation.
const res = []
if (c.s) res.push({ url: c.s, title: c.sv || 'Scripture in this language' })
for (const dd of c.d || []) res.push({ url: dd.u, title: dd.s })
// Every resource this language has, from the one helper the other surfaces use. This
// listed only c.s and c.d, so film, audio, Bible video, tracts, Scripture Earth and
// GRN never appeared here — a language whose only resource was a Words of Life
// recording rendered with no links while the map offered a working one.
const res = resourceRows(c)
e = {
iso: code,
language: c.l || 'unknown',
@ -112,6 +115,7 @@ const languages = computed(() => {
audio: c.au || null,
bibleVideo: c.bv || null,
resources: res,
scripture_rows: res.filter((r) => r.type === 'Scripture').length,
}
by.set(code, e)
}
@ -141,7 +145,11 @@ const stats = computed(() => {
people_groups_without_scripture: without.reduce((n, x) => n + x.people_group_count, 0),
population_without_scripture: without.reduce((n, x) => n + x.population, 0),
ebible_languages: l.filter((x) => x.has_scripture).length,
ebible_translations: l.reduce((n, x) => n + x.resources.length, 0),
// Counts TEXT Scripture rows only. `resources` now carries film, audio, Bible video,
// tracts, Scripture Earth and GRN as well, and this line sits under a sentence that
// names eBible.org — widening the number without changing the label would have made
// the page overstate how many translations exist.
ebible_translations: l.reduce((n, x) => n + x.scripture_rows, 0),
}
})
@ -199,17 +207,28 @@ const fmt = (n) => (Number(n) || 0).toLocaleString()
// film, and this list exists to show the gap honestly. The row now says both things at once: the
// pill reports the scripture gap, the links report what does exist.
const linksFor = (l) => {
const out = (l.resources || []).slice(0, 2).map((r) => ({ url: r.url, title: r.title, kind: 'scripture' }))
// Guard on the URL itself, not on has_film/jesus_film: a truthy flag with no URL would render an
// anchor to nowhere, which is worse than showing nothing.
if (l.film_url) out.push({ url: l.film_url, title: 'JESUS Film', kind: 'film' })
// Audio Scripture, on the same guard-the-URL rule as film above. A language with no text
// Scripture but a recording is one of the most useful rows on this page, and it used to
// render with nothing at all beside its gap pill.
if (l.audio && l.audio.u) out.push({ url: l.audio.u, title: l.audio.n || 'Audio Scripture', kind: 'audio' })
// Bible video is its own kind, never folded into film. See the map's chip for why.
if (l.bibleVideo && l.bibleVideo.u) out.push({ url: l.bibleVideo.u, title: l.bibleVideo.sc || l.bibleVideo.n || 'Bible video', kind: 'video' })
return out
// The kind is taken from the row's own TYPE, never assumed. This used to tag the first two
// rows "scripture" whatever they were, which was harmless while the list only held text
// Scripture and became a doctrinal error the moment it held gospel audio: Words of Life is
// gospel messages with songs, and styling it as Scripture tells a reader they have a
// translation when they do not.
const KIND = {
Scripture: 'scripture',
'Bible Stories': 'scripture',
Film: 'film',
'Audio Scripture': 'audio',
'Gospel audio': 'audio',
'Bible video': 'video',
'Gospel presentation': 'film',
'Discipleship training': 'film',
}
// Film, audio and Bible video used to be appended here by hand as well as arriving in
// `resources`; the helper returns them once, so appending again would double them.
return (l.resources || []).slice(0, 4).map((r) => ({
url: r.url,
title: r.title,
kind: KIND[r.type] || 'scripture',
}))
}
useShadowStyles(

View file

@ -159,7 +159,14 @@ try { scriptureEarth = JSON.parse(readFileSync(SCRIPTUREEARTH, 'utf8')).byIso ||
// Global Recordings Network via 5fish (scripts/fetch-grn-index.mjs). Optional like the rest.
let grn = {}
try { grn = JSON.parse(readFileSync(GRN, 'utf8')).byIso || {} } catch { grn = {} }
let grnSeen = new Set()
try {
const j = JSON.parse(readFileSync(GRN, 'utf8'))
grn = j.byIso || {}
// Every code the sweep has DECIDED — found or not found. A code it has not reached yet is
// neither, and must not be described as searched.
grnSeen = new Set([...Object.keys(grn), ...(j.none || [])])
} catch { grn = {}; grnSeen = new Set() }
let zume = {}
let zumeApprox = {}
@ -340,17 +347,6 @@ const NOT_SCANNED = 'api_bible, dbl, progress_bible'
// have nothing, and the honest statement is which pool could still have it.
const SIGN_SCANNED = 'arclight (14 sign languages, all captured), fcbh (none)'
const SIGN_NOT_SCANNED = 'deaf_bible_society, door_international — both key-gated'
for (const v of Object.values(codes)) {
if (v.sg === 1) {
v.ck = SIGN_SCANNED
v.un = SIGN_NOT_SCANNED
continue
}
const had = String(v.ck || '').split(',').map((x) => x.trim()).filter(Boolean)
const all = new Set([...had, 'ebible', 'arclight', 'door43', 'fcbh'])
v.ck = [...all].join(', ')
v.un = NOT_SCANNED
}
// Attach Zume where the language matches. A code with no other resource still gets an
// entry, because "training exists in your language" is a real answer to "what is there".
@ -459,6 +455,38 @@ for (const [c, a] of Object.entries(zumeApprox)) {
zumeApproxCodes++
}
// PROVENANCE, AFTER EVERY SOURCE HAS ATTACHED. This loop used to run before the Scripture
// Earth and GRN attaches, so the 110 codes those fetchers CREATE kept the raw constant and
// claimed grn had been checked for them when it had not. It is the same ordering lesson as
// the dead-link drop below: a normalising pass that runs before the thing it normalises
// reports a tidy answer about a room that is still filling up.
// entries(), not values(): the GRN sweep is decided PER CODE, so this loop needs the code.
for (const [c, v] of Object.entries(codes)) {
if (v.sg === 1) {
v.ck = SIGN_SCANNED
v.un = SIGN_NOT_SCANNED
continue
}
// WHAT WAS ACTUALLY CHECKED FOR THIS CODE, not what the pipeline can check in general.
// This line is read by a person deciding whether "nothing found" means anything, so a
// pool named here must really have been searched FOR THEM.
//
// scriptureearth is unconditional: that sweep covered every code this map uses, and a
// code absent from their index is a checked negative, not an unchecked one.
//
// grn is NOT. It is swept a batch at a time at the ten-second crawl delay their robots.txt
// asks for, so most codes have not been looked at yet. Claiming it for all of them would
// have told 2,100 groups their gap was searched in the largest gospel-audio pool there is
// when it was not — the exact stale-caveat error this file has had to correct twice before.
const had = String(v.ck || '').split(',').map((x) => x.trim()).filter(Boolean)
const all = new Set([...had, 'ebible', 'arclight', 'door43', 'fcbh', 'scriptureearth'])
const grnDecided = grnSeen.has(c)
if (grnDecided) all.add('grn')
else all.delete('grn')
v.ck = [...all].sort().join(', ')
v.un = grnDecided ? NOT_SCANNED : `${NOT_SCANNED}, grn (not yet swept)`
}
// Drop anything verification proved dead. THIS MUST RUN AFTER EVERY SOURCE HAS ATTACHED:
// it used to sit before the audio merge, so 71 dead bible.is filesets were attached after
// the drop had already happened and shipped anyway, while the pass reported "dropped 0".

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -14,14 +14,14 @@
"country": "People's Republic of China",
"region": "Asia",
"groups": 391,
"no_resource": 111,
"pop_no_resource": 128103100,
"no_resource": 107,
"pop_no_resource": 127734000,
"scripture": 118,
"film": 227,
"growing_unreached": 0,
"deaths_day": 274.2,
"growth": -0.12,
"pct_no_resource": 28.4,
"pct_no_resource": 27.4,
"band": "partial"
},
{
@ -29,14 +29,14 @@
"country": "Lao People's Democratic Republic",
"region": "Asia",
"groups": 158,
"no_resource": 72,
"pop_no_resource": 982785,
"no_resource": 67,
"pop_no_resource": 841085,
"scripture": 55,
"film": 58,
"growing_unreached": 0,
"deaths_day": 15.5,
"growth": null,
"pct_no_resource": 45.6,
"pct_no_resource": 42.4,
"band": "high"
},
{
@ -44,14 +44,14 @@
"country": "India",
"region": "Asia",
"groups": 2146,
"no_resource": 55,
"pop_no_resource": 1320695,
"no_resource": 42,
"pop_no_resource": 768235,
"scripture": 1757,
"film": 2044,
"growing_unreached": 38,
"deaths_day": 721.2,
"growth": 0.89,
"pct_no_resource": 2.6,
"pct_no_resource": 2,
"band": "partial"
},
{
@ -74,14 +74,14 @@
"country": "Chad",
"region": "Africa",
"groups": 150,
"no_resource": 69,
"pop_no_resource": 2565700,
"no_resource": 58,
"pop_no_resource": 1615300,
"scripture": 46,
"film": 49,
"growing_unreached": 37,
"deaths_day": 68.4,
"growth": 4.95,
"pct_no_resource": 46,
"pct_no_resource": 38.7,
"band": "high"
},
{
@ -89,29 +89,29 @@
"country": "Sudan",
"region": "Africa",
"groups": 185,
"no_resource": 58,
"pop_no_resource": 2809900,
"no_resource": 54,
"pop_no_resource": 2682300,
"scripture": 96,
"film": 117,
"growing_unreached": 36,
"deaths_day": 359.6,
"growth": 0.81,
"pct_no_resource": 31.4,
"band": "high"
"pct_no_resource": 29.2,
"band": "partial"
},
{
"iso3": "NGA",
"country": "Nigeria",
"region": "Africa",
"groups": 433,
"no_resource": 180,
"pop_no_resource": 6759525,
"no_resource": 163,
"pop_no_resource": 5264025,
"scripture": 105,
"film": 188,
"growing_unreached": 0,
"deaths_day": 44,
"growth": null,
"pct_no_resource": 41.6,
"pct_no_resource": 37.6,
"band": "high"
},
{
@ -119,14 +119,14 @@
"country": "Indonesia",
"region": "Asia",
"groups": 669,
"no_resource": 320,
"pop_no_resource": 4968960,
"no_resource": 319,
"pop_no_resource": 4883960,
"scripture": 222,
"film": 265,
"growing_unreached": 23,
"deaths_day": 47.2,
"growth": 0.81,
"pct_no_resource": 47.8,
"pct_no_resource": 47.7,
"band": "high"
},
{
@ -134,14 +134,14 @@
"country": "Afghanistan",
"region": "Asia",
"groups": 45,
"no_resource": 19,
"pop_no_resource": 1159300,
"no_resource": 17,
"pop_no_resource": 908800,
"scripture": 15,
"film": 25,
"growing_unreached": 17,
"deaths_day": 17.6,
"growth": 2.84,
"pct_no_resource": 42.2,
"pct_no_resource": 37.8,
"band": "high"
},
{
@ -179,14 +179,14 @@
"country": "Pakistan",
"region": "Asia",
"groups": 86,
"no_resource": 16,
"pop_no_resource": 10446875,
"no_resource": 15,
"pop_no_resource": 10437875,
"scripture": 22,
"film": 66,
"growing_unreached": 11,
"deaths_day": 362.3,
"growth": 1.51,
"pct_no_resource": 18.6,
"pct_no_resource": 17.4,
"band": "partial"
},
{
@ -194,14 +194,14 @@
"country": "Vietnam",
"region": "Asia",
"groups": 74,
"no_resource": 27,
"pop_no_resource": 1540175,
"no_resource": 25,
"pop_no_resource": 1312675,
"scripture": 28,
"film": 38,
"growing_unreached": 11,
"deaths_day": 5.7,
"growth": 0.63,
"pct_no_resource": 36.5,
"pct_no_resource": 33.8,
"band": "high"
},
{
@ -209,14 +209,14 @@
"country": "Islamic Republic of Iran",
"region": "Asia",
"groups": 47,
"no_resource": 10,
"pop_no_resource": 445400,
"no_resource": 8,
"pop_no_resource": 198400,
"scripture": 27,
"film": 32,
"growing_unreached": 10,
"deaths_day": 55.3,
"growth": 1.05,
"pct_no_resource": 21.3,
"pct_no_resource": 17,
"band": "partial"
},
{
@ -224,14 +224,14 @@
"country": "Ethiopia",
"region": "Africa",
"groups": 119,
"no_resource": 18,
"pop_no_resource": 1789100,
"no_resource": 17,
"pop_no_resource": 1672100,
"scripture": 59,
"film": 95,
"growing_unreached": 8,
"deaths_day": 130,
"growth": 2.58,
"pct_no_resource": 15.1,
"pct_no_resource": 14.3,
"band": "partial"
},
{
@ -239,14 +239,14 @@
"country": "South Sudan",
"region": "Africa",
"groups": 80,
"no_resource": 20,
"pop_no_resource": 546600,
"no_resource": 19,
"pop_no_resource": 460100,
"scripture": 39,
"film": 52,
"growing_unreached": 8,
"deaths_day": 59,
"growth": 3.93,
"pct_no_resource": 25,
"pct_no_resource": 23.8,
"band": "partial"
},
{
@ -254,14 +254,14 @@
"country": "Mali",
"region": "Africa",
"groups": 40,
"no_resource": 10,
"pop_no_resource": 1000800,
"no_resource": 8,
"pop_no_resource": 836400,
"scripture": 20,
"film": 26,
"growing_unreached": 8,
"deaths_day": 54.8,
"growth": 2.94,
"pct_no_resource": 25,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -269,14 +269,14 @@
"country": "Malaysia",
"region": "Asia",
"groups": 160,
"no_resource": 65,
"pop_no_resource": 1918550,
"no_resource": 64,
"pop_no_resource": 1832050,
"scripture": 75,
"film": 68,
"growing_unreached": 8,
"deaths_day": 8.2,
"growth": 1.22,
"pct_no_resource": 40.6,
"pct_no_resource": 40,
"band": "high"
},
{
@ -284,14 +284,14 @@
"country": "Cameroon",
"region": "Africa",
"groups": 260,
"no_resource": 126,
"pop_no_resource": 3210925,
"no_resource": 119,
"pop_no_resource": 2700925,
"scripture": 72,
"film": 84,
"growing_unreached": 8,
"deaths_day": 3.4,
"growth": 2.61,
"pct_no_resource": 48.5,
"pct_no_resource": 45.8,
"band": "high"
},
{
@ -299,14 +299,14 @@
"country": "Thailand",
"region": "Asia",
"groups": 62,
"no_resource": 15,
"pop_no_resource": 1069800,
"no_resource": 13,
"pop_no_resource": 980800,
"scripture": 34,
"film": 37,
"growing_unreached": 0,
"deaths_day": 12.6,
"growth": -0.05,
"pct_no_resource": 24.2,
"pct_no_resource": 21,
"band": "partial"
},
{
@ -314,15 +314,15 @@
"country": "Guinea",
"region": "Africa",
"groups": 41,
"no_resource": 14,
"pop_no_resource": 484600,
"no_resource": 12,
"pop_no_resource": 311100,
"scripture": 19,
"film": 24,
"growing_unreached": 7,
"deaths_day": 14.5,
"growth": 2.4,
"pct_no_resource": 34.1,
"band": "high"
"pct_no_resource": 29.3,
"band": "partial"
},
{
"iso3": "YEM",
@ -359,14 +359,14 @@
"country": "Nepal",
"region": "Asia",
"groups": 171,
"no_resource": 46,
"pop_no_resource": 1035050,
"no_resource": 44,
"pop_no_resource": 822550,
"scripture": 97,
"film": 111,
"growing_unreached": 0,
"deaths_day": 34,
"growth": null,
"pct_no_resource": 26.9,
"pct_no_resource": 25.7,
"band": "partial"
},
{
@ -374,14 +374,14 @@
"country": "Algeria",
"region": "Africa",
"groups": 33,
"no_resource": 5,
"pop_no_resource": 625200,
"no_resource": 4,
"pop_no_resource": 622800,
"scripture": 23,
"film": 23,
"growing_unreached": 6,
"deaths_day": 21.1,
"growth": 1.4,
"pct_no_resource": 15.2,
"pct_no_resource": 12.1,
"band": "partial"
},
{
@ -389,14 +389,14 @@
"country": "Burkina Faso",
"region": "Africa",
"groups": 76,
"no_resource": 17,
"pop_no_resource": 727700,
"no_resource": 16,
"pop_no_resource": 574700,
"scripture": 41,
"film": 37,
"growing_unreached": 6,
"deaths_day": 13.4,
"growth": 2.25,
"pct_no_resource": 22.4,
"pct_no_resource": 21.1,
"band": "partial"
},
{
@ -404,14 +404,14 @@
"country": "Angola",
"region": "Africa",
"groups": 43,
"no_resource": 10,
"pop_no_resource": 1123400,
"no_resource": 8,
"pop_no_resource": 949900,
"scripture": 13,
"film": 26,
"growing_unreached": 6,
"deaths_day": 13.2,
"growth": 3.04,
"pct_no_resource": 23.3,
"pct_no_resource": 18.6,
"band": "partial"
},
{
@ -449,14 +449,14 @@
"country": "Democratic Republic of the Congo",
"region": "Africa",
"groups": 215,
"no_resource": 89,
"pop_no_resource": 7568000,
"no_resource": 77,
"pop_no_resource": 6386400,
"scripture": 50,
"film": 95,
"growing_unreached": 5,
"deaths_day": 10.9,
"growth": 3.24,
"pct_no_resource": 41.4,
"pct_no_resource": 35.8,
"band": "high"
},
{
@ -479,14 +479,14 @@
"country": "United Republic of Tanzania",
"region": "Africa",
"groups": 132,
"no_resource": 18,
"pop_no_resource": 3289400,
"no_resource": 16,
"pop_no_resource": 3029900,
"scripture": 81,
"film": 101,
"growing_unreached": 4,
"deaths_day": 126.9,
"growth": 2.87,
"pct_no_resource": 13.6,
"pct_no_resource": 12.1,
"band": "partial"
},
{
@ -494,14 +494,14 @@
"country": "Myanmar",
"region": "Asia",
"groups": 110,
"no_resource": 25,
"pop_no_resource": 1274400,
"no_resource": 22,
"pop_no_resource": 999400,
"scripture": 63,
"film": 64,
"growing_unreached": 4,
"deaths_day": 25.8,
"growth": 0.67,
"pct_no_resource": 22.7,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -509,14 +509,14 @@
"country": "Benin",
"region": "Africa",
"groups": 57,
"no_resource": 3,
"pop_no_resource": 277000,
"no_resource": 2,
"pop_no_resource": 178500,
"scripture": 29,
"film": 38,
"growing_unreached": 4,
"deaths_day": 21.9,
"growth": 2.46,
"pct_no_resource": 5.3,
"pct_no_resource": 3.5,
"band": "partial"
},
{
@ -524,14 +524,14 @@
"country": "Libya",
"region": "Africa",
"groups": 27,
"no_resource": 5,
"pop_no_resource": 317800,
"no_resource": 4,
"pop_no_resource": 315000,
"scripture": 16,
"film": 20,
"growing_unreached": 4,
"deaths_day": 0,
"growth": 1.03,
"pct_no_resource": 18.5,
"pct_no_resource": 14.8,
"band": "partial"
},
{
@ -539,14 +539,14 @@
"country": "Iraq",
"region": "Asia",
"groups": 25,
"no_resource": 3,
"pop_no_resource": 774004,
"no_resource": 2,
"pop_no_resource": 774000,
"scripture": 16,
"film": 21,
"growing_unreached": 3,
"deaths_day": 42.3,
"growth": 2.12,
"pct_no_resource": 12,
"pct_no_resource": 8,
"band": "partial"
},
{
@ -554,14 +554,14 @@
"country": "Togo",
"region": "Africa",
"groups": 46,
"no_resource": 5,
"pop_no_resource": 248000,
"no_resource": 3,
"pop_no_resource": 143500,
"scripture": 32,
"film": 32,
"growing_unreached": 3,
"deaths_day": 27.9,
"growth": 2.2,
"pct_no_resource": 10.9,
"pct_no_resource": 6.5,
"band": "partial"
},
{
@ -689,14 +689,14 @@
"country": "Israel",
"region": "Asia",
"groups": 32,
"no_resource": 5,
"pop_no_resource": 807000,
"no_resource": 4,
"pop_no_resource": 670000,
"scripture": 23,
"film": 27,
"growing_unreached": 2,
"deaths_day": 2.8,
"growth": 1.54,
"pct_no_resource": 15.6,
"pct_no_resource": 12.5,
"band": "partial"
},
{
@ -764,14 +764,14 @@
"country": "Republic of The Gambia",
"region": "Africa",
"groups": 17,
"no_resource": 3,
"pop_no_resource": 35000,
"no_resource": 2,
"pop_no_resource": 29500,
"scripture": 6,
"film": 13,
"growing_unreached": 2,
"deaths_day": 0.6,
"growth": 2.28,
"pct_no_resource": 17.6,
"pct_no_resource": 11.8,
"band": "partial"
},
{
@ -779,14 +779,14 @@
"country": "Republic of the Congo",
"region": "Africa",
"groups": 64,
"no_resource": 27,
"pop_no_resource": 907200,
"no_resource": 24,
"pop_no_resource": 644700,
"scripture": 16,
"film": 27,
"growing_unreached": 2,
"deaths_day": 1,
"growth": 2.4,
"pct_no_resource": 42.2,
"pct_no_resource": 37.5,
"band": "high"
},
{
@ -884,14 +884,14 @@
"country": "Kenya",
"region": "Africa",
"groups": 69,
"no_resource": 5,
"pop_no_resource": 300300,
"no_resource": 4,
"pop_no_resource": 268800,
"scripture": 52,
"film": 62,
"growing_unreached": 1,
"deaths_day": 8.6,
"growth": 1.96,
"pct_no_resource": 7.2,
"pct_no_resource": 5.8,
"band": "partial"
},
{
@ -959,14 +959,14 @@
"country": "Cote d'Ivoire",
"region": "Africa",
"groups": 83,
"no_resource": 19,
"pop_no_resource": 541500,
"no_resource": 18,
"pop_no_resource": 459500,
"scripture": 44,
"film": 53,
"growing_unreached": 1,
"deaths_day": 3.7,
"growth": 2.44,
"pct_no_resource": 22.9,
"pct_no_resource": 21.7,
"band": "partial"
},
{
@ -1049,14 +1049,14 @@
"country": "Papua New Guinea",
"region": "Oceania",
"groups": 831,
"no_resource": 468,
"pop_no_resource": 2179750,
"no_resource": 463,
"pop_no_resource": 1882350,
"scripture": 332,
"film": 143,
"growing_unreached": 1,
"deaths_day": 0.9,
"growth": 1.78,
"pct_no_resource": 56.3,
"pct_no_resource": 55.7,
"band": "high"
},
{
@ -1094,14 +1094,14 @@
"country": "Central African Republic",
"region": "Africa",
"groups": 69,
"no_resource": 31,
"pop_no_resource": 1025250,
"no_resource": 30,
"pop_no_resource": 948750,
"scripture": 16,
"film": 28,
"growing_unreached": 1,
"deaths_day": 0.6,
"growth": 3.4,
"pct_no_resource": 44.9,
"pct_no_resource": 43.5,
"band": "high"
},
{
@ -1109,14 +1109,14 @@
"country": "Guinea-Bissau",
"region": "Africa",
"groups": 25,
"no_resource": 6,
"pop_no_resource": 55400,
"no_resource": 5,
"pop_no_resource": 43400,
"scripture": 10,
"film": 19,
"growing_unreached": 1,
"deaths_day": 0.7,
"growth": 2.21,
"pct_no_resource": 24,
"pct_no_resource": 20,
"band": "partial"
},
{
@ -1139,14 +1139,14 @@
"country": "Egypt",
"region": "Africa",
"groups": 23,
"no_resource": 4,
"pop_no_resource": 1688000,
"no_resource": 3,
"pop_no_resource": 1638000,
"scripture": 10,
"film": 19,
"growing_unreached": 1,
"deaths_day": 0.4,
"growth": 1.73,
"pct_no_resource": 17.4,
"pct_no_resource": 13,
"band": "partial"
},
{
@ -1259,14 +1259,14 @@
"country": "Fiji",
"region": "Oceania",
"groups": 21,
"no_resource": 9,
"pop_no_resource": 136450,
"no_resource": 8,
"pop_no_resource": 45450,
"scripture": 12,
"film": 12,
"growing_unreached": 1,
"deaths_day": 0.1,
"growth": 0.5,
"pct_no_resource": 42.9,
"pct_no_resource": 38.1,
"band": "high"
},
{
@ -1649,14 +1649,14 @@
"country": "French Guiana",
"region": "Americas",
"groups": 25,
"no_resource": 4,
"pop_no_resource": 124550,
"no_resource": 3,
"pop_no_resource": 4550,
"scripture": 18,
"film": 19,
"growing_unreached": 0,
"deaths_day": 0,
"growth": null,
"pct_no_resource": 16,
"pct_no_resource": 12,
"band": "partial"
},
{
@ -2009,14 +2009,14 @@
"country": "Namibia",
"region": "Africa",
"groups": 29,
"no_resource": 5,
"pop_no_resource": 113225,
"no_resource": 4,
"pop_no_resource": 25725,
"scripture": 4,
"film": 19,
"growing_unreached": 0,
"deaths_day": 3.3,
"growth": 2.24,
"pct_no_resource": 17.2,
"pct_no_resource": 13.8,
"band": "partial"
},
{
@ -2129,14 +2129,14 @@
"country": "Senegal",
"region": "Africa",
"groups": 28,
"no_resource": 4,
"pop_no_resource": 311800,
"no_resource": 3,
"pop_no_resource": 255800,
"scripture": 15,
"film": 23,
"growing_unreached": 0,
"deaths_day": 0.3,
"growth": 2.32,
"pct_no_resource": 14.3,
"pct_no_resource": 10.7,
"band": "partial"
},
{
@ -2144,14 +2144,14 @@
"country": "Philippines",
"region": "Asia",
"groups": 156,
"no_resource": 26,
"pop_no_resource": 3398275,
"no_resource": 25,
"pop_no_resource": 3279275,
"scripture": 108,
"film": 90,
"growing_unreached": 0,
"deaths_day": 0.2,
"growth": 0.83,
"pct_no_resource": 16.7,
"pct_no_resource": 16,
"band": "partial"
},
{
@ -2264,14 +2264,14 @@
"country": "United States of America",
"region": "Americas",
"groups": 623,
"no_resource": 38,
"pop_no_resource": 462510,
"no_resource": 35,
"pop_no_resource": 447105,
"scripture": 517,
"film": 549,
"growing_unreached": 0,
"deaths_day": 0.1,
"growth": 0.96,
"pct_no_resource": 6.1,
"pct_no_resource": 5.6,
"band": "partial"
},
{
@ -2504,14 +2504,14 @@
"country": "Liberia",
"region": "Africa",
"groups": 33,
"no_resource": 7,
"pop_no_resource": 431300,
"no_resource": 6,
"pop_no_resource": 292300,
"scripture": 19,
"film": 24,
"growing_unreached": 0,
"deaths_day": 0,
"growth": 2.16,
"pct_no_resource": 21.2,
"pct_no_resource": 18.2,
"band": "partial"
},
{
@ -2692,14 +2692,14 @@
"country": "United Kingdom",
"region": "Europe",
"groups": 74,
"no_resource": 3,
"pop_no_resource": 682800,
"no_resource": 2,
"pop_no_resource": 676800,
"scripture": 62,
"film": 71,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 4.1,
"pct_no_resource": 2.7,
"band": "partial",
"from_pins_only": true
},
@ -2836,14 +2836,14 @@
"country": "Ghana",
"region": "Africa",
"groups": 96,
"no_resource": 14,
"pop_no_resource": 637300,
"no_resource": 13,
"pop_no_resource": 514300,
"scripture": 71,
"film": 70,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 14.6,
"pct_no_resource": 13.5,
"band": "partial",
"from_pins_only": true
},
@ -2948,14 +2948,14 @@
"country": "Taiwan",
"region": "Asia",
"groups": 21,
"no_resource": 5,
"pop_no_resource": 306300,
"no_resource": 3,
"pop_no_resource": 96300,
"scripture": 16,
"film": 12,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 23.8,
"pct_no_resource": 14.3,
"band": "partial",
"from_pins_only": true
},
@ -3156,15 +3156,15 @@
"country": "Timor-Leste",
"region": "Asia",
"groups": 17,
"no_resource": 12,
"pop_no_resource": 448500,
"no_resource": 10,
"pop_no_resource": 203500,
"scripture": 2,
"film": 1,
"growing_unreached": null,
"deaths_day": null,
"growth": null,
"pct_no_resource": 70.6,
"band": "critical",
"pct_no_resource": 58.8,
"band": "high",
"from_pins_only": true
},
{

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
{
"meta": {
"title": "Gospel resource link verification",
"generated": "2026-09-24T18:33:00.458Z",
"generated": "2026-09-24T19:37:21.540Z",
"method": "HEAD, redirects followed. verified = HTTP 200. broken = 404/410. unknown = unreachable, an ambiguous status, or a URL shape that cannot be checked by fetch.",
"scope": "Every URL in data/tables/people-group-pins.json, deduplicated.",
"states": {
@ -86,12 +86,12 @@
]
},
"summary": {
"records": 7565,
"checkedThisPass": 74,
"records": 7658,
"checkedThisPass": 93,
"carriedForward": 74,
"pruned": 0,
"broken": 74,
"verified": 7486,
"verified": 7579,
"unknown": 5
},
"byHost": {
@ -132,7 +132,7 @@
"verified": 921
},
"fivefish.org": {
"verified": 74
"verified": 167
}
}
},
@ -37961,6 +37961,471 @@
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/13367?language=Exirima)",
"at": "2026-09-24T18:33:00.244Z"
},
"https://fivefish.org/kfo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/11208?language=Koro)",
"at": "2026-09-24T19:37:14.390Z"
},
"https://fivefish.org/tyi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2839?language=Kiteke%3A%20Zanaga)",
"at": "2026-09-24T19:37:14.418Z"
},
"https://fivefish.org/les": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1328?language=Walese)",
"at": "2026-09-24T19:37:14.433Z"
},
"https://fivefish.org/ekp": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2375?language=Ekpeye)",
"at": "2026-09-24T19:37:14.471Z"
},
"https://fivefish.org/fay": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/19022?language=Fars%2C%20Southwestern)",
"at": "2026-09-24T19:37:14.500Z"
},
"https://fivefish.org/hed": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2974?language=Kado)",
"at": "2026-09-24T19:37:14.544Z"
},
"https://fivefish.org/bmi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2921?language=Baguirmi)",
"at": "2026-09-24T19:37:14.572Z"
},
"https://fivefish.org/muo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4771?language=Nyongnepa)",
"at": "2026-09-24T19:37:14.714Z"
},
"https://fivefish.org/zmq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1529?language=Kiwenye%20Mituku)",
"at": "2026-09-24T19:37:15.004Z"
},
"https://fivefish.org/byc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2963?language=Ubaghara)",
"at": "2026-09-24T19:37:15.011Z"
},
"https://fivefish.org/sen": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6015?language=Senoufo%2C%20Nanerige)",
"at": "2026-09-24T19:37:15.040Z"
},
"https://fivefish.org/kem": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4613?language=Kemak)",
"at": "2026-09-24T19:37:15.115Z"
},
"https://fivefish.org/luj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1367?language=Luna%20Inkongo)",
"at": "2026-09-24T19:37:15.161Z"
},
"https://fivefish.org/pwn": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1993?language=Paiwan)",
"at": "2026-09-24T19:37:15.195Z"
},
"https://fivefish.org/lse": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1574?language=Lolenge)",
"at": "2026-09-24T19:37:15.202Z"
},
"https://fivefish.org/lar": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4881?language=Larteh)",
"at": "2026-09-24T19:37:15.231Z"
},
"https://fivefish.org/cde": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3063?language=Chenchu)",
"at": "2026-09-24T19:37:15.515Z"
},
"https://fivefish.org/nre": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3232?language=Naga%2C%20Southern%20Rengma)",
"at": "2026-09-24T19:37:15.736Z"
},
"https://fivefish.org/pmm": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2854?language=Pomo)",
"at": "2026-09-24T19:37:15.788Z"
},
"https://fivefish.org/xub": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4070?language=Kurumba%2C%20Vatta)",
"at": "2026-09-24T19:37:15.818Z"
},
"https://fivefish.org/wyy": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4303?language=Fijian%2C%20Western)",
"at": "2026-09-24T19:37:16.016Z"
},
"https://fivefish.org/yiz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4806?language=Azhe)",
"at": "2026-09-24T19:37:16.037Z"
},
"https://fivefish.org/lgm": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4517?language=Lega-Mwenga)",
"at": "2026-09-24T19:37:16.052Z"
},
"https://fivefish.org/ged": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/393?language=Gade)",
"at": "2026-09-24T19:37:16.127Z"
},
"https://fivefish.org/kep": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3908?language=Kaikadi)",
"at": "2026-09-24T19:37:16.315Z"
},
"https://fivefish.org/bws": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1548?language=Bomboma)",
"at": "2026-09-24T19:37:16.347Z"
},
"https://fivefish.org/brt": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1758?language=Njwande)",
"at": "2026-09-24T19:37:16.387Z"
},
"https://fivefish.org/laa": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/296?language=Subanon%2C%20Margosatubig)",
"at": "2026-09-24T19:37:16.438Z"
},
"https://fivefish.org/bji": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1439?language=Burji)",
"at": "2026-09-24T19:37:16.554Z"
},
"https://fivefish.org/bip": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4583?language=Kibila)",
"at": "2026-09-24T19:37:16.662Z"
},
"https://fivefish.org/jjr": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1960?language=Bankal)",
"at": "2026-09-24T19:37:16.708Z"
},
"https://fivefish.org/orx": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2374?language=Oron)",
"at": "2026-09-24T19:37:16.716Z"
},
"https://fivefish.org/lel": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1518?language=Bushilele)",
"at": "2026-09-24T19:37:16.788Z"
},
"https://fivefish.org/mrh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5027?language=Chin%2C%20Mara%3A%20Myanmar)",
"at": "2026-09-24T19:37:16.971Z"
},
"https://fivefish.org/dgh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1770?language=Dghwede)",
"at": "2026-09-24T19:37:17.102Z"
},
"https://fivefish.org/ymk": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5101?language=Makwe)",
"at": "2026-09-24T19:37:17.207Z"
},
"https://fivefish.org/ala": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2139?language=Alago)",
"at": "2026-09-24T19:37:17.228Z"
},
"https://fivefish.org/nlj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1361?language=Kinyali)",
"at": "2026-09-24T19:37:17.242Z"
},
"https://fivefish.org/kra": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4165?language=Kumhali)",
"at": "2026-09-24T19:37:17.265Z"
},
"https://fivefish.org/ngn": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2627?language=Ngwaw)",
"at": "2026-09-24T19:37:17.463Z"
},
"https://fivefish.org/lum": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2774?language=Luimbi)",
"at": "2026-09-24T19:37:17.471Z"
},
"https://fivefish.org/vaj": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4261?language=%21Kung)",
"at": "2026-09-24T19:37:17.547Z"
},
"https://fivefish.org/mkz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/5068?language=Makasae%20Uatolari%20Kilikai)",
"at": "2026-09-24T19:37:17.737Z"
},
"https://fivefish.org/gim": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1488?language=Gimi%20Group)",
"at": "2026-09-24T19:37:17.922Z"
},
"https://fivefish.org/int": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/10906?language=Intha)",
"at": "2026-09-24T19:37:17.951Z"
},
"https://fivefish.org/sed": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/789?language=Sedang)",
"at": "2026-09-24T19:37:17.993Z"
},
"https://fivefish.org/jaf": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2558?language=Jarra)",
"at": "2026-09-24T19:37:18.068Z"
},
"https://fivefish.org/lrl": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/12770?language=Lari)",
"at": "2026-09-24T19:37:18.080Z"
},
"https://fivefish.org/tii": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1555?language=Kitiene)",
"at": "2026-09-24T19:37:18.123Z"
},
"https://fivefish.org/cov": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6333?language=Cao%20Miao%2C%20Sanjiang)",
"at": "2026-09-24T19:37:18.240Z"
},
"https://fivefish.org/sct": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/22852?language=Katang%2C%20Southern)",
"at": "2026-09-24T19:37:18.387Z"
},
"https://fivefish.org/kwc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6396?language=Likwala)",
"at": "2026-09-24T19:37:18.419Z"
},
"https://fivefish.org/atu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/15963?language=Reel)",
"at": "2026-09-24T19:37:18.537Z"
},
"https://fivefish.org/grd": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1966?language=Guruntum-Mbaaru)",
"at": "2026-09-24T19:37:18.604Z"
},
"https://fivefish.org/ngq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1075?language=Ngurime)",
"at": "2026-09-24T19:37:18.646Z"
},
"https://fivefish.org/rin": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2145?language=Nungu)",
"at": "2026-09-24T19:37:18.774Z"
},
"https://fivefish.org/tuq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3098?language=Toubbou)",
"at": "2026-09-24T19:37:18.810Z"
},
"https://fivefish.org/raa": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3227?language=Dungmali)",
"at": "2026-09-24T19:37:18.908Z"
},
"https://fivefish.org/tay": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1998?language=Tayal)",
"at": "2026-09-24T19:37:18.917Z"
},
"https://fivefish.org/dai": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4790?language=Day)",
"at": "2026-09-24T19:37:18.943Z"
},
"https://fivefish.org/gec": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1600?language=Grebo%2C%20Gboloo)",
"at": "2026-09-24T19:37:19.113Z"
},
"https://fivefish.org/lla": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1791?language=Lalla)",
"at": "2026-09-24T19:37:19.214Z"
},
"https://fivefish.org/nfu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6234?language=Mfumte)",
"at": "2026-09-24T19:37:19.221Z"
},
"https://fivefish.org/hol": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1406?language=Kiholo)",
"at": "2026-09-24T19:37:19.453Z"
},
"https://fivefish.org/wom": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4755?language=Wom%20%5BNigeria%5D)",
"at": "2026-09-24T19:37:19.466Z"
},
"https://fivefish.org/wgi": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/358?language=Wahgi%20Group)",
"at": "2026-09-24T19:37:19.471Z"
},
"https://fivefish.org/jad": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4998?language=Jahanka)",
"at": "2026-09-24T19:37:19.602Z"
},
"https://fivefish.org/xnz": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1027?language=Kena)",
"at": "2026-09-24T19:37:19.645Z"
},
"https://fivefish.org/hia": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4429?language=Lamang)",
"at": "2026-09-24T19:37:19.672Z"
},
"https://fivefish.org/lna": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1422?language=Langbasi)",
"at": "2026-09-24T19:37:19.765Z"
},
"https://fivefish.org/erh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6383?language=Erohwa)",
"at": "2026-09-24T19:37:19.835Z"
},
"https://fivefish.org/rir": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4644?language=Ribun)",
"at": "2026-09-24T19:37:19.971Z"
},
"https://fivefish.org/gcr": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2362?language=Guianese%20French%20Creole)",
"at": "2026-09-24T19:37:20.083Z"
},
"https://fivefish.org/bag": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/17738?language=Tuki)",
"at": "2026-09-24T19:37:20.145Z"
},
"https://fivefish.org/hml": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6246?language=Miao%3A%20Laohu)",
"at": "2026-09-24T19:37:20.154Z"
},
"https://fivefish.org/yhd": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4966?language=Arabic%2C%20Judeo-Iraqi)",
"at": "2026-09-24T19:37:20.212Z"
},
"https://fivefish.org/tou": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/792?language=Tho)",
"at": "2026-09-24T19:37:20.239Z"
},
"https://fivefish.org/dau": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2957?language=Dadjo)",
"at": "2026-09-24T19:37:20.332Z"
},
"https://fivefish.org/aoe": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3262?language=Anen%20Anga)",
"at": "2026-09-24T19:37:20.400Z"
},
"https://fivefish.org/hca": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/19048?language=Andaman%20Hindi%20Creole)",
"at": "2026-09-24T19:37:20.449Z"
},
"https://fivefish.org/mgb": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/13709?language=Mararit%3A%20Abou%20Charib)",
"at": "2026-09-24T19:37:20.603Z"
},
"https://fivefish.org/bsy": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/409?language=Bisaya%2C%20Sabah)",
"at": "2026-09-24T19:37:20.749Z"
},
"https://fivefish.org/aks": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4786?language=Akaselem)",
"at": "2026-09-24T19:37:20.766Z"
},
"https://fivefish.org/nyw": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/15183?language=Nyaw)",
"at": "2026-09-24T19:37:20.863Z"
},
"https://fivefish.org/blo": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/4811?language=Anii)",
"at": "2026-09-24T19:37:20.896Z"
},
"https://fivefish.org/djc": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3099?language=Daju%2C%20Dar%20Daju)",
"at": "2026-09-24T19:37:20.994Z"
},
"https://fivefish.org/psh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3317?language=Pashai%3A%20Kohistani%20%26%20Shahi)",
"at": "2026-09-24T19:37:20.999Z"
},
"https://fivefish.org/kie": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3089?language=Kibet%3A%20Mouro)",
"at": "2026-09-24T19:37:21.053Z"
},
"https://fivefish.org/bid": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2984?language=Bidio)",
"at": "2026-09-24T19:37:21.082Z"
},
"https://fivefish.org/kkh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/1152?language=Khun)",
"at": "2026-09-24T19:37:21.201Z"
},
"https://fivefish.org/knu": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/2530?language=Kono)",
"at": "2026-09-24T19:37:21.234Z"
},
"https://fivefish.org/dsq": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/6313?language=Dausahaq)",
"at": "2026-09-24T19:37:21.253Z"
},
"https://fivefish.org/bsh": {
"state": "verified",
"detail": "HTTP 200 (via https://fivefish.org/3341?language=Kate)",
"at": "2026-09-24T19:37:21.390Z"
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -2,8 +2,8 @@
"source": "https://fivefish.org/<iso639-3> (5fish, Global Recordings Network)",
"fetched": "2026-09-24",
"_note": "GRN gospel AUDIO, typed as Gospel audio and never as Scripture — \"Words of Life\" is gospel messages and Bible stories, not a translation of the canon. The host never 404s: a nonsense path returns a geo-located page with HTTP 200, so existence is read from the page title, with a negative and a positive control run in band every time. Their robots.txt asks for Crawl-delay: 10 and this fetcher obeys it, sequentially.",
"codes": 74,
"decidedNone": 106,
"codes": 167,
"decidedNone": 183,
"byIso": {
"mnp": {
"u": "https://fivefish.org/mnp",
@ -300,6 +300,378 @@
"khc": {
"u": "https://fivefish.org/khc",
"n": "Tukangbesi North: Wanci"
},
"orx": {
"u": "https://fivefish.org/orx",
"n": "Oron"
},
"luj": {
"u": "https://fivefish.org/luj",
"n": "Luna Inkongo"
},
"dsq": {
"u": "https://fivefish.org/dsq",
"n": "Dausahaq"
},
"bsh": {
"u": "https://fivefish.org/bsh",
"n": "Kate"
},
"dai": {
"u": "https://fivefish.org/dai",
"n": "Day"
},
"mkz": {
"u": "https://fivefish.org/mkz",
"n": "Makasae Uatolari Kilikai"
},
"kep": {
"u": "https://fivefish.org/kep",
"n": "Kaikadi"
},
"ymk": {
"u": "https://fivefish.org/ymk",
"n": "Makwe"
},
"tyi": {
"u": "https://fivefish.org/tyi",
"n": "Kiteke: Zanaga"
},
"sed": {
"u": "https://fivefish.org/sed",
"n": "Sedang"
},
"sen": {
"u": "https://fivefish.org/sen",
"n": "Senoufo, Nanerige"
},
"int": {
"u": "https://fivefish.org/int",
"n": "Intha"
},
"bji": {
"u": "https://fivefish.org/bji",
"n": "Burji"
},
"les": {
"u": "https://fivefish.org/les",
"n": "Walese"
},
"bmi": {
"u": "https://fivefish.org/bmi",
"n": "Baguirmi"
},
"yhd": {
"u": "https://fivefish.org/yhd",
"n": "Arabic, Judeo-Iraqi"
},
"ged": {
"u": "https://fivefish.org/ged",
"n": "Gade"
},
"gec": {
"u": "https://fivefish.org/gec",
"n": "Grebo, Gboloo"
},
"lel": {
"u": "https://fivefish.org/lel",
"n": "Bushilele"
},
"hml": {
"u": "https://fivefish.org/hml",
"n": "Miao: Laohu"
},
"cov": {
"u": "https://fivefish.org/cov",
"n": "Cao Miao, Sanjiang"
},
"lrl": {
"u": "https://fivefish.org/lrl",
"n": "Lari"
},
"kra": {
"u": "https://fivefish.org/kra",
"n": "Kumhali"
},
"jad": {
"u": "https://fivefish.org/jad",
"n": "Jahanka"
},
"ala": {
"u": "https://fivefish.org/ala",
"n": "Alago"
},
"lar": {
"u": "https://fivefish.org/lar",
"n": "Larteh"
},
"knu": {
"u": "https://fivefish.org/knu",
"n": "Kono"
},
"gcr": {
"u": "https://fivefish.org/gcr",
"n": "Guianese French Creole"
},
"blo": {
"u": "https://fivefish.org/blo",
"n": "Anii"
},
"laa": {
"u": "https://fivefish.org/laa",
"n": "Subanen, Southern"
},
"sct": {
"u": "https://fivefish.org/sct",
"n": "Katang, Southern"
},
"pwn": {
"u": "https://fivefish.org/pwn",
"n": "Paiwan"
},
"fay": {
"u": "https://fivefish.org/fay",
"n": "Fars, Southwestern"
},
"hia": {
"u": "https://fivefish.org/hia",
"n": "Lamang"
},
"zmq": {
"u": "https://fivefish.org/zmq",
"n": "Kiwenye Mituku"
},
"xnz": {
"u": "https://fivefish.org/xnz",
"n": "Kena"
},
"nlj": {
"u": "https://fivefish.org/nlj",
"n": "Kinyali"
},
"erh": {
"u": "https://fivefish.org/erh",
"n": "Erohwa"
},
"tii": {
"u": "https://fivefish.org/tii",
"n": "Kitiene"
},
"aoe": {
"u": "https://fivefish.org/aoe",
"n": "Anen Anga"
},
"rin": {
"u": "https://fivefish.org/rin",
"n": "Nungu"
},
"ekp": {
"u": "https://fivefish.org/ekp",
"n": "Ekpeye"
},
"kie": {
"u": "https://fivefish.org/kie",
"n": "Kibet: Mouro"
},
"wgi": {
"u": "https://fivefish.org/wgi",
"n": "Wahgi Group"
},
"jjr": {
"u": "https://fivefish.org/jjr",
"n": "Bankal"
},
"tuq": {
"u": "https://fivefish.org/tuq",
"n": "Toubbou"
},
"kkh": {
"u": "https://fivefish.org/kkh",
"n": "Khun"
},
"hca": {
"u": "https://fivefish.org/hca",
"n": "Andaman Hindi Creole"
},
"hed": {
"u": "https://fivefish.org/hed",
"n": "Kado"
},
"dau": {
"u": "https://fivefish.org/dau",
"n": "Dadjo"
},
"lum": {
"u": "https://fivefish.org/lum",
"n": "Luimbi"
},
"ngn": {
"u": "https://fivefish.org/ngn",
"n": "Ngwaw"
},
"bip": {
"u": "https://fivefish.org/bip",
"n": "Kibila"
},
"nyw": {
"u": "https://fivefish.org/nyw",
"n": "Nyaw"
},
"bsy": {
"u": "https://fivefish.org/bsy",
"n": "Bisaya, Sabah"
},
"lla": {
"u": "https://fivefish.org/lla",
"n": "Lalla"
},
"ngq": {
"u": "https://fivefish.org/ngq",
"n": "Ngurime"
},
"yiz": {
"u": "https://fivefish.org/yiz",
"n": "Azhe"
},
"bid": {
"u": "https://fivefish.org/bid",
"n": "Bidio"
},
"psh": {
"u": "https://fivefish.org/psh",
"n": "Pashai: Kohistani & Shahi"
},
"mgb": {
"u": "https://fivefish.org/mgb",
"n": "Mararit: Abou Charib"
},
"lse": {
"u": "https://fivefish.org/lse",
"n": "Lolenge"
},
"kwc": {
"u": "https://fivefish.org/kwc",
"n": "Likwala"
},
"lgm": {
"u": "https://fivefish.org/lgm",
"n": "Lega-Mwenga"
},
"jaf": {
"u": "https://fivefish.org/jaf",
"n": "Jarra"
},
"tay": {
"u": "https://fivefish.org/tay",
"n": "Tayal"
},
"djc": {
"u": "https://fivefish.org/djc",
"n": "Daju, Dar Daju"
},
"pmm": {
"u": "https://fivefish.org/pmm",
"n": "Pomo"
},
"wyy": {
"u": "https://fivefish.org/wyy",
"n": "Fijian, Western"
},
"muo": {
"u": "https://fivefish.org/muo",
"n": "Nyongnepa"
},
"bag": {
"u": "https://fivefish.org/bag",
"n": "Tuki"
},
"vaj": {
"u": "https://fivefish.org/vaj",
"n": "!Kung"
},
"atu": {
"u": "https://fivefish.org/atu",
"n": "Reel"
},
"bws": {
"u": "https://fivefish.org/bws",
"n": "Bomboma"
},
"dgh": {
"u": "https://fivefish.org/dgh",
"n": "Dghwede"
},
"rir": {
"u": "https://fivefish.org/rir",
"n": "Ribun"
},
"aks": {
"u": "https://fivefish.org/aks",
"n": "Akaselem"
},
"kem": {
"u": "https://fivefish.org/kem",
"n": "Kemak"
},
"raa": {
"u": "https://fivefish.org/raa",
"n": "Dungmali"
},
"kfo": {
"u": "https://fivefish.org/kfo",
"n": "Koro"
},
"lna": {
"u": "https://fivefish.org/lna",
"n": "Langbasi"
},
"xub": {
"u": "https://fivefish.org/xub",
"n": "Kurumba, Vatta"
},
"hol": {
"u": "https://fivefish.org/hol",
"n": "Kiholo"
},
"gim": {
"u": "https://fivefish.org/gim",
"n": "Gimi Group"
},
"nre": {
"u": "https://fivefish.org/nre",
"n": "Naga, Southern Rengma"
},
"nfu": {
"u": "https://fivefish.org/nfu",
"n": "Mfumte"
},
"mrh": {
"u": "https://fivefish.org/mrh",
"n": "Chin, Mara: Myanmar"
},
"wom": {
"u": "https://fivefish.org/wom",
"n": "Wom [Nigeria]"
},
"grd": {
"u": "https://fivefish.org/grd",
"n": "Guruntum-Mbaaru"
},
"tou": {
"u": "https://fivefish.org/tou",
"n": "Tho"
},
"byc": {
"u": "https://fivefish.org/byc",
"n": "Ubaghara"
},
"brt": {
"u": "https://fivefish.org/brt",
"n": "Njwande"
},
"cde": {
"u": "https://fivefish.org/cde",
"n": "Chenchu"
}
},
"none": [
@ -408,6 +780,83 @@
"dux",
"ajz",
"mdi",
"psi"
"psi",
"bxu",
"nrb",
"cda",
"sqs",
"daj",
"csx",
"zhd",
"gcl",
"ecs",
"qcm",
"lgz",
"mzy",
"yel",
"nwe",
"cng",
"gbs",
"aig",
"adf",
"nzu",
"dpp",
"muq",
"tbi",
"jle",
"buc",
"qfa",
"tse",
"thi",
"bvl",
"sfb",
"okb",
"ysl",
"lwa",
"doq",
"rou",
"mub",
"cnu",
"mxf",
"csf",
"beb",
"mmz",
"cse",
"psr",
"qht",
"asf",
"tco",
"pnt",
"hsh",
"qae",
"shv",
"sqm",
"bdr",
"bct",
"lbo",
"mei",
"bfl",
"sgg",
"mhd",
"ero",
"sgx",
"asq",
"gwx",
"mgy",
"mes",
"kyp",
"bqk",
"kkw",
"tct",
"sqt",
"pmb",
"brm",
"nru",
"bmg",
"esn",
"nzy",
"ryn",
"cml",
"tda"
]
}

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -596,7 +596,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "doxa-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL",
"integrity": "sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX",
"elements": [
"gospel-resources-map"
],
@ -616,7 +616,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -630,7 +630,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -644,7 +644,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -658,7 +658,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -672,7 +672,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-g462+Qv+4YlY20GDlJfUBO+cmyDbbVuo5yQpM/EUz3hXR6XWGOgc4NzW0CdXSlUX\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]
@ -681,7 +681,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "doxa-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++",
"integrity": "sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv",
"elements": [
"my-upg-100-list-map"
],
@ -701,7 +701,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -715,7 +715,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -729,7 +729,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-4WnbHvh/qt/xIADBpWm8xKj0gcGj6hStHIGhrTLibVZtGGga8Bd/YXh31SD/tDCv" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: