Merge main into bundler-2/work
# Conflicts: # package.json # public/doxa-maps/index.html
This commit is contained in:
commit
3921c605d6
53 changed files with 3828 additions and 1666 deletions
|
|
@ -125,6 +125,35 @@ owe a complete audit.
|
|||
★ **Read the image when the deliverable is visual.** Numbers have certified visually broken output
|
||||
in this repo more than once — a green generator is not a look-at-it.
|
||||
|
||||
## Landing on main: the shape of the lock
|
||||
|
||||
`main` is shared and serialized by `main-capture`. The ordering is load-bearing and BOTH
|
||||
obvious forms are wrong — each was tried, and each broke something:
|
||||
|
||||
| Form | What it does wrong |
|
||||
|---|---|
|
||||
| `capture ; scan && push ; release` | `;` runs `release` even when **capture failed** — so a non-owner frees the actual owner's lock. This happened: it dropped another agent's hold while they were mid-landing. |
|
||||
| `capture && scan && push && release` | `&&` skips `release` when **push fails** — the branch stays protected by a lock nobody will now drop, and everyone else is wedged. |
|
||||
|
||||
Release must be conditional on **your capture succeeding**, never on your push succeeding:
|
||||
|
||||
```sh
|
||||
main-capture capture --wait && { main-capture scan && git push origin HEAD:main ; main-capture release ; }
|
||||
# or, equivalently:
|
||||
if main-capture capture --wait; then main-capture scan && git push origin HEAD:main; main-capture release; fi
|
||||
```
|
||||
|
||||
Capture failed → `release` never runs, so you cannot free someone else's lock.
|
||||
Capture succeeded → `release` always runs, so a failed push cannot wedge the branch.
|
||||
|
||||
Two more rules that cost real work to learn:
|
||||
|
||||
- **Hold the lock THROUGH fetch-merge-push**, not just the push. Capturing, releasing, then
|
||||
merging means you merged against a `main` someone else has already moved.
|
||||
- **Never rebase a branch that has ever recorded a `-s ours` merge.** Replaying duplicates the
|
||||
recorded ancestry. Merge instead. On conflicts in generated `public/`, take `main`'s tree and
|
||||
regenerate from source rather than hand-resolving built bytes.
|
||||
|
||||
## Where the hard-won knowledge lives
|
||||
|
||||
Read these only when they bear on your task; do not read them all:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,29 +14,29 @@
|
|||
"country": "People's Republic of China",
|
||||
"region": "Asia",
|
||||
"groups": 391,
|
||||
"no_resource": 125,
|
||||
"pop_no_resource": 150271700,
|
||||
"no_resource": 111,
|
||||
"pop_no_resource": 128103100,
|
||||
"scripture": 118,
|
||||
"film": 227,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 274.2,
|
||||
"growth": -0.12,
|
||||
"pct_no_resource": 32,
|
||||
"band": "high"
|
||||
"pct_no_resource": 28.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "LAO",
|
||||
"country": "Lao People's Democratic Republic",
|
||||
"region": "Asia",
|
||||
"groups": 158,
|
||||
"no_resource": 79,
|
||||
"pop_no_resource": 1306185,
|
||||
"no_resource": 72,
|
||||
"pop_no_resource": 982785,
|
||||
"scripture": 55,
|
||||
"film": 58,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 15.5,
|
||||
"growth": null,
|
||||
"pct_no_resource": 50,
|
||||
"pct_no_resource": 45.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -44,14 +44,14 @@
|
|||
"country": "India",
|
||||
"region": "Asia",
|
||||
"groups": 2146,
|
||||
"no_resource": 63,
|
||||
"pop_no_resource": 2157770,
|
||||
"no_resource": 55,
|
||||
"pop_no_resource": 1320695,
|
||||
"scripture": 1757,
|
||||
"film": 2044,
|
||||
"growing_unreached": 38,
|
||||
"deaths_day": 721.2,
|
||||
"growth": 0.89,
|
||||
"pct_no_resource": 2.9,
|
||||
"pct_no_resource": 2.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -74,14 +74,14 @@
|
|||
"country": "Chad",
|
||||
"region": "Africa",
|
||||
"groups": 150,
|
||||
"no_resource": 79,
|
||||
"pop_no_resource": 4813200,
|
||||
"no_resource": 69,
|
||||
"pop_no_resource": 2565700,
|
||||
"scripture": 46,
|
||||
"film": 49,
|
||||
"growing_unreached": 37,
|
||||
"deaths_day": 68.4,
|
||||
"growth": 4.95,
|
||||
"pct_no_resource": 52.7,
|
||||
"pct_no_resource": 46,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -89,14 +89,14 @@
|
|||
"country": "Sudan",
|
||||
"region": "Africa",
|
||||
"groups": 185,
|
||||
"no_resource": 62,
|
||||
"pop_no_resource": 3220900,
|
||||
"no_resource": 58,
|
||||
"pop_no_resource": 2809900,
|
||||
"scripture": 96,
|
||||
"film": 117,
|
||||
"growing_unreached": 36,
|
||||
"deaths_day": 359.6,
|
||||
"growth": 0.81,
|
||||
"pct_no_resource": 33.5,
|
||||
"pct_no_resource": 31.4,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -104,14 +104,14 @@
|
|||
"country": "Nigeria",
|
||||
"region": "Africa",
|
||||
"groups": 433,
|
||||
"no_resource": 187,
|
||||
"pop_no_resource": 8978825,
|
||||
"no_resource": 180,
|
||||
"pop_no_resource": 6759525,
|
||||
"scripture": 105,
|
||||
"film": 188,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 44,
|
||||
"growth": null,
|
||||
"pct_no_resource": 43.2,
|
||||
"pct_no_resource": 41.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -119,14 +119,14 @@
|
|||
"country": "Indonesia",
|
||||
"region": "Asia",
|
||||
"groups": 669,
|
||||
"no_resource": 321,
|
||||
"pop_no_resource": 5140960,
|
||||
"no_resource": 320,
|
||||
"pop_no_resource": 4968960,
|
||||
"scripture": 222,
|
||||
"film": 265,
|
||||
"growing_unreached": 23,
|
||||
"deaths_day": 47.2,
|
||||
"growth": 0.81,
|
||||
"pct_no_resource": 48,
|
||||
"pct_no_resource": 47.8,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -164,14 +164,14 @@
|
|||
"country": "Bhutan",
|
||||
"region": "Asia",
|
||||
"groups": 36,
|
||||
"no_resource": 19,
|
||||
"pop_no_resource": 238225,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 233625,
|
||||
"scripture": 11,
|
||||
"film": 16,
|
||||
"growing_unreached": 13,
|
||||
"deaths_day": 2.1,
|
||||
"growth": 0.65,
|
||||
"pct_no_resource": 52.8,
|
||||
"pct_no_resource": 50,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -194,14 +194,14 @@
|
|||
"country": "Vietnam",
|
||||
"region": "Asia",
|
||||
"groups": 74,
|
||||
"no_resource": 29,
|
||||
"pop_no_resource": 1860175,
|
||||
"no_resource": 27,
|
||||
"pop_no_resource": 1540175,
|
||||
"scripture": 28,
|
||||
"film": 38,
|
||||
"growing_unreached": 11,
|
||||
"deaths_day": 5.7,
|
||||
"growth": 0.63,
|
||||
"pct_no_resource": 39.2,
|
||||
"pct_no_resource": 36.5,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -209,14 +209,14 @@
|
|||
"country": "Islamic Republic of Iran",
|
||||
"region": "Asia",
|
||||
"groups": 47,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 625400,
|
||||
"no_resource": 10,
|
||||
"pop_no_resource": 445400,
|
||||
"scripture": 27,
|
||||
"film": 32,
|
||||
"growing_unreached": 10,
|
||||
"deaths_day": 55.3,
|
||||
"growth": 1.05,
|
||||
"pct_no_resource": 23.4,
|
||||
"pct_no_resource": 21.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -239,14 +239,14 @@
|
|||
"country": "South Sudan",
|
||||
"region": "Africa",
|
||||
"groups": 80,
|
||||
"no_resource": 22,
|
||||
"pop_no_resource": 577100,
|
||||
"no_resource": 20,
|
||||
"pop_no_resource": 546600,
|
||||
"scripture": 39,
|
||||
"film": 52,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 59,
|
||||
"growth": 3.93,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -254,14 +254,14 @@
|
|||
"country": "Mali",
|
||||
"region": "Africa",
|
||||
"groups": 40,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 1066800,
|
||||
"no_resource": 10,
|
||||
"pop_no_resource": 1000800,
|
||||
"scripture": 20,
|
||||
"film": 26,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 54.8,
|
||||
"growth": 2.94,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -269,14 +269,14 @@
|
|||
"country": "Malaysia",
|
||||
"region": "Asia",
|
||||
"groups": 160,
|
||||
"no_resource": 67,
|
||||
"pop_no_resource": 2650950,
|
||||
"no_resource": 65,
|
||||
"pop_no_resource": 1918550,
|
||||
"scripture": 75,
|
||||
"film": 68,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 8.2,
|
||||
"growth": 1.22,
|
||||
"pct_no_resource": 41.9,
|
||||
"pct_no_resource": 40.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -284,14 +284,14 @@
|
|||
"country": "Cameroon",
|
||||
"region": "Africa",
|
||||
"groups": 260,
|
||||
"no_resource": 129,
|
||||
"pop_no_resource": 3656425,
|
||||
"no_resource": 126,
|
||||
"pop_no_resource": 3210925,
|
||||
"scripture": 72,
|
||||
"film": 84,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 3.4,
|
||||
"growth": 2.61,
|
||||
"pct_no_resource": 49.6,
|
||||
"pct_no_resource": 48.5,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -299,14 +299,14 @@
|
|||
"country": "Thailand",
|
||||
"region": "Asia",
|
||||
"groups": 62,
|
||||
"no_resource": 16,
|
||||
"pop_no_resource": 1528800,
|
||||
"no_resource": 15,
|
||||
"pop_no_resource": 1069800,
|
||||
"scripture": 34,
|
||||
"film": 37,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 12.6,
|
||||
"growth": -0.05,
|
||||
"pct_no_resource": 25.8,
|
||||
"pct_no_resource": 24.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -359,14 +359,14 @@
|
|||
"country": "Nepal",
|
||||
"region": "Asia",
|
||||
"groups": 171,
|
||||
"no_resource": 47,
|
||||
"pop_no_resource": 1259050,
|
||||
"no_resource": 46,
|
||||
"pop_no_resource": 1035050,
|
||||
"scripture": 97,
|
||||
"film": 111,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 34,
|
||||
"growth": null,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 26.9,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -389,14 +389,14 @@
|
|||
"country": "Burkina Faso",
|
||||
"region": "Africa",
|
||||
"groups": 76,
|
||||
"no_resource": 20,
|
||||
"pop_no_resource": 1500700,
|
||||
"no_resource": 17,
|
||||
"pop_no_resource": 727700,
|
||||
"scripture": 41,
|
||||
"film": 37,
|
||||
"growing_unreached": 6,
|
||||
"deaths_day": 13.4,
|
||||
"growth": 2.25,
|
||||
"pct_no_resource": 26.3,
|
||||
"pct_no_resource": 22.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -449,14 +449,14 @@
|
|||
"country": "Democratic Republic of the Congo",
|
||||
"region": "Africa",
|
||||
"groups": 215,
|
||||
"no_resource": 105,
|
||||
"pop_no_resource": 12428000,
|
||||
"no_resource": 89,
|
||||
"pop_no_resource": 7568000,
|
||||
"scripture": 50,
|
||||
"film": 95,
|
||||
"growing_unreached": 5,
|
||||
"deaths_day": 10.9,
|
||||
"growth": 3.24,
|
||||
"pct_no_resource": 48.8,
|
||||
"pct_no_resource": 41.4,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -479,14 +479,14 @@
|
|||
"country": "United Republic of Tanzania",
|
||||
"region": "Africa",
|
||||
"groups": 132,
|
||||
"no_resource": 22,
|
||||
"pop_no_resource": 4760400,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 3289400,
|
||||
"scripture": 81,
|
||||
"film": 101,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 126.9,
|
||||
"growth": 2.87,
|
||||
"pct_no_resource": 16.7,
|
||||
"pct_no_resource": 13.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -509,14 +509,14 @@
|
|||
"country": "Benin",
|
||||
"region": "Africa",
|
||||
"groups": 57,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 500000,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 277000,
|
||||
"scripture": 29,
|
||||
"film": 38,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 21.9,
|
||||
"growth": 2.46,
|
||||
"pct_no_resource": 7,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -524,14 +524,14 @@
|
|||
"country": "Libya",
|
||||
"region": "Africa",
|
||||
"groups": 27,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 329300,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 317800,
|
||||
"scripture": 16,
|
||||
"film": 20,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 0,
|
||||
"growth": 1.03,
|
||||
"pct_no_resource": 22.2,
|
||||
"pct_no_resource": 18.5,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -569,15 +569,15 @@
|
|||
"country": "Comoros",
|
||||
"region": "Africa",
|
||||
"groups": 11,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 445500,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 45500,
|
||||
"scripture": 6,
|
||||
"film": 6,
|
||||
"growing_unreached": 3,
|
||||
"deaths_day": 8.3,
|
||||
"growth": 1.89,
|
||||
"pct_no_resource": 36.4,
|
||||
"band": "high"
|
||||
"pct_no_resource": 27.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "BRN",
|
||||
|
|
@ -629,14 +629,14 @@
|
|||
"country": "Mozambique",
|
||||
"region": "Africa",
|
||||
"groups": 49,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 2882000,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 1032000,
|
||||
"scripture": 31,
|
||||
"film": 41,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 26.4,
|
||||
"growth": 2.92,
|
||||
"pct_no_resource": 12.2,
|
||||
"pct_no_resource": 8.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -659,14 +659,14 @@
|
|||
"country": "Uganda",
|
||||
"region": "Africa",
|
||||
"groups": 54,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 2970200,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 460200,
|
||||
"scripture": 38,
|
||||
"film": 46,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 23,
|
||||
"growth": 2.75,
|
||||
"pct_no_resource": 11.1,
|
||||
"pct_no_resource": 7.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -674,14 +674,14 @@
|
|||
"country": "Peru",
|
||||
"region": "Americas",
|
||||
"groups": 90,
|
||||
"no_resource": 12,
|
||||
"pop_no_resource": 1160875,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 660875,
|
||||
"scripture": 73,
|
||||
"film": 57,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 5.4,
|
||||
"growth": 1.09,
|
||||
"pct_no_resource": 13.3,
|
||||
"pct_no_resource": 12.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -704,14 +704,14 @@
|
|||
"country": "Niger",
|
||||
"region": "Africa",
|
||||
"groups": 25,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 1153500,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 1138500,
|
||||
"scripture": 16,
|
||||
"film": 18,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 2.1,
|
||||
"growth": 3.28,
|
||||
"pct_no_resource": 20,
|
||||
"pct_no_resource": 16,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -749,14 +749,14 @@
|
|||
"country": "Sweden",
|
||||
"region": "Europe",
|
||||
"groups": 32,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 65500,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 62500,
|
||||
"scripture": 26,
|
||||
"film": 26,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 1.1,
|
||||
"growth": 0.31,
|
||||
"pct_no_resource": 18.8,
|
||||
"pct_no_resource": 15.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -764,14 +764,14 @@
|
|||
"country": "Republic of The Gambia",
|
||||
"region": "Africa",
|
||||
"groups": 17,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 430000,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 35000,
|
||||
"scripture": 6,
|
||||
"film": 13,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 0.6,
|
||||
"growth": 2.28,
|
||||
"pct_no_resource": 23.5,
|
||||
"pct_no_resource": 17.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -779,14 +779,14 @@
|
|||
"country": "Republic of the Congo",
|
||||
"region": "Africa",
|
||||
"groups": 64,
|
||||
"no_resource": 30,
|
||||
"pop_no_resource": 1344200,
|
||||
"no_resource": 27,
|
||||
"pop_no_resource": 907200,
|
||||
"scripture": 16,
|
||||
"film": 27,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 1,
|
||||
"growth": 2.4,
|
||||
"pct_no_resource": 46.9,
|
||||
"pct_no_resource": 42.2,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -959,14 +959,14 @@
|
|||
"country": "Cote d'Ivoire",
|
||||
"region": "Africa",
|
||||
"groups": 83,
|
||||
"no_resource": 21,
|
||||
"pop_no_resource": 1167500,
|
||||
"no_resource": 19,
|
||||
"pop_no_resource": 541500,
|
||||
"scripture": 44,
|
||||
"film": 53,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 3.7,
|
||||
"growth": 2.44,
|
||||
"pct_no_resource": 25.3,
|
||||
"pct_no_resource": 22.9,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -1094,14 +1094,14 @@
|
|||
"country": "Central African Republic",
|
||||
"region": "Africa",
|
||||
"groups": 69,
|
||||
"no_resource": 34,
|
||||
"pop_no_resource": 1496250,
|
||||
"no_resource": 31,
|
||||
"pop_no_resource": 1025250,
|
||||
"scripture": 16,
|
||||
"film": 28,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 0.6,
|
||||
"growth": 3.4,
|
||||
"pct_no_resource": 49.3,
|
||||
"pct_no_resource": 44.9,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -1574,15 +1574,15 @@
|
|||
"country": "Liechtenstein",
|
||||
"region": "Europe",
|
||||
"groups": 4,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 36400,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 400,
|
||||
"scripture": 2,
|
||||
"film": 2,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 0,
|
||||
"growth": 1.5,
|
||||
"pct_no_resource": 50,
|
||||
"band": "high"
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "FRO",
|
||||
|
|
@ -2009,14 +2009,14 @@
|
|||
"country": "Namibia",
|
||||
"region": "Africa",
|
||||
"groups": 29,
|
||||
"no_resource": 7,
|
||||
"pop_no_resource": 992225,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 113225,
|
||||
"scripture": 4,
|
||||
"film": 19,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 3.3,
|
||||
"growth": 2.24,
|
||||
"pct_no_resource": 24.1,
|
||||
"pct_no_resource": 17.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2054,14 +2054,14 @@
|
|||
"country": "Türkiye",
|
||||
"region": "Asia",
|
||||
"groups": 40,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 1175000,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 855000,
|
||||
"scripture": 35,
|
||||
"film": 37,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 3.7,
|
||||
"growth": 0.23,
|
||||
"pct_no_resource": 5,
|
||||
"pct_no_resource": 2.5,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2144,14 +2144,14 @@
|
|||
"country": "Philippines",
|
||||
"region": "Asia",
|
||||
"groups": 156,
|
||||
"no_resource": 28,
|
||||
"pop_no_resource": 4727275,
|
||||
"no_resource": 26,
|
||||
"pop_no_resource": 3398275,
|
||||
"scripture": 108,
|
||||
"film": 90,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 0.2,
|
||||
"growth": 0.83,
|
||||
"pct_no_resource": 17.9,
|
||||
"pct_no_resource": 16.7,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2264,14 +2264,14 @@
|
|||
"country": "United States of America",
|
||||
"region": "Americas",
|
||||
"groups": 623,
|
||||
"no_resource": 41,
|
||||
"pop_no_resource": 465640,
|
||||
"no_resource": 38,
|
||||
"pop_no_resource": 462510,
|
||||
"scripture": 517,
|
||||
"film": 549,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 0.1,
|
||||
"growth": 0.96,
|
||||
"pct_no_resource": 6.6,
|
||||
"pct_no_resource": 6.1,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2772,14 +2772,14 @@
|
|||
"country": "Singapore",
|
||||
"region": "Asia",
|
||||
"groups": 38,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 237500,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 218500,
|
||||
"scripture": 33,
|
||||
"film": 35,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 7.9,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2836,14 +2836,14 @@
|
|||
"country": "Ghana",
|
||||
"region": "Africa",
|
||||
"groups": 96,
|
||||
"no_resource": 15,
|
||||
"pop_no_resource": 2402300,
|
||||
"no_resource": 14,
|
||||
"pop_no_resource": 637300,
|
||||
"scripture": 71,
|
||||
"film": 70,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 15.6,
|
||||
"pct_no_resource": 14.6,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2868,14 +2868,14 @@
|
|||
"country": "Canada",
|
||||
"region": "Americas",
|
||||
"groups": 304,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 183000,
|
||||
"no_resource": 16,
|
||||
"pop_no_resource": 157400,
|
||||
"scripture": 256,
|
||||
"film": 270,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 5.9,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2900,14 +2900,14 @@
|
|||
"country": "Switzerland",
|
||||
"region": "Europe",
|
||||
"groups": 13,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 5612500,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 92500,
|
||||
"scripture": 11,
|
||||
"film": 11,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 15.4,
|
||||
"pct_no_resource": 7.7,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3140,15 +3140,15 @@
|
|||
"country": "Trinidad and Tobago",
|
||||
"region": "Americas",
|
||||
"groups": 11,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 388500,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 176000,
|
||||
"scripture": 7,
|
||||
"film": 6,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 36.4,
|
||||
"band": "high",
|
||||
"pct_no_resource": 18.2,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
{
|
||||
|
|
@ -3156,14 +3156,14 @@
|
|||
"country": "Timor-Leste",
|
||||
"region": "Asia",
|
||||
"groups": 17,
|
||||
"no_resource": 13,
|
||||
"pop_no_resource": 704500,
|
||||
"no_resource": 12,
|
||||
"pop_no_resource": 448500,
|
||||
"scripture": 2,
|
||||
"film": 1,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 76.5,
|
||||
"pct_no_resource": 70.6,
|
||||
"band": "critical",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3252,14 +3252,14 @@
|
|||
"country": "Italy",
|
||||
"region": "Europe",
|
||||
"groups": 38,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 872000,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 600000,
|
||||
"scripture": 30,
|
||||
"film": 36,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 5.3,
|
||||
"pct_no_resource": 2.6,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3364,15 +3364,15 @@
|
|||
"country": "Ukraine",
|
||||
"region": "Europe",
|
||||
"groups": 10,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 334000,
|
||||
"no_resource": 0,
|
||||
"pop_no_resource": 0,
|
||||
"scripture": 8,
|
||||
"film": 9,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 10,
|
||||
"band": "partial",
|
||||
"pct_no_resource": 0,
|
||||
"band": "covered",
|
||||
"from_pins_only": true
|
||||
},
|
||||
{
|
||||
|
|
@ -3412,14 +3412,14 @@
|
|||
"country": "Poland",
|
||||
"region": "Europe",
|
||||
"groups": 9,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 394500,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 384000,
|
||||
"scripture": 7,
|
||||
"film": 7,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 22.2,
|
||||
"pct_no_resource": 11.1,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"meta": {
|
||||
"title": "Gospel resource link verification",
|
||||
"generated": "2026-09-24T17:38:08.638Z",
|
||||
"generated": "2026-09-24T18:33:00.458Z",
|
||||
"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": 7491,
|
||||
"checkedThisPass": 921,
|
||||
"records": 7565,
|
||||
"checkedThisPass": 74,
|
||||
"carriedForward": 74,
|
||||
"pruned": 0,
|
||||
"broken": 74,
|
||||
"verified": 7412,
|
||||
"verified": 7486,
|
||||
"unknown": 5
|
||||
},
|
||||
"byHost": {
|
||||
|
|
@ -130,6 +130,9 @@
|
|||
},
|
||||
"scriptureearth.org": {
|
||||
"verified": 921
|
||||
},
|
||||
"fivefish.org": {
|
||||
"verified": 74
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -37588,6 +37591,376 @@
|
|||
"state": "verified",
|
||||
"detail": "HTTP 200",
|
||||
"at": "2026-09-24T17:38:08.356Z"
|
||||
},
|
||||
"https://fivefish.org/cpx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/421?language=Chinese%2C%20Puxian)",
|
||||
"at": "2026-09-24T18:32:54.859Z"
|
||||
},
|
||||
"https://fivefish.org/dio": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/9250?language=Dibo)",
|
||||
"at": "2026-09-24T18:32:54.880Z"
|
||||
},
|
||||
"https://fivefish.org/xmc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13359?language=eMarrevoni)",
|
||||
"at": "2026-09-24T18:32:54.901Z"
|
||||
},
|
||||
"https://fivefish.org/buf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1281?language=Bushongo)",
|
||||
"at": "2026-09-24T18:32:54.928Z"
|
||||
},
|
||||
"https://fivefish.org/njx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2864?language=Kikouni)",
|
||||
"at": "2026-09-24T18:32:54.954Z"
|
||||
},
|
||||
"https://fivefish.org/lej": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1526?language=Kilengola)",
|
||||
"at": "2026-09-24T18:32:54.967Z"
|
||||
},
|
||||
"https://fivefish.org/aba": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2554?language=Abe)",
|
||||
"at": "2026-09-24T18:32:55.077Z"
|
||||
},
|
||||
"https://fivefish.org/gew": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2003?language=Gyere)",
|
||||
"at": "2026-09-24T18:32:55.144Z"
|
||||
},
|
||||
"https://fivefish.org/sgd": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/279?language=Surigaonon)",
|
||||
"at": "2026-09-24T18:32:55.424Z"
|
||||
},
|
||||
"https://fivefish.org/nkw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/94?language=Lonkutu)",
|
||||
"at": "2026-09-24T18:32:55.496Z"
|
||||
},
|
||||
"https://fivefish.org/brf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/303?language=Kibira)",
|
||||
"at": "2026-09-24T18:32:55.538Z"
|
||||
},
|
||||
"https://fivefish.org/hmg": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6168?language=Miao%2C%20Shai%20Lou%20Zhenningxian)",
|
||||
"at": "2026-09-24T18:32:55.554Z"
|
||||
},
|
||||
"https://fivefish.org/koo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1101?language=Konzo)",
|
||||
"at": "2026-09-24T18:32:55.570Z"
|
||||
},
|
||||
"https://fivefish.org/bar": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3202?language=Austrian%3A%20Tyrolean)",
|
||||
"at": "2026-09-24T18:32:55.595Z"
|
||||
},
|
||||
"https://fivefish.org/ish": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2202?language=Ishan)",
|
||||
"at": "2026-09-24T18:32:55.709Z"
|
||||
},
|
||||
"https://fivefish.org/bli": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1856?language=Lolia)",
|
||||
"at": "2026-09-24T18:32:55.743Z"
|
||||
},
|
||||
"https://fivefish.org/bgx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/7617?language=Balkan%20Gagauz%20Turkish%3A%20Yuruk)",
|
||||
"at": "2026-09-24T18:32:55.992Z"
|
||||
},
|
||||
"https://fivefish.org/ckl": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1777?language=Chibuku)",
|
||||
"at": "2026-09-24T18:32:56.011Z"
|
||||
},
|
||||
"https://fivefish.org/slx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1275?language=Salampasu)",
|
||||
"at": "2026-09-24T18:32:56.047Z"
|
||||
},
|
||||
"https://fivefish.org/eto": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/9663?language=Eton)",
|
||||
"at": "2026-09-24T18:32:56.176Z"
|
||||
},
|
||||
"https://fivefish.org/mgm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4612?language=Mambae)",
|
||||
"at": "2026-09-24T18:32:56.190Z"
|
||||
},
|
||||
"https://fivefish.org/qxp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/15876?language=Quechua%2C%20Puno)",
|
||||
"at": "2026-09-24T18:32:56.211Z"
|
||||
},
|
||||
"https://fivefish.org/bta": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/7843?language=Bata)",
|
||||
"at": "2026-09-24T18:32:56.297Z"
|
||||
},
|
||||
"https://fivefish.org/smm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/14441?language=Musasa)",
|
||||
"at": "2026-09-24T18:32:56.422Z"
|
||||
},
|
||||
"https://fivefish.org/msi": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13428?language=Malay%2C%20Sabah)",
|
||||
"at": "2026-09-24T18:32:56.569Z"
|
||||
},
|
||||
"https://fivefish.org/bpj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1278?language=Bindji)",
|
||||
"at": "2026-09-24T18:32:56.636Z"
|
||||
},
|
||||
"https://fivefish.org/kqk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4888?language=Kotafon)",
|
||||
"at": "2026-09-24T18:32:56.656Z"
|
||||
},
|
||||
"https://fivefish.org/ttj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/984?language=Tooro)",
|
||||
"at": "2026-09-24T18:32:56.747Z"
|
||||
},
|
||||
"https://fivefish.org/khc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/17734?language=Tukangbesi%20North%3A%20Wanci)",
|
||||
"at": "2026-09-24T18:32:56.759Z"
|
||||
},
|
||||
"https://fivefish.org/bnx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1353?language=Kibangubangu)",
|
||||
"at": "2026-09-24T18:32:56.906Z"
|
||||
},
|
||||
"https://fivefish.org/suj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4528?language=Kisubi)",
|
||||
"at": "2026-09-24T18:32:56.929Z"
|
||||
},
|
||||
"https://fivefish.org/mnp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/507?language=Hockchia)",
|
||||
"at": "2026-09-24T18:32:56.958Z"
|
||||
},
|
||||
"https://fivefish.org/kel": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1309?language=Ikela)",
|
||||
"at": "2026-09-24T18:32:57.128Z"
|
||||
},
|
||||
"https://fivefish.org/czh": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6237?language=Chinese%2C%20Huizhou)",
|
||||
"at": "2026-09-24T18:32:57.237Z"
|
||||
},
|
||||
"https://fivefish.org/rue": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4289?language=Ruthenian)",
|
||||
"at": "2026-09-24T18:32:57.264Z"
|
||||
},
|
||||
"https://fivefish.org/nbm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1384?language=Ngbaka%20Mabo)",
|
||||
"at": "2026-09-24T18:32:57.295Z"
|
||||
},
|
||||
"https://fivefish.org/zeh": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/5123?language=Zhuang%3A%20Guigang%20Guiping)",
|
||||
"at": "2026-09-24T18:32:57.316Z"
|
||||
},
|
||||
"https://fivefish.org/bqv": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4543?language=Koro%20Wachi)",
|
||||
"at": "2026-09-24T18:32:57.639Z"
|
||||
},
|
||||
"https://fivefish.org/abr": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6021?language=Abron)",
|
||||
"at": "2026-09-24T18:32:57.687Z"
|
||||
},
|
||||
"https://fivefish.org/nto": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1858?language=Ntomba)",
|
||||
"at": "2026-09-24T18:32:57.724Z"
|
||||
},
|
||||
"https://fivefish.org/rkm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3068?language=Maraka)",
|
||||
"at": "2026-09-24T18:32:57.732Z"
|
||||
},
|
||||
"https://fivefish.org/shc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1593?language=Kilua-Kisonde)",
|
||||
"at": "2026-09-24T18:32:57.806Z"
|
||||
},
|
||||
"https://fivefish.org/trf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3495?language=Trinidadian%20English%20Creole)",
|
||||
"at": "2026-09-24T18:32:57.878Z"
|
||||
},
|
||||
"https://fivefish.org/daw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/283?language=Davaweno)",
|
||||
"at": "2026-09-24T18:32:57.965Z"
|
||||
},
|
||||
"https://fivefish.org/mde": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2971?language=Maba)",
|
||||
"at": "2026-09-24T18:32:58.215Z"
|
||||
},
|
||||
"https://fivefish.org/sip": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/678?language=Sikkimese)",
|
||||
"at": "2026-09-24T18:32:58.276Z"
|
||||
},
|
||||
"https://fivefish.org/tek": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4529?language=Kiteke)",
|
||||
"at": "2026-09-24T18:32:58.311Z"
|
||||
},
|
||||
"https://fivefish.org/wof": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4592?language=Wolof%2C%20Gambian)",
|
||||
"at": "2026-09-24T18:32:58.405Z"
|
||||
},
|
||||
"https://fivefish.org/jya": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/11143?language=Jiarong)",
|
||||
"at": "2026-09-24T18:32:58.428Z"
|
||||
},
|
||||
"https://fivefish.org/ndo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1952?language=Ndonga)",
|
||||
"at": "2026-09-24T18:32:58.476Z"
|
||||
},
|
||||
"https://fivefish.org/xac": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/619?language=Bodo%3A%20Kachari)",
|
||||
"at": "2026-09-24T18:32:58.491Z"
|
||||
},
|
||||
"https://fivefish.org/rwk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1065?language=Rwa)",
|
||||
"at": "2026-09-24T18:32:58.808Z"
|
||||
},
|
||||
"https://fivefish.org/bbp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1742?language=Banda)",
|
||||
"at": "2026-09-24T18:32:58.818Z"
|
||||
},
|
||||
"https://fivefish.org/gsw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2909?language=German%2C%20Swiss%3A%20Basel)",
|
||||
"at": "2026-09-24T18:32:58.853Z"
|
||||
},
|
||||
"https://fivefish.org/bjo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1495?language=Banda%2C%20Mid-Southern%3A%20Yakpa)",
|
||||
"at": "2026-09-24T18:32:58.951Z"
|
||||
},
|
||||
"https://fivefish.org/zag": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2939?language=Zaghawa)",
|
||||
"at": "2026-09-24T18:32:59.067Z"
|
||||
},
|
||||
"https://fivefish.org/dse": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19011?language=Sign%20Language%20of%20the%20Netherlands)",
|
||||
"at": "2026-09-24T18:32:59.114Z"
|
||||
},
|
||||
"https://fivefish.org/yix": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4983?language=Axi)",
|
||||
"at": "2026-09-24T18:32:59.196Z"
|
||||
},
|
||||
"https://fivefish.org/pht": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1711?language=Phu%20Thai)",
|
||||
"at": "2026-09-24T18:32:59.278Z"
|
||||
},
|
||||
"https://fivefish.org/sjo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4943?language=Xibe)",
|
||||
"at": "2026-09-24T18:32:59.300Z"
|
||||
},
|
||||
"https://fivefish.org/fsl": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19026?language=French%20Sign%20Language)",
|
||||
"at": "2026-09-24T18:32:59.368Z"
|
||||
},
|
||||
"https://fivefish.org/kfa": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4078?language=Coorgi)",
|
||||
"at": "2026-09-24T18:32:59.516Z"
|
||||
},
|
||||
"https://fivefish.org/bdm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1771?language=Buduma)",
|
||||
"at": "2026-09-24T18:32:59.578Z"
|
||||
},
|
||||
"https://fivefish.org/tma": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3511?language=Tama)",
|
||||
"at": "2026-09-24T18:32:59.632Z"
|
||||
},
|
||||
"https://fivefish.org/avd": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6913?language=Alviri)",
|
||||
"at": "2026-09-24T18:32:59.666Z"
|
||||
},
|
||||
"https://fivefish.org/gow": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1028?language=Fiome)",
|
||||
"at": "2026-09-24T18:32:59.818Z"
|
||||
},
|
||||
"https://fivefish.org/tyr": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/17053?language=Tai%20Daeng)",
|
||||
"at": "2026-09-24T18:32:59.831Z"
|
||||
},
|
||||
"https://fivefish.org/twm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/14200?language=Monpa%2C%20Tawang)",
|
||||
"at": "2026-09-24T18:32:59.946Z"
|
||||
},
|
||||
"https://fivefish.org/wni": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6031?language=Shindzwani)",
|
||||
"at": "2026-09-24T18:32:59.994Z"
|
||||
},
|
||||
"https://fivefish.org/mne": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2966?language=Naba%3A%20Kouka)",
|
||||
"at": "2026-09-24T18:33:00.059Z"
|
||||
},
|
||||
"https://fivefish.org/hmb": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4849?language=Songhay%2C%20Humburi%20Senni%3A%20Maranse)",
|
||||
"at": "2026-09-24T18:33:00.068Z"
|
||||
},
|
||||
"https://fivefish.org/rui": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1067?language=Rufiji)",
|
||||
"at": "2026-09-24T18:33:00.101Z"
|
||||
},
|
||||
"https://fivefish.org/pks": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19209?language=Pakistan%20Sign%20Language)",
|
||||
"at": "2026-09-24T18:33:00.233Z"
|
||||
},
|
||||
"https://fivefish.org/vmk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13367?language=Exirima)",
|
||||
"at": "2026-09-24T18:33:00.244Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
413
data/tables/grn-index.json
Normal file
413
data/tables/grn-index.json
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
{
|
||||
"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,
|
||||
"byIso": {
|
||||
"mnp": {
|
||||
"u": "https://fivefish.org/mnp",
|
||||
"n": "Hockchia"
|
||||
},
|
||||
"pks": {
|
||||
"u": "https://fivefish.org/pks",
|
||||
"n": "Pakistan Sign Language"
|
||||
},
|
||||
"gsw": {
|
||||
"u": "https://fivefish.org/gsw",
|
||||
"n": "German, Swiss: Basel"
|
||||
},
|
||||
"czh": {
|
||||
"u": "https://fivefish.org/czh",
|
||||
"n": "Chinese, Huizhou"
|
||||
},
|
||||
"cpx": {
|
||||
"u": "https://fivefish.org/cpx",
|
||||
"n": "Chinese, Puxian"
|
||||
},
|
||||
"abr": {
|
||||
"u": "https://fivefish.org/abr",
|
||||
"n": "Abron"
|
||||
},
|
||||
"koo": {
|
||||
"u": "https://fivefish.org/koo",
|
||||
"n": "Konzo"
|
||||
},
|
||||
"ttj": {
|
||||
"u": "https://fivefish.org/ttj",
|
||||
"n": "Tooro"
|
||||
},
|
||||
"zeh": {
|
||||
"u": "https://fivefish.org/zeh",
|
||||
"n": "Zhuang: Guigang Guiping"
|
||||
},
|
||||
"sgd": {
|
||||
"u": "https://fivefish.org/sgd",
|
||||
"n": "Cantilangnon"
|
||||
},
|
||||
"fsl": {
|
||||
"u": "https://fivefish.org/fsl",
|
||||
"n": "French Sign Language"
|
||||
},
|
||||
"vmk": {
|
||||
"u": "https://fivefish.org/vmk",
|
||||
"n": "Exirima"
|
||||
},
|
||||
"kel": {
|
||||
"u": "https://fivefish.org/kel",
|
||||
"n": "Ikela"
|
||||
},
|
||||
"ndo": {
|
||||
"u": "https://fivefish.org/ndo",
|
||||
"n": "Ndonga"
|
||||
},
|
||||
"pht": {
|
||||
"u": "https://fivefish.org/pht",
|
||||
"n": "Phu Thai"
|
||||
},
|
||||
"xmc": {
|
||||
"u": "https://fivefish.org/xmc",
|
||||
"n": "eMarrevoni"
|
||||
},
|
||||
"mne": {
|
||||
"u": "https://fivefish.org/mne",
|
||||
"n": "Naba: Kouka"
|
||||
},
|
||||
"ish": {
|
||||
"u": "https://fivefish.org/ish",
|
||||
"n": "Ishan"
|
||||
},
|
||||
"msi": {
|
||||
"u": "https://fivefish.org/msi",
|
||||
"n": "Malay, Sabah"
|
||||
},
|
||||
"mde": {
|
||||
"u": "https://fivefish.org/mde",
|
||||
"n": "Maba"
|
||||
},
|
||||
"rui": {
|
||||
"u": "https://fivefish.org/rui",
|
||||
"n": "Rufiji"
|
||||
},
|
||||
"rkm": {
|
||||
"u": "https://fivefish.org/rkm",
|
||||
"n": "Maraka"
|
||||
},
|
||||
"zag": {
|
||||
"u": "https://fivefish.org/zag",
|
||||
"n": "Zaghawa"
|
||||
},
|
||||
"qxp": {
|
||||
"u": "https://fivefish.org/qxp",
|
||||
"n": "Quechua, Puno"
|
||||
},
|
||||
"gew": {
|
||||
"u": "https://fivefish.org/gew",
|
||||
"n": "Gyere"
|
||||
},
|
||||
"suj": {
|
||||
"u": "https://fivefish.org/suj",
|
||||
"n": "Kisubi"
|
||||
},
|
||||
"eto": {
|
||||
"u": "https://fivefish.org/eto",
|
||||
"n": "Eton"
|
||||
},
|
||||
"tek": {
|
||||
"u": "https://fivefish.org/tek",
|
||||
"n": "Kiteke"
|
||||
},
|
||||
"nto": {
|
||||
"u": "https://fivefish.org/nto",
|
||||
"n": "Ntomba"
|
||||
},
|
||||
"wni": {
|
||||
"u": "https://fivefish.org/wni",
|
||||
"n": "Shindzwani"
|
||||
},
|
||||
"nbm": {
|
||||
"u": "https://fivefish.org/nbm",
|
||||
"n": "Ngbaka Mabo"
|
||||
},
|
||||
"hmg": {
|
||||
"u": "https://fivefish.org/hmg",
|
||||
"n": "Miao, Shai Lou Zhenningxian"
|
||||
},
|
||||
"wof": {
|
||||
"u": "https://fivefish.org/wof",
|
||||
"n": "Wolof, Gambian"
|
||||
},
|
||||
"bnx": {
|
||||
"u": "https://fivefish.org/bnx",
|
||||
"n": "Kibangubangu"
|
||||
},
|
||||
"bpj": {
|
||||
"u": "https://fivefish.org/bpj",
|
||||
"n": "Bindji"
|
||||
},
|
||||
"bbp": {
|
||||
"u": "https://fivefish.org/bbp",
|
||||
"n": "Banda"
|
||||
},
|
||||
"bta": {
|
||||
"u": "https://fivefish.org/bta",
|
||||
"n": "Bata"
|
||||
},
|
||||
"aba": {
|
||||
"u": "https://fivefish.org/aba",
|
||||
"n": "Abbey"
|
||||
},
|
||||
"buf": {
|
||||
"u": "https://fivefish.org/buf",
|
||||
"n": "Bushongo"
|
||||
},
|
||||
"jya": {
|
||||
"u": "https://fivefish.org/jya",
|
||||
"n": "Jiarong"
|
||||
},
|
||||
"rue": {
|
||||
"u": "https://fivefish.org/rue",
|
||||
"n": "Ruthenian"
|
||||
},
|
||||
"brf": {
|
||||
"u": "https://fivefish.org/brf",
|
||||
"n": "Kibira"
|
||||
},
|
||||
"bgx": {
|
||||
"u": "https://fivefish.org/bgx",
|
||||
"n": "Balkan Gagauz Turkish: Yuruk"
|
||||
},
|
||||
"tma": {
|
||||
"u": "https://fivefish.org/tma",
|
||||
"n": "Tama"
|
||||
},
|
||||
"bar": {
|
||||
"u": "https://fivefish.org/bar",
|
||||
"n": "Austrian: Tyrolean"
|
||||
},
|
||||
"sip": {
|
||||
"u": "https://fivefish.org/sip",
|
||||
"n": "Sikkimese"
|
||||
},
|
||||
"rwk": {
|
||||
"u": "https://fivefish.org/rwk",
|
||||
"n": "Rwa"
|
||||
},
|
||||
"slx": {
|
||||
"u": "https://fivefish.org/slx",
|
||||
"n": "Salampasu"
|
||||
},
|
||||
"hmb": {
|
||||
"u": "https://fivefish.org/hmb",
|
||||
"n": "Songhay, Humburi Senni: Maranse"
|
||||
},
|
||||
"dio": {
|
||||
"u": "https://fivefish.org/dio",
|
||||
"n": "Dibo"
|
||||
},
|
||||
"mgm": {
|
||||
"u": "https://fivefish.org/mgm",
|
||||
"n": "Mambae"
|
||||
},
|
||||
"lej": {
|
||||
"u": "https://fivefish.org/lej",
|
||||
"n": "Kilengola"
|
||||
},
|
||||
"bli": {
|
||||
"u": "https://fivefish.org/bli",
|
||||
"n": "Lolia"
|
||||
},
|
||||
"ckl": {
|
||||
"u": "https://fivefish.org/ckl",
|
||||
"n": "Chibuku"
|
||||
},
|
||||
"nkw": {
|
||||
"u": "https://fivefish.org/nkw",
|
||||
"n": "Lonkutu"
|
||||
},
|
||||
"tyr": {
|
||||
"u": "https://fivefish.org/tyr",
|
||||
"n": "Tai Daeng"
|
||||
},
|
||||
"smm": {
|
||||
"u": "https://fivefish.org/smm",
|
||||
"n": "Musasa"
|
||||
},
|
||||
"kqk": {
|
||||
"u": "https://fivefish.org/kqk",
|
||||
"n": "Kotafon"
|
||||
},
|
||||
"trf": {
|
||||
"u": "https://fivefish.org/trf",
|
||||
"n": "Trinidadian English Creole"
|
||||
},
|
||||
"shc": {
|
||||
"u": "https://fivefish.org/shc",
|
||||
"n": "Kilua-Kisonde"
|
||||
},
|
||||
"xac": {
|
||||
"u": "https://fivefish.org/xac",
|
||||
"n": "Bodo: Kachari"
|
||||
},
|
||||
"kfa": {
|
||||
"u": "https://fivefish.org/kfa",
|
||||
"n": "Coorgi"
|
||||
},
|
||||
"twm": {
|
||||
"u": "https://fivefish.org/twm",
|
||||
"n": "Monpa, Tawang"
|
||||
},
|
||||
"bjo": {
|
||||
"u": "https://fivefish.org/bjo",
|
||||
"n": "Banda, Mid-Southern: Yakpa"
|
||||
},
|
||||
"bdm": {
|
||||
"u": "https://fivefish.org/bdm",
|
||||
"n": "Buduma"
|
||||
},
|
||||
"daw": {
|
||||
"u": "https://fivefish.org/daw",
|
||||
"n": "Davawenyo"
|
||||
},
|
||||
"sjo": {
|
||||
"u": "https://fivefish.org/sjo",
|
||||
"n": "Xibe"
|
||||
},
|
||||
"gow": {
|
||||
"u": "https://fivefish.org/gow",
|
||||
"n": "Fiome"
|
||||
},
|
||||
"avd": {
|
||||
"u": "https://fivefish.org/avd",
|
||||
"n": "Alviri"
|
||||
},
|
||||
"dse": {
|
||||
"u": "https://fivefish.org/dse",
|
||||
"n": "Sign Language of the Netherlands"
|
||||
},
|
||||
"yix": {
|
||||
"u": "https://fivefish.org/yix",
|
||||
"n": "Axi"
|
||||
},
|
||||
"bqv": {
|
||||
"u": "https://fivefish.org/bqv",
|
||||
"n": "Koro Wachi"
|
||||
},
|
||||
"njx": {
|
||||
"u": "https://fivefish.org/njx",
|
||||
"n": "Kikouni"
|
||||
},
|
||||
"khc": {
|
||||
"u": "https://fivefish.org/khc",
|
||||
"n": "Tukangbesi North: Wanci"
|
||||
}
|
||||
},
|
||||
"none": [
|
||||
"cjy",
|
||||
"gan",
|
||||
"csl",
|
||||
"inl",
|
||||
"ayh",
|
||||
"mmr",
|
||||
"nsi",
|
||||
"zlj",
|
||||
"acx",
|
||||
"nos",
|
||||
"qcd",
|
||||
"zgb",
|
||||
"eth",
|
||||
"esl",
|
||||
"psp",
|
||||
"cps",
|
||||
"czo",
|
||||
"tsm",
|
||||
"hab",
|
||||
"abv",
|
||||
"gsg",
|
||||
"bfi",
|
||||
"now",
|
||||
"zmi",
|
||||
"thz",
|
||||
"auh",
|
||||
"tsq",
|
||||
"mfu",
|
||||
"fia",
|
||||
"ise",
|
||||
"sfs",
|
||||
"aju",
|
||||
"ssp",
|
||||
"mhm",
|
||||
"suw",
|
||||
"ysm",
|
||||
"bww",
|
||||
"asp",
|
||||
"vic",
|
||||
"aed",
|
||||
"qns",
|
||||
"han",
|
||||
"qiq",
|
||||
"mry",
|
||||
"kxd",
|
||||
"afg",
|
||||
"fmp",
|
||||
"bah",
|
||||
"ugn",
|
||||
"wxa",
|
||||
"phu",
|
||||
"pso",
|
||||
"xms",
|
||||
"mlm",
|
||||
"hmh",
|
||||
"zlq",
|
||||
"kdg",
|
||||
"sdl",
|
||||
"nsp",
|
||||
"shz",
|
||||
"diz",
|
||||
"sjg",
|
||||
"tza",
|
||||
"zhk",
|
||||
"prl",
|
||||
"gex",
|
||||
"xml",
|
||||
"noq",
|
||||
"ypg",
|
||||
"jbn",
|
||||
"mzc",
|
||||
"vsl",
|
||||
"qry",
|
||||
"gse",
|
||||
"ras",
|
||||
"mny",
|
||||
"soe",
|
||||
"hnm",
|
||||
"qkp",
|
||||
"amj",
|
||||
"btw",
|
||||
"ayc",
|
||||
"qbf",
|
||||
"bog",
|
||||
"xki",
|
||||
"cri",
|
||||
"sbm",
|
||||
"kiv",
|
||||
"tiz",
|
||||
"kwl",
|
||||
"grj",
|
||||
"gdq",
|
||||
"qsg",
|
||||
"qso",
|
||||
"lws",
|
||||
"bpd",
|
||||
"dec",
|
||||
"lap",
|
||||
"gsm",
|
||||
"cds",
|
||||
"tor",
|
||||
"csg",
|
||||
"dux",
|
||||
"ajz",
|
||||
"mdi",
|
||||
"psi"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -49,7 +49,7 @@ The scripts these commands are grounded in (from this branch's `package.json`):
|
|||
"build": "bash internal/build-lock.sh",
|
||||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source check:backlog-refs",
|
||||
"build:bundles": "node internal/run-parallel.mjs --bundles", // the 22 vite builds, 4 at a time
|
||||
"preview": "bun x vite preview",
|
||||
"serve": "node internal/serve-strict.mjs", // 404s a directory, like production
|
||||
|
|
|
|||
101
internal/check-backlog-refs.mjs
Normal file
101
internal/check-backlog-refs.mjs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-backlog-refs.mjs — a card may not point at a card that does not exist.
|
||||
*
|
||||
* THE DEFECT THIS CATCHES. `depends_on` is the board's readiness mechanism: a card is ready when
|
||||
* its dependencies are done. A dependency naming a card that is NOT IN THE BACKLOG can never be
|
||||
* done, so the card can never become ready — and nothing says so. It reads as ordinary blocked
|
||||
* work forever.
|
||||
*
|
||||
* MEASURED 2026-09-24, three dangling references across the builder backlog:
|
||||
* IDEA-SLIM-MAP-SEARCHBAR-HOW-DO-FILTERS-FIT <- BUILD-PEOPLE-GROUP-SEARCH-…depends_on
|
||||
* IDEA-REGIONS-DASHBOARD-REVERSE-ENGINEER-DXLIFE <- two regions cards' depends_on
|
||||
* PRINCIPLE-MAPS-ARE-SECONDARY-LISTS-AND-GRIDS-ARE-PRIMARY <- two cards' parent
|
||||
* The first one matters most: that card is status:todo and waiting on something unfindable.
|
||||
* The last is cited by the loop prompt itself as an open card ("PRINCIPLE-MA"), so a reader is
|
||||
* being pointed at work that has no record.
|
||||
*
|
||||
* ★ SEVERITY IS NOT UNIFORM, so this gate grades it. A dangling `depends_on` on an OPEN card is a
|
||||
* FAILURE — that card is unreachable. A dangling `parent`, or a dangling anything on a card that is
|
||||
* already done, is REPORTED but does not fail: losing a parent is a filing problem, not a blocked
|
||||
* one, and failing on it would only teach people to delete the reference rather than restore the
|
||||
* card. Naming the difference is the point.
|
||||
*
|
||||
* ⚠ IT ONLY CHECKS REFERENCES THAT LOOK LIKE CARD IDS. Fields like `parent` also legitimately hold
|
||||
* prose in some cards ("the regions epic"), and treating those as broken links would drown the
|
||||
* real ones. The shape is SCREAMING-KEBAB, 9+ chars — the convention every id in this board uses.
|
||||
*
|
||||
* Usage: node internal/check-backlog-refs.mjs
|
||||
* SKIPs when _qa-cycle is absent (it lives outside every repo, by design).
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const QA = process.env.QA_CYCLE || resolve(HERE, '..', '..', '_qa-cycle')
|
||||
if (!existsSync(QA)) {
|
||||
console.log('BACKLOG-REFS: SKIP — _qa-cycle not present (expected in CI; it is in no repo).')
|
||||
console.log(' This gate reads the AGENTIC BACKLOGS, which live outside the repo and are not')
|
||||
console.log(' cloned with it. Absence is expected; it is NOT a pass.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const PHASES = ['01-ideation-research', '02-builder-cycle', '03-deployment-cycle']
|
||||
const REF_FIELDS = ['parent', 'depends_on', 'blocks', 'supersedes', 'supersedes_mechanism_of']
|
||||
// The id convention every card in this board follows. Prose in a `parent` field must NOT match.
|
||||
const ID_SHAPE = /^[A-Z][A-Z0-9-]{8,}$/
|
||||
|
||||
const cards = []
|
||||
for (const phase of PHASES) {
|
||||
const p = join(QA, phase, 'backlog.json')
|
||||
if (!existsSync(p)) continue
|
||||
const j = JSON.parse(readFileSync(p, 'utf8'))
|
||||
for (const c of j.cards || []) cards.push({ ...c, _phase: phase })
|
||||
}
|
||||
if (!cards.length) {
|
||||
console.log('BACKLOG-REFS: SKIP — no cards found in any phase backlog.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const ids = new Set(cards.map((c) => c.id))
|
||||
const OPEN = new Set(['todo', 'doing', 'in-progress', 'blocked'])
|
||||
|
||||
const blocking = [] // dangling depends_on on an OPEN card — the card can never become ready
|
||||
const noted = [] // everything else dangling — filing debt, reported not failed
|
||||
|
||||
for (const c of cards) {
|
||||
const f = c.fields || {}
|
||||
for (const key of REF_FIELDS) {
|
||||
const raw = f[key] !== undefined ? f[key] : c[key]
|
||||
if (!raw) continue
|
||||
for (const ref of (Array.isArray(raw) ? raw : [raw])) {
|
||||
if (typeof ref !== 'string' || !ID_SHAPE.test(ref)) continue
|
||||
if (ids.has(ref)) continue
|
||||
const entry = { from: c.id, key, ref, status: c.status || '(none)' }
|
||||
if (key === 'depends_on' && OPEN.has(String(c.status || '').toLowerCase())) blocking.push(entry)
|
||||
else noted.push(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`BACKLOG-REFS: ${cards.length} card(s) across ${PHASES.length} backlog(s), `
|
||||
+ `${REF_FIELDS.length} reference field(s) checked.`)
|
||||
if (noted.length) {
|
||||
console.log(` ${noted.length} dangling reference(s) that do NOT block anything:`)
|
||||
for (const n of noted) console.log(` ${n.from} [${n.status}] .${n.key} -> ${n.ref}`)
|
||||
console.log(' Filing debt: a lost parent or a stale link on a finished card. Restore the card')
|
||||
console.log(' or drop the reference — but do not delete it just to quiet this line.')
|
||||
}
|
||||
|
||||
if (blocking.length) {
|
||||
console.error(`\nBACKLOG-REFS: FAIL — ${blocking.length} OPEN card(s) depend on a card that does not exist.`)
|
||||
for (const b of blocking) {
|
||||
console.error(` ✗ ${b.from} [${b.status}] depends_on ${b.ref}`)
|
||||
}
|
||||
console.error('\n A dependency that is not in the backlog can never be done, so these cards can')
|
||||
console.error(' never become ready — and nothing says so. They read as ordinary blocked work.')
|
||||
console.error(' Fix: restore the missing card, or remove the dependency if it is obsolete.')
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('BACKLOG-REFS: PASS — every open card\'s dependencies name a card that exists.')
|
||||
210
internal/check-embed-basemap-paints.mjs
Normal file
210
internal/check-embed-basemap-paints.mjs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
* check-embed-basemap-paints.mjs — the basemap really paints, in a real third-party iframe,
|
||||
* on the token the page actually ships with.
|
||||
*
|
||||
* WHY THIS EXISTS. Every browser probe in this repo forced a Mapbox token onto the page
|
||||
* (`?tk=`, or `window.MAP_TOKEN`) from a shared file. That token is `u=doxalife`, which is
|
||||
* restricted and 403s on the basemap and country boundaries. The leaf pages BAKE a different
|
||||
* token, `u=gosystems`, which works. So every probe measured a basemap that was broken only
|
||||
* because the probe broke it — and passed anyway, because it went on to count PINS, which are
|
||||
* drawn from our own GeoJSON and paint with no token at all.
|
||||
*
|
||||
* The consequence is that "the map renders" has never actually been measured. A pin count is
|
||||
* not a basemap. This check exists to make that claim measurable, and it deliberately does
|
||||
* NOT force a token: it loads the page exactly as a stranger's browser would.
|
||||
*
|
||||
* IT IS A REAL EMBED. The leaf is served on one port and the host page on another, so the
|
||||
* iframe is genuinely cross-origin — the same relationship a third-party site has to it.
|
||||
*
|
||||
* IT CAN FAIL, AND IS SHOWN TO. A check that cannot fail proves nothing, so every run also
|
||||
* loads the SAME page with the restricted token forced on, and requires that run to fail the
|
||||
* basemap test. If the negative control passes, the check reports that it proved nothing
|
||||
* rather than reporting success — a broken detector must never read as a green result.
|
||||
*
|
||||
* Evidence is taken two independent ways, because either alone has a failure mode:
|
||||
* 1. NETWORK — Mapbox tile/style responses, counted by status. 401/403 is decisive.
|
||||
* 2. PIXELS — colour variety in a slice of the canvas. A page can 200 on everything and
|
||||
* still paint nothing, and a screenshot is the only witness to that.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { writeFileSync, mkdtempSync, readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const REPO = resolve(HERE, '..')
|
||||
const PUBLIC = resolve(REPO, 'public')
|
||||
|
||||
const LEAF = 'doxa-maps/gospel-resources/resources-map/index.html'
|
||||
const leafPort = 5810 + Math.floor(Math.random() * 60)
|
||||
const hostPort = leafPort + 1
|
||||
|
||||
/** Mapbox endpoints that actually draw the map. Telemetry is not evidence of anything. */
|
||||
const isBasemap = (u) =>
|
||||
/api\.mapbox\.com\/(styles|v4|fonts|tilesets)|tiles\.mapbox\.com/.test(u) &&
|
||||
!/events\.mapbox\.com/.test(u)
|
||||
|
||||
function serve(dir, port) {
|
||||
return spawn(process.execPath, [resolve(REPO, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', dir], {
|
||||
stdio: 'ignore',
|
||||
})
|
||||
}
|
||||
|
||||
async function waitFor(url, ms = 20000) {
|
||||
const until = Date.now() + ms
|
||||
while (Date.now() < until) {
|
||||
if ((await fetch(url).catch(() => null))?.ok) return true
|
||||
await new Promise((r) => setTimeout(r, 250))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/* A third-party page: nothing but an iframe, served from another origin. */
|
||||
const hostDir = mkdtempSync(resolve(tmpdir(), 'gr-embed-'))
|
||||
writeFileSync(
|
||||
resolve(hostDir, 'index.html'),
|
||||
`<!doctype html><meta charset="utf-8"><title>third-party host</title>
|
||||
<body style="margin:0;background:#f3f3f1">
|
||||
<iframe id="f" src="http://127.0.0.1:${leafPort}/${LEAF}" style="width:1100px;height:760px;border:0"></iframe>
|
||||
</body>`,
|
||||
)
|
||||
|
||||
const leafSrv = serve(PUBLIC, leafPort)
|
||||
const hostSrv = serve(hostDir, hostPort)
|
||||
const stop = () => {
|
||||
try { leafSrv.kill() } catch {}
|
||||
try { hostSrv.kill() } catch {}
|
||||
}
|
||||
|
||||
if (!(await waitFor(`http://127.0.0.1:${leafPort}/${LEAF}`)) || !(await waitFor(`http://127.0.0.1:${hostPort}/index.html`))) {
|
||||
console.error('EMBED-BASEMAP: NOT MEASURED — a server did not come up.')
|
||||
stop()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({
|
||||
args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--no-sandbox'],
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {string|null} forceToken null = the page's OWN baked token (the real case).
|
||||
*/
|
||||
async function run(forceToken) {
|
||||
/* The init script MUST be installed on the CONTEXT, not the page: the leaf lives in a
|
||||
cross-origin iframe, and a page-level init script never reaches it. Installing it on the
|
||||
page made both runs use the baked token and the check correctly reported that it had
|
||||
proved nothing — which is the only reason this was caught rather than shipped as a pass. */
|
||||
const ctx = await browser.newContext({ viewport: { width: 1200, height: 820 } })
|
||||
const page = await ctx.newPage()
|
||||
if (forceToken) {
|
||||
/* THE CONTROL HAS TO WORK AT THE NETWORK LAYER. Setting window.MAP_TOKEN does nothing to
|
||||
this leaf: it injects `tk` straight into the element's profile-config, and the window
|
||||
global is only a fallback for leaves that do not. That is worth knowing on its own —
|
||||
this page is immune to the forced-token hazard by construction — but it means the only
|
||||
way to ask "what if the token were the restricted one" is to rewrite the token on the
|
||||
requests themselves. */
|
||||
await ctx.route(/api\.mapbox\.com|tiles\.mapbox\.com/, async (route) => {
|
||||
const u = new URL(route.request().url())
|
||||
if (u.searchParams.has('access_token')) u.searchParams.set('access_token', forceToken)
|
||||
await route.continue({ url: u.toString() })
|
||||
})
|
||||
}
|
||||
const net = { ok: 0, denied: 0, other: 0 }
|
||||
page.on('response', (r) => {
|
||||
if (!isBasemap(r.url())) return
|
||||
const s = r.status()
|
||||
if (s === 401 || s === 403) net.denied++
|
||||
else if (s >= 200 && s < 300) net.ok++
|
||||
else net.other++
|
||||
})
|
||||
await page.goto(`http://127.0.0.1:${hostPort}/index.html`, { waitUntil: 'networkidle', timeout: 60000 })
|
||||
await page.waitForTimeout(12000)
|
||||
|
||||
const frame = page.frames().find((f) => f.url().includes('resources-map'))
|
||||
if (!frame) return { net, pixels: null, reason: 'the iframe never loaded the leaf' }
|
||||
|
||||
/* A slice well away from the sheet and the controls, so we sample map, not chrome. */
|
||||
const seen = await frame.evaluate(() => (window.MAP_TOKEN || '').slice(0, 24) || '(none on window)')
|
||||
const shot = await page.screenshot({ clip: { x: 60, y: 180, width: 700, height: 420 } })
|
||||
const png = shot
|
||||
await page.close()
|
||||
await ctx.close()
|
||||
return { net, png, seen }
|
||||
}
|
||||
|
||||
const real = await run(null)
|
||||
const forcedTokenPath = '/tmp/mbtk'
|
||||
let restricted = ''
|
||||
try { restricted = readFileSync(forcedTokenPath, 'utf8').trim() } catch { /* control simply cannot run */ }
|
||||
const control = restricted ? await run(restricted) : null
|
||||
|
||||
/* Pixel variety, measured in-browser so no image library is needed. */
|
||||
const shotPage = await browser.newPage()
|
||||
async function variety(png) {
|
||||
if (!png) return 0
|
||||
return shotPage.evaluate(async (b64) => {
|
||||
const img = new Image()
|
||||
img.src = `data:image/png;base64,${b64}`
|
||||
await img.decode()
|
||||
const c = document.createElement('canvas')
|
||||
c.width = img.width
|
||||
c.height = img.height
|
||||
c.getContext('2d').drawImage(img, 0, 0)
|
||||
const d = c.getContext('2d').getImageData(0, 0, c.width, c.height).data
|
||||
const seen = new Set()
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
seen.add(((d[i] >> 3) << 10) | ((d[i + 1] >> 3) << 5) | (d[i + 2] >> 3))
|
||||
}
|
||||
return seen.size
|
||||
}, png.toString('base64'))
|
||||
}
|
||||
|
||||
const realColours = await variety(real.png)
|
||||
const ctlColours = control ? await variety(control.png) : null
|
||||
if (process.env.EMBED_SHOTS) {
|
||||
const { writeFileSync: wf } = await import('node:fs')
|
||||
if (real.png) wf(`${process.env.EMBED_SHOTS}/embed-baked.png`, real.png)
|
||||
if (control && control.png) wf(`${process.env.EMBED_SHOTS}/embed-restricted.png`, control.png)
|
||||
console.log(` shots written to ${process.env.EMBED_SHOTS}`)
|
||||
}
|
||||
await browser.close()
|
||||
stop()
|
||||
|
||||
/* A painted basemap has many colours; a blank ground has a handful. */
|
||||
const COLOUR_FLOOR = 40
|
||||
const realPaints = real.net.denied === 0 && real.net.ok > 0 && realColours >= COLOUR_FLOOR
|
||||
|
||||
console.log(`EMBED-BASEMAP: cross-origin iframe, leaf on :${leafPort} inside a host on :${hostPort}.`)
|
||||
console.log(` baked token tiles ${real.net.ok} ok / ${real.net.denied} denied · ${realColours} distinct colours`)
|
||||
if (control) {
|
||||
console.log(` restricted token tiles ${control.net.ok} ok / ${control.net.denied} denied · ${ctlColours} distinct colours`)
|
||||
console.log(` token the frame used: baked="${real.seen}" control="${control.seen}"`)
|
||||
}
|
||||
|
||||
if (!control) {
|
||||
console.error(`EMBED-BASEMAP: NOT MEASURED — ${forcedTokenPath} is absent, so the negative control could not run.`)
|
||||
console.error(' Without a control this check cannot be shown to fail, and a result it cannot')
|
||||
console.error(' fail on is not evidence. Refusing to report a pass.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const controlFails = control.net.denied > 0 || ctlColours < COLOUR_FLOOR
|
||||
if (!controlFails) {
|
||||
console.error('EMBED-BASEMAP: PROVED NOTHING — the restricted token painted a basemap too,')
|
||||
console.error(' so this check cannot tell a working token from a broken one. Fix the control')
|
||||
console.error(' before trusting any pass it reports.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!realPaints) {
|
||||
console.error('EMBED-BASEMAP: FAIL — the page does NOT paint a basemap on its own baked token')
|
||||
console.error(` in a third-party iframe (${real.net.denied} denied tile response(s), ${realColours} colours).`)
|
||||
console.error(' Pins can still draw here: they come from our GeoJSON and need no token, which is')
|
||||
console.error(' exactly why a pin count must never be read as "the map renders".')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('EMBED-BASEMAP: PASS — the basemap paints on the baked token in a third-party iframe,')
|
||||
console.log(' and the same page with the restricted token forced on fails, so the check discriminates.')
|
||||
|
|
@ -93,7 +93,7 @@ if (POISON) {
|
|||
}
|
||||
|
||||
const SPECIAL = (c) => c === 'und' || c === 'mul' || c === 'mis' || c === 'zxx' || (c >= 'qaa' && c <= 'qtz')
|
||||
const hasRes = (v) => !!(v && (v.s || v.f || v.au || v.bv || v.se || (v.d && v.d.length)))
|
||||
const hasRes = (v) => !!(v && (v.s || v.f || v.au || v.bv || v.se || v.ga || (v.d && v.d.length)))
|
||||
const fails = []
|
||||
const add = (clause, msg) => fails.push(` [${clause}] ${msg}`)
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ if (missing.length) add('D', `${missing.length} code(s) carry a resource but app
|
|||
// E — the table cannot invent a URL
|
||||
const known = new Set()
|
||||
for (const v of Object.values(codes)) {
|
||||
if (v.s) known.add(v.s); if (v.f) known.add(v.f); if (v.z) known.add(v.z); if (v.za) known.add(v.za.u); if (v.au) known.add(v.au.u); if (v.bv) known.add(v.bv.u); if (v.gp) known.add(v.gp.u); if (v.se) known.add(v.se.u)
|
||||
if (v.s) known.add(v.s); if (v.f) known.add(v.f); if (v.z) known.add(v.z); if (v.za) known.add(v.za.u); if (v.au) known.add(v.au.u); if (v.bv) known.add(v.bv.u); if (v.gp) known.add(v.gp.u); if (v.se) known.add(v.se.u); if (v.ga) known.add(v.ga.u)
|
||||
for (const d of v.d || []) known.add(d.u)
|
||||
}
|
||||
const invented = trows.filter((t) => t.resource_url && !known.has(t.resource_url))
|
||||
|
|
@ -164,7 +164,7 @@ if (misTyped.length) add('H', `${misTyped.length} row(s) named "Open Bible Stori
|
|||
// G — no plain-http URL
|
||||
const httpUrls = []
|
||||
for (const [c, v] of Object.entries(codes)) {
|
||||
for (const u of [v.s, v.f, v.z, v.za && v.za.u, v.au && v.au.u, v.bv && v.bv.u, v.gp && v.gp.u, v.se && v.se.u, ...(v.d || []).map((d) => d.u)]) {
|
||||
for (const u of [v.s, v.f, v.z, v.za && v.za.u, v.au && v.au.u, v.bv && v.bv.u, v.gp && v.gp.u, v.se && v.se.u, v.ga && v.ga.u, ...(v.d || []).map((d) => d.u)]) {
|
||||
if (u && /^http:\/\//i.test(u)) httpUrls.push(`${c} ${u}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ if (httpUrls.length) add('G', `${httpUrls.length} plain-http URL(s) (e.g. ${http
|
|||
let fBad = 0
|
||||
for (const r of rows) {
|
||||
const v = codes[r[4]] || {}
|
||||
const want = v.s && v.f ? 2 : (v.s || v.f || (v.d && v.d.length) || v.au || v.bv || v.se) ? 1 : 0
|
||||
const want = v.s && v.f ? 2 : (v.s || v.f || (v.d && v.d.length) || v.au || v.bv || v.se || v.ga) ? 1 : 0
|
||||
if (want !== r[6]) fBad++
|
||||
}
|
||||
if (fBad) add('F', `${fBad} pin(s) carry a tier that disagrees with their code's URLs`)
|
||||
|
|
@ -205,6 +205,7 @@ if (verif && verif.links) {
|
|||
if (v.au && v.au.u) shipped.add(v.au.u)
|
||||
if (v.bv && v.bv.u) shipped.add(v.bv.u)
|
||||
if (v.se && v.se.u) shipped.add(v.se.u)
|
||||
if (v.ga && v.ga.u) shipped.add(v.ga.u)
|
||||
if (v.gp && v.gp.u) shipped.add(v.gp.u)
|
||||
for (const d of v.d || []) if (d.u) shipped.add(d.u)
|
||||
}
|
||||
|
|
@ -241,6 +242,7 @@ if (verif && verif.links) {
|
|||
if (v.au && v.au.u) shipped2.add(v.au.u)
|
||||
if (v.bv && v.bv.u) shipped2.add(v.bv.u)
|
||||
if (v.se && v.se.u) shipped2.add(v.se.u)
|
||||
if (v.ga && v.ga.u) shipped2.add(v.ga.u)
|
||||
if (v.gp && v.gp.u) shipped2.add(v.gp.u)
|
||||
for (const d of v.d || []) if (d.u) shipped2.add(d.u)
|
||||
}
|
||||
|
|
|
|||
106
internal/check-poster-card-fits.mjs
Normal file
106
internal/check-poster-card-fits.mjs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-poster-card-fits — a poster card's print-options row must stay inside its own card.
|
||||
*
|
||||
* THE DEFECT THIS CATCHES, reported by the repo-hygiene agent off a fresh 1440px screenshot and
|
||||
* confirmed here: every poster card's print-options row (Prayer List · Card Deck · stock select ·
|
||||
* Poster Map) burst its card and overlapped the neighbours. MEASURED 2026-09-24 at 1440px:
|
||||
* 27 of 27 cards overflowing, worst 110px past a 212px card.
|
||||
*
|
||||
* ★ WHY SOURCE READING MISSED IT. `.pfs` already had `width:82%`, so the rule looks correct — a
|
||||
* constrained box. But flex items default to `min-width:auto` and REFUSE to shrink below their
|
||||
* intrinsic content width, so four controls pushed the row open regardless of the container's
|
||||
* width. The fix is flex-wrap plus min-width:0 on the children; the point for this gate is that
|
||||
* only GEOMETRY shows it.
|
||||
*
|
||||
* ⚠ IT ASSERTS TWO THINGS, because either alone is satisfiable by a broken UI:
|
||||
* 1. No print row intrudes into another card's box — the reported symptom.
|
||||
* 2. Every control still has a usable box. "Nothing overflows" is trivially true if the row
|
||||
* collapsed the buttons to zero width, which would be a worse product than the overflow.
|
||||
*
|
||||
* Usage: node internal/check-poster-card-fits.mjs [leafUrl] — SKIPs when nothing is served.
|
||||
*/
|
||||
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)
|
||||
|
||||
let reachable = false
|
||||
try { const r = await fetch(LEAF, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ }
|
||||
if (!reachable) {
|
||||
console.log(`POSTER-CARD-FITS: SKIP — nothing served at ${LEAF}.`)
|
||||
console.log(' Serve the built tree: python3 -m http.server 4173 --directory public (or set BASE_URL)')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
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 } })
|
||||
await page.goto(LEAF, { waitUntil: 'load', timeout: 45000 })
|
||||
await page.waitForTimeout(10000)
|
||||
r = 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
|
||||
for (const { c, b } of boxes) {
|
||||
const row = c.querySelector('.pfs')
|
||||
if (!row) continue
|
||||
const rb = row.getBoundingClientRect()
|
||||
if (rb.right > b.right + 1 || rb.left < b.left - 1) {
|
||||
overflowing++
|
||||
worst = Math.max(worst, Math.round(Math.max(rb.right - b.right, b.left - rb.left)))
|
||||
}
|
||||
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)
|
||||
const oy = Math.min(rb.bottom, o.b.bottom) - Math.max(rb.top, o.b.top)
|
||||
if (ox > 1 && oy > 1) intrusions++
|
||||
}
|
||||
}
|
||||
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 }
|
||||
})()`)
|
||||
} 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`)
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nPOSTER-CARD-FITS: FAIL — ${fails.length} problem(s)`)
|
||||
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.')
|
||||
|
|
@ -76,6 +76,7 @@ await new Promise(r => setTimeout(r, 800))
|
|||
|
||||
const browser = await chromium.launch({ args: ['--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'] })
|
||||
const fails = []
|
||||
const checked = {} // mode -> tabs actually driven; printed, and 0 is a FAIL (see the summary)
|
||||
try {
|
||||
for (mode of MODES) {
|
||||
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } })
|
||||
|
|
@ -119,6 +120,7 @@ try {
|
|||
const needCanvas = !discovered
|
||||
const ok = v.state === 'ready' && (!needCanvas || v.canvas > 0)
|
||||
const why = v.state !== 'ready' ? `state ${v.state}${v.error ? ' — ' + v.error : ''}` : (needCanvas && !v.canvas ? 'mounted but painted no map canvas' : '')
|
||||
checked[mode] = (checked[mode] || 0) + 1
|
||||
console.log(`${ok ? 'PASS' : 'FAIL'} [${mode}] ${id}${why ? ' — ' + why : ''}`)
|
||||
if (!ok) fails.push(`[${mode}] ${id}: ${why}`)
|
||||
}
|
||||
|
|
@ -128,8 +130,14 @@ try {
|
|||
}
|
||||
} finally { await browser.close(); cleanup() }
|
||||
|
||||
// ★ A MODE THAT RAN NOTHING HAS NOT PASSED. A flag lost on the way in (package.json once ended
|
||||
// this entry in an inline # comment, so an appended --mode landed inside the comment) must not
|
||||
// read as "both modes green": each requested mode has to have driven at least one tab.
|
||||
for (const m of MODES) if (!checked[m]) fails.push(`[${m}] checked 0 tabs — this mode did not run`)
|
||||
const tally = MODES.map(m => `${m} ${checked[m] || 0} tab(s)`).join(', ')
|
||||
if (fails.length) {
|
||||
console.log(`\nchecked: ${tally}`)
|
||||
console.log(`\ncheck-research-gallery-embed: FAIL (${fails.length})`); for (const f of fails) console.log(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('\ncheck-research-gallery-embed: PASS — the Research Gallery embeds on a foreign origin in ' + MODES.join(' + ') + ' mode')
|
||||
console.log(`\ncheck-research-gallery-embed: PASS — the Research Gallery embeds on a foreign origin (${tally})`)
|
||||
|
|
|
|||
85
internal/check-surfaces-read-the-tier.mjs
Normal file
85
internal/check-surfaces-read-the-tier.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* check-surfaces-read-the-tier.mjs — one place decides "this group has nothing".
|
||||
*
|
||||
* THE BUG THIS EXISTS FOR, THREE TIMES OVER. The pin index computes a tier per group:
|
||||
* 0 means no resource was found in any pool checked. The charts profile used to decide the
|
||||
* same thing again, by listing the resource fields by hand:
|
||||
*
|
||||
* none: !c.s && !c.f && !c.au && !c.bv && !c.z && !(c.d && c.d.length)
|
||||
*
|
||||
* Every time a source was added, that list was one field short and the two views of the same
|
||||
* dataset disagreed — 7 codes the first time, then 28 when Scripture Earth landed: drawn as
|
||||
* having nothing by the chart while the map offered them a working link. Nothing failed. The
|
||||
* chart simply told a quieter, wronger story than the map, and only a hand count found it.
|
||||
*
|
||||
* THE RULE. A surface may READ the tier the pin index computed. It may not RE-DERIVE it from
|
||||
* the raw resource fields. Adding a source should reach every view for free.
|
||||
*
|
||||
* Run with --poison to prove the check can fail: it re-injects the hand-rolled formula into
|
||||
* a copy of each surface and requires every one to be caught.
|
||||
*/
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { resolve, dirname, basename } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const DIR = resolve(HERE, '../plugin-designs/gospel-resources')
|
||||
const POISON = process.argv.includes('--poison')
|
||||
|
||||
/**
|
||||
* A hand-rolled "has nothing" decision: a negated resource field, joined by && to at least
|
||||
* two more negated field reads. Matching the SHAPE rather than one spelling is the point —
|
||||
* the next drift will use a different field list, not this one.
|
||||
*/
|
||||
const FIELD = String.raw`!\s*c\.(?:s|f|au|bv|se|ga|z|gp|d)\b[^\n]{0,40}`
|
||||
const HANDROLLED = new RegExp(`${FIELD}&&\\s*${FIELD}&&\\s*${FIELD}`)
|
||||
|
||||
function offendersIn(src, label) {
|
||||
const out = []
|
||||
const lines = src.split('\n')
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
/* Only lines that are actually deciding emptiness, not e.g. a render guard. */
|
||||
if (!/\bnone\b|\bgap\b|\bnothing\b|hasNothing/.test(line)) continue
|
||||
if (HANDROLLED.test(line)) out.push(`${label}:${i + 1} ${line.trim().slice(0, 90)}`)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const surfaces = readdirSync(DIR).filter((f) => f.endsWith('.vue'))
|
||||
if (!surfaces.length) {
|
||||
console.error('SURFACES-READ-THE-TIER: no .vue surfaces found — the check would pass vacuously.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (POISON) {
|
||||
const INJECT =
|
||||
' none: !c.s && !c.f && !c.au && !c.bv && !c.z && !(c.d && c.d.length),'
|
||||
let caught = 0
|
||||
for (const f of surfaces) {
|
||||
const poisoned = `${readFileSync(resolve(DIR, f), 'utf8')}\n${INJECT}\n`
|
||||
if (offendersIn(poisoned, f).length) caught++
|
||||
else console.error(` NOT CAUGHT in ${f}`)
|
||||
}
|
||||
if (caught === surfaces.length) {
|
||||
console.log(
|
||||
`SURFACES-READ-THE-TIER --poison: ${caught}/${surfaces.length} surface(s) caught the injected formula.`,
|
||||
)
|
||||
console.log('SURFACES-READ-THE-TIER: SELF-TEST PASS — the check fails as designed.')
|
||||
process.exit(0)
|
||||
}
|
||||
console.error('SURFACES-READ-THE-TIER: SELF-TEST FAIL — the check cannot detect the bug it exists for.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const offenders = surfaces.flatMap((f) => offendersIn(readFileSync(resolve(DIR, f), 'utf8'), f))
|
||||
if (offenders.length) {
|
||||
console.error(`SURFACES-READ-THE-TIER: FAIL — ${offenders.length} surface line(s) re-derive "has nothing":`)
|
||||
for (const o of offenders) console.error(` ${o}`)
|
||||
console.error(' Read the tier the pin index computed (pins[i][6] === 0) instead of listing')
|
||||
console.error(' the resource fields again — that list goes stale every time a source is added.')
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(
|
||||
`SURFACES-READ-THE-TIER: PASS — ${surfaces.length} surface(s), none re-derives the tier.`,
|
||||
)
|
||||
|
|
@ -71,6 +71,9 @@ const GATES = [
|
|||
// and answer a tap. It also scans every link in the index against the screened-publisher
|
||||
// list, which is a doctrine boundary rather than a rendering one.
|
||||
{ script: 'verify:gospel-map', why: 'the gospel map renders its pins, answers a tap, and links only screened publishers' },
|
||||
// verify:gospel-map counts pins, which paint with no token at all. This one is the only
|
||||
// gate that measures the BASEMAP, in a real cross-origin iframe, on the baked token.
|
||||
{ script: 'check:embed-basemap', why: "the gospel map's basemap paints in a third-party iframe on its own baked token" },
|
||||
{ script: 'verify:gate1', why: 'the Gate-1 chain filters live data end to end' },
|
||||
{ script: 'check:a11y-button-names', why: 'every visible button has an accessible name' },
|
||||
{ script: 'check:nav-reachable', why: 'a phone can open the cap-5 tab rail' },
|
||||
|
|
|
|||
|
|
@ -433,10 +433,20 @@ 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.
|
||||
'.pfs{position:absolute;bottom:-14px;left:50%;transform:translateX(-50%);' +
|
||||
'width:82%;margin:0;padding:0 4px 3px;border:1px solid rgba(0,0,0,.14);' +
|
||||
'border-radius:2px;background:#fff;display:flex;gap:2px;justify-content:center;' +
|
||||
'width:82%;max-width:82%;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;' +
|
||||
'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%}' +
|
||||
'.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}' +
|
||||
|
|
|
|||
13
package.json
13
package.json
|
|
@ -68,6 +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: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.",
|
||||
|
|
@ -146,23 +147,27 @@
|
|||
"check:synced-css": "node internal/check-synced-css-copies.mjs",
|
||||
"check:composite-reg": "node internal/check-composite-registration.mjs",
|
||||
"check:filter-width": "node internal/check-filter-width-stable.mjs",
|
||||
"check:gallery-embed": "node internal/check-research-gallery-embed.mjs # the cap-5 Research Gallery must embed on a FOREIGN ORIGIN (a stand-in host page on localhost, the built tree on 127.0.0.1) in BOTH modes — bare <script>+<doxa-maps-spa> and <iframe> — with the layout inside the host box and every curated tab painting a map. Starts its own servers on OS-assigned ports; needs only a built tree. Runs both modes; for one mode use the sibling entries check:gallery-embed:js and check:gallery-embed:iframe, and to narrow to specific tabs invoke internal/check-research-gallery-embed.mjs directly. Naming the flags HERE made check:script-flags red: this command ends in a # comment, so anything appended lands inside the comment and the shell drops it, and the gate would run its default and exit 0 looking like success. Exit 2 = SKIP (no built tree / no playwright).",
|
||||
"check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js # the JS-embed half of check:gallery-embed, as its own entry. The flag cannot reach the gate through `bun run check:gallery-embed --mode js`: that script ends in an inline # comment, so the shell drops anything appended after it and the gate silently runs its default and exits 0 — check:script-flags exists for exactly that trap.",
|
||||
"check:gallery-embed:iframe": "node internal/check-research-gallery-embed.mjs --mode iframe # the iframe half, same reason.",
|
||||
"check:gallery-embed": "node internal/check-research-gallery-embed.mjs # the cap-5 Research Gallery must embed on a FOREIGN ORIGIN (a stand-in host page on localhost, the built tree on 127.0.0.1) in BOTH modes, bare <script>+<doxa-maps-spa> and <iframe>, with the layout inside the host box and every curated tab painting a map. The summary line prints the tab count PER MODE, and a mode that checked zero tabs is a FAIL. One mode only: check:gallery-embed:js or check:gallery-embed:iframe. Starts its own servers on OS-assigned ports; needs only a built tree. Exit 2 = SKIP (no built tree / no playwright).",
|
||||
"check:gallery-embed:js": "node internal/check-research-gallery-embed.mjs --mode js",
|
||||
"check:gallery-embed:iframe": "node internal/check-research-gallery-embed.mjs --mode iframe",
|
||||
"check:spa-tab-order": "node internal/check-spa-tab-order.mjs # the cap-5 SPA must open on Big Picture and keep the Driver's order (Big Picture, UPG-100, Prayer Progress, Gospel Resources) — asserted as PRESENCE + tab-1 + RELATIVE order on the RENDERED rail, because the rail gained three sections and now renders 12 rows with others between them. ⚠ NOT WAVE-WIRED: it FAILS rather than skips when the SPA is unreachable, deliberately (\"a gate that cannot reach its subject has not passed, it has not run\") — correct standalone, but gates:post runs on checkouts with no server. Needs `bun run serve` on 4173.",
|
||||
"check:backlog-status": "node internal/check-backlog-status-enum.mjs",
|
||||
"check:backlog-refs": "node internal/check-backlog-refs.mjs # a card may not depend on a card that does not exist. A dangling depends_on can never be done, so the card can never become ready and nothing says so — it reads as ordinary blocked work forever. FAILS only for depends_on on an OPEN card; a dangling parent or a stale link on a done card is REPORTED, because failing on filing debt teaches people to delete the reference rather than restore the card. Reads all three phase backlogs — references legitimately cross phases. SKIPs when _qa-cycle is absent.",
|
||||
"check:mjs-parse": "node internal/check-mjs-parses.mjs",
|
||||
"check:snapshot-guard": "node internal/check-snapshot-guard.mjs # every gospel-resources catalogue fetcher must write through scripts/_snapshot-guard.mjs, which refuses a write that loses more than a tenth of the previous snapshot. Written after a rate-limited FCBH run silently replaced 1,666 video scopes with 1,103. Static, sub-second. Self-test: `bun run check:snapshot-guard:poison` injects a raw write and the check must fail on it.",
|
||||
"check:snapshot-guard:poison": "node internal/check-snapshot-guard.mjs --poison",
|
||||
"check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs # the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.",
|
||||
"check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison",
|
||||
"check:surfaces-read-the-tier": "node internal/check-surfaces-read-the-tier.mjs # a surface may READ the tier the pin index computed, never re-derive 'this group has nothing' from the raw resource fields. That hand-rolled field list went stale every time a source was added: 7 codes drawn as having nothing while the map showed them a link, then 28 when Scripture Earth landed. Static, sub-second. Self-test: `bun run check:surfaces-read-the-tier:poison` re-injects the old formula into every surface and each must be caught.",
|
||||
"check:surfaces-read-the-tier:poison": "node internal/check-surfaces-read-the-tier.mjs --poison",
|
||||
"check:embed-basemap": "node internal/check-embed-basemap-paints.mjs # the gospel map's BASEMAP must paint inside a genuinely cross-origin iframe on the token the page BAKES, not one a probe forces on it. Every other browser gate here forces a restricted token (u=doxalife) that 403s on the basemap, then counts PINS — which come from our own GeoJSON and paint with no token at all — so 'the map renders' had never been measured. Runs a negative control every time by rewriting the token on the wire; if the control passes it reports PROVED NOTHING rather than a pass. ~60s, needs a browser.",
|
||||
"check:synced-css:self-test": "node internal/check-synced-css-copies.mjs --self-test",
|
||||
"serve:permissive": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public # LOOPBACK ONLY. This was --bind 0.0.0.0, which published the built tree on every interface of whatever machine ran a gate; measured on the build VPS it answered HTTP 200 on the box's PUBLIC IP while a browser gate was running. Every consumer (verify-browser-all, check:a11y-button-names, check:nav-reachable, smoke:share-all) already defaults to 127.0.0.1, so nothing needed the wider bind. Override with BASE_URL/BASE if you deliberately want to reach it from another host.",
|
||||
"check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs",
|
||||
"check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test",
|
||||
"check:legend-source": "node internal/check-legend-matches-source.mjs # the numbers the maps SHOW must match the numbers the live SOURCE has. Every other gate checks INTERNAL consistency — a page can agree with itself perfectly while its legend has stopped describing reality. Offline SKIPs (exit 0); only a reachable endpoint that disagrees fails.",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:wp-embed check:doc-scripts",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:legend-source check:backlog-refs",
|
||||
"build:bundles": "node internal/run-parallel.mjs --bundles",
|
||||
"check:maplibre-drops": "node internal/check-maplibre-layer-drops.mjs",
|
||||
"check:tabs-localized": "node internal/check-tabs-localized.mjs # a chosen locale must CHANGE the rendered tab text. check:i18n-parity compares catalogs to each OTHER, so it cannot tell a shipped translation from a dead one; tabs.json is reached only via a DYNAMIC key (SemanticTreeLegend.vue:40), invisible to any grep. Renders it and reads the result.",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_comment": "Declares the composability tier. 'nested-mfe-spa' (display alias 'app-shell') = Capability-5: this plugin is the OUTER app shell of the dx-maps app, the Research Gallery. Its single element <doxa-maps-spa> renders a vertical-tab rail whose tabs each mount a REAL, independently embeddable plugin: the shell loads that bundle's script (URL from manifest.json) and creates its registered custom element in the pane. There is no iframe mode. Tab order and grouping live in NAV_MODEL/TAB_DEFS in doxa-maps-spa.vue; `composes` below lists the seven curated tabs as bundle/profile and must be kept in step with TAB_DEFS (check:composite reads it; the studio's Composed-of panel shows it). Bundles not placed in a section appear under Experimental automatically. This explicit meta is the ONLY way the Cat-5 tier is declared (discover.mjs never infers it).",
|
||||
"_comment": "Declares the composability tier. 'nested-mfe-spa' (display alias 'app-shell') = Capability-5: this plugin is the OUTER app shell of the dx-maps app, the Research Gallery. Its single element <doxa-maps-spa> renders a vertical-tab rail whose tabs each mount a REAL, independently embeddable plugin: the shell loads that bundle's script (URL from manifest.json) and creates its registered custom element in the pane. There is no iframe mode. Tab order and grouping live in NAV_MODEL/TAB_DEFS in doxa-maps-spa.vue; `composes` below lists the seven curated tabs as bundle/profile and must be kept in step with TAB_DEFS (check:composite reads it; the studio's \"Inside doxa-maps-spa\" tree shows it). Bundles not placed in a section appear under Experimental automatically. This explicit meta is the ONLY way the Cat-5 tier is declared (discover.mjs never infers it).",
|
||||
"type": "nested-mfe-spa",
|
||||
"composes": [
|
||||
"dashboard-plus-grid/dashboard-plus-grid",
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@
|
|||
* ═══ WHAT A BAR MEANS, AND WHAT IT MUST NEVER BE READ AS ════════════════════
|
||||
* A bar counts what was FOUND IN THE POOLS CHECKED — eBible.org and PNG.Bible (text
|
||||
* Scripture), Arclight/JESUS Film (film), Door43/unfoldingWord (stories and Scripture)
|
||||
* and Faith Comes By Hearing (audio Scripture). It is NEVER proof of absence. Three
|
||||
* pools remain unchecked: API.Bible, DBL and progress.bible. GRN is a fourth and still
|
||||
* answers 403 on every path including robots.txt.
|
||||
* Faith Comes By Hearing (audio Scripture), Scripture Earth (minority-language Scripture)
|
||||
* and Global Recordings Network (gospel audio). It is NEVER proof of absence. Three pools
|
||||
* remain unchecked — API.Bible, DBL and progress.bible — and GRN is swept a batch at a time
|
||||
* at the crawl delay they ask for, so part of it is still unread.
|
||||
*
|
||||
* Three consequences the charts are built around:
|
||||
* 1. A pool nobody has queried is drawn as a HATCHED "not measured" row, never a zero
|
||||
|
|
@ -128,7 +129,7 @@ const languages = computed(() => {
|
|||
if (!d || !d.codes) return []
|
||||
const { codes, pins } = d
|
||||
const by = new Map()
|
||||
for (const [, , , iso3, code, pop] of pins) {
|
||||
for (const [, , , iso3, code, pop, tier] of pins) {
|
||||
if (!code) continue
|
||||
let e = by.get(code)
|
||||
if (!e) {
|
||||
|
|
@ -153,8 +154,8 @@ const languages = computed(() => {
|
|||
jesusFilm: !!c.f,
|
||||
obs,
|
||||
// Audio is now MEASURED, from Faith Comes By Hearing, so this is a real count and
|
||||
// no longer an "unknown". GRN is still unsearchable and still 403s on everything,
|
||||
// but it was never the only audio pool and the chart no longer implies it was.
|
||||
// no longer an "unknown". GRN is now searched too, through their 5fish site, and
|
||||
// counted separately as gospel audio rather than folded in here.
|
||||
audioKnown: !!(c.au && /audio/.test(c.au.m || '')),
|
||||
// THREE MEDIA THE CHART DID NOT DRAW AT ALL. Bible video reaches 1,666 languages —
|
||||
// more than Scripture does — and was simply absent, so the picture a reader took
|
||||
|
|
@ -162,10 +163,13 @@ const languages = computed(() => {
|
|||
bibleVideo: !!(c.bv || (c.au && /video/.test(c.au.m || ''))),
|
||||
tract: !!c.gp,
|
||||
training: !!c.z,
|
||||
// Bible video and training count too. Without them 7 codes were drawn as having
|
||||
// nothing while the map showed them a real link — small, but it is the same
|
||||
// contradiction between two views of one dataset.
|
||||
none: !c.s && !c.f && !c.au && !c.bv && !c.z && !(c.d && c.d.length),
|
||||
// READ THE TIER, DO NOT RECOMPUTE IT. This line used to list the resource fields by
|
||||
// hand, and it drifted every single time a source was added: first 7 codes drawn as
|
||||
// having nothing while the map showed them a link, then 28 when Scripture Earth
|
||||
// landed. The pin index already decides this, one place, and a gate guards it — so
|
||||
// the chart asks rather than deciding again. A new source now reaches this view for
|
||||
// free instead of silently leaving it a source behind.
|
||||
none: tier === 0,
|
||||
unmatchable: !code,
|
||||
countries: new Set(),
|
||||
}
|
||||
|
|
@ -396,12 +400,12 @@ useShadowStyles(
|
|||
The "Where Nothing Was Found" table is keyed per people group and does include them.</small>
|
||||
</template>
|
||||
<!-- This said "four resource pools, including all audio, were never queried" until
|
||||
Faith Comes By Hearing was added. Audio is queried now; GRN is the one that
|
||||
cannot be. A stale caveat misleads exactly as much as a stale number. -->
|
||||
<br>Not found is not the same as not existing. Audio has been searched at Faith Comes
|
||||
By Hearing, but GRN — the largest audio pool — answers 403 to every request including
|
||||
its own robots.txt, and api.bible, the Digital Bible Library and progress.bible all
|
||||
need credentials this build does not have.
|
||||
Faith Comes By Hearing was added, then again when GRN was reached through 5fish.
|
||||
A stale caveat misleads exactly as much as a stale number. -->
|
||||
<br>Not found is not the same as not existing. Audio has been searched at Faith Comes By
|
||||
Hearing and at Global Recordings Network, the latter a batch at a time at the ten-second
|
||||
crawl delay they ask for, so part of that pool is still unread. api.bible, the Digital
|
||||
Bible Library and progress.bible all need credentials this build does not have.
|
||||
</div>
|
||||
|
||||
<div class="gc-tabs">
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@
|
|||
* ═══ THE HONESTY THE UI IS BUILT AROUND ═════════════════════════════════════
|
||||
* `has_scripture:false` means NOT FOUND IN THE POOLS CHECKED — eBible.org, PNG.Bible and
|
||||
* Faith Comes By Hearing for text, Arclight for film, Door43 for stories, and FCBH again
|
||||
* for audio. It does NOT mean no scripture exists. GRN is still unchecked and answers 403
|
||||
* on every path including robots.txt; API.Bible, DBL and progress.bible need keys. Every
|
||||
* for audio, Scripture Earth for minority-language Scripture, and Global Recordings Network
|
||||
* for gospel audio. It does NOT mean no scripture exists: GRN is swept a batch at a time at
|
||||
* the crawl delay they ask for, so part of it is still unread, and API.Bible, DBL and
|
||||
* progress.bible need keys. Every
|
||||
* card names its checked and unchecked pools, and a pool nobody queried renders as
|
||||
* "unknown" rather than "no".
|
||||
*
|
||||
|
|
@ -329,9 +331,10 @@ useShadowStyles(
|
|||
currently <b>eBible.org</b> ({{ fmt(stats.ebible_translations) }} translations across
|
||||
{{ fmt(stats.ebible_languages) }} languages, public data), plus PNG.Bible, the JESUS
|
||||
Film catalogue, Door43, and Faith Comes By Hearing for both text and audio Scripture. It does
|
||||
<b>not</b> mean no scripture exists: GRN, the Digital Bible Library and progress.bible
|
||||
are <b>not yet checked</b> — GRN answers 403 to every request, including its own
|
||||
robots.txt. A pool nobody has queried shows as unknown, never as “no”.
|
||||
<b>not</b> mean no scripture exists: Scripture Earth and Global Recordings Network are
|
||||
searched too, GRN a batch at a time at the crawl delay they ask for, so part of it is
|
||||
still unread; api.bible, the Digital Bible Library and progress.bible need credentials
|
||||
this build does not have. A pool nobody has queried shows as unknown, never as “no”.
|
||||
Languages are joined to people groups by exact ISO 639-3 code.
|
||||
<br><br>
|
||||
This page lists <b>published resource availability only</b>. It carries no information
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@ function groupAt(i) {
|
|||
bible_video: c.bv || null,
|
||||
gospel_tract: c.gp || null,
|
||||
scripture_earth: c.se || null,
|
||||
gospel_audio: c.ga || null,
|
||||
related: c.rel || null,
|
||||
zume_url: c.z || '',
|
||||
zume_approx: c.za || null,
|
||||
|
|
@ -1089,10 +1090,10 @@ useShadowStyles(
|
|||
across its groups; a dot is that ONE group's own language. So a red dot can sit on a green
|
||||
country — that is the case worth finding, not a rendering fault. Both are counted from the
|
||||
same index, so the colour and the dots can no longer disagree.
|
||||
<br>“None found” = not in the pools checked (eBible, PNG.Bible, JESUS Film, Door43 and
|
||||
Faith Comes By Hearing). <b>GRN, the largest audio pool, is still unchecked</b> — it answers
|
||||
403 to every request, including its own robots.txt — so for an oral culture there is one
|
||||
place this map has not been able to look. Never proof of absence.
|
||||
<br>“None found” = not in the pools checked (eBible, PNG.Bible, JESUS Film, Door43,
|
||||
Faith Comes By Hearing, Scripture Earth and Global Recordings Network). GRN is reached
|
||||
through their 5fish site and is being swept <b>a batch at a time</b>, at the ten-second
|
||||
crawl delay they ask for, so part of that pool is still unread. Never proof of absence.
|
||||
<template v-if="mode === 'coverage'">
|
||||
A “found” colour is equally bounded: it counts what these pools list, not everything that exists.
|
||||
</template>
|
||||
|
|
@ -1135,13 +1136,12 @@ useShadowStyles(
|
|||
sending translators or broadcasters.
|
||||
</template>
|
||||
<template v-else>These are the 40 largest; tap one to fly there.</template>
|
||||
<!-- This paragraph used to say audio had never been searched for any of them. That
|
||||
stopped being true when Faith Comes By Hearing was added, and a stale caveat is
|
||||
as misleading as a stale number. GRN is still unsearchable and still named. -->
|
||||
<br>Audio <b>has</b> been searched, at Faith Comes By Hearing — these groups have none
|
||||
there either. GRN remains the one audio pool nobody has looked in: it answers 403 to
|
||||
every request, including its own robots.txt, and needs an agreement rather than a
|
||||
scraper.
|
||||
<!-- This paragraph has twice had to stop claiming a pool was unsearched: first when
|
||||
Faith Comes By Hearing was added, then when GRN was reached through 5fish. A
|
||||
stale caveat is as misleading as a stale number. -->
|
||||
<br>Audio <b>has</b> been searched — at Faith Comes By Hearing, and at Global
|
||||
Recordings Network through their 5fish site, which is being swept a batch at a time
|
||||
because they ask crawlers for a ten-second delay. These groups have none in either.
|
||||
<br><em>related:</em> where a line below carries it, ISO 639-3 files that code under a
|
||||
macrolanguage with another variety that does have resources. It is how the standard
|
||||
groups the codes, not a claim they are mutually intelligible.
|
||||
|
|
@ -1235,10 +1235,9 @@ useShadowStyles(
|
|||
<span v-else class="gm-chip meh">Film · none found</span>
|
||||
|
||||
<!-- AUDIO SCRIPTURE, from Faith Comes By Hearing. This chip read "not searched
|
||||
(GRN)" on every group on this map for as long as it has existed, because GRN
|
||||
answers 403 to everything including robots.txt. That is still true and still
|
||||
said below — but it is no longer the whole answer, and a group with an audio
|
||||
Bible should never have been told the audio question was simply unasked. -->
|
||||
(GRN)" on every group for as long as this map existed. GRN is now searched too,
|
||||
through 5fish, and appears as its own Gospel audio chip — so this one speaks
|
||||
only for FCBH and no longer implies the audio question went unasked. -->
|
||||
<!-- ONE link, and it says what it carries. A bible.is fileset is a single page
|
||||
that may hold audio, text or both, so two chips to the same URL would be
|
||||
noise; the media list is the useful part, because "there is a recording"
|
||||
|
|
@ -1254,8 +1253,8 @@ useShadowStyles(
|
|||
matters — which pool could still have signed Scripture — further down. When
|
||||
FCBH does hold something for them it is text, and the link above still shows. -->
|
||||
<span v-else-if="!selectedGroup.is_sign" class="gm-chip meh"
|
||||
title="Faith Comes By Hearing publishes no audio or text Scripture in this language. GRN (Global Recordings Network) is the other main audio pool and answers 403 to every request, so it has not been searched for any group on this map.">
|
||||
Audio · none at FCBH, GRN unsearchable
|
||||
title="Faith Comes By Hearing publishes no audio or text Scripture in this language. Global Recordings Network is searched separately and appears as its own Gospel audio chip when they hold something.">
|
||||
Audio · none found at FCBH
|
||||
</span>
|
||||
|
||||
<!-- Bible VIDEO, kept visibly apart from Film. Film here means the JESUS Film;
|
||||
|
|
@ -1287,6 +1286,16 @@ useShadowStyles(
|
|||
Gospel presentation · {{ selectedGroup.gospel_tract.n }}
|
||||
</a>
|
||||
|
||||
<!-- GOSPEL AUDIO — for a language with no writing system this may be the only
|
||||
form the gospel can take, so it gets its own chip rather than being folded
|
||||
into the audio-Scripture one. The title says plainly that it is not a
|
||||
translation: a worker must not set out believing they have Scripture. -->
|
||||
<a v-if="selectedGroup.gospel_audio" class="gm-chip ok wrap"
|
||||
:href="selectedGroup.gospel_audio.u" target="_blank" rel="noopener noreferrer"
|
||||
title="Global Recordings Network publishes gospel audio in this language — usually Words of Life, gospel messages and Bible stories with songs. It is not a translation of the Bible.">
|
||||
Gospel audio · GRN
|
||||
</a>
|
||||
|
||||
<!-- SCRIPTURE EARTH — SIL and its translation partners, reaching small languages
|
||||
the big catalogues skip. The chip names WHAT IS THERE rather than saying just
|
||||
"Scripture Earth", because the kinds differ per language and a worker needs to
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const FCBH = resolve(REPO, 'data/tables/fcbh-index.json')
|
|||
const MACRO = resolve(REPO, 'data/tables/iso-macrolanguages.json')
|
||||
const GODTOOLS = resolve(REPO, 'data/tables/godtools-index.json')
|
||||
const SCRIPTUREEARTH = resolve(REPO, 'data/tables/scriptureearth-index.json')
|
||||
const GRN = resolve(REPO, 'data/tables/grn-index.json')
|
||||
|
||||
// Two entries with the same subject rendered as "Open Bible Stories Open Bible Stories"
|
||||
// — two links a reader cannot tell apart. 40 languages are in that state, because a
|
||||
|
|
@ -156,6 +157,10 @@ try { godtools = JSON.parse(readFileSync(GODTOOLS, 'utf8')).codes || {} } catch
|
|||
let scriptureEarth = {}
|
||||
try { scriptureEarth = JSON.parse(readFileSync(SCRIPTUREEARTH, 'utf8')).byIso || {} } catch { scriptureEarth = {} }
|
||||
|
||||
// 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 zume = {}
|
||||
let zumeApprox = {}
|
||||
try {
|
||||
|
|
@ -322,8 +327,8 @@ for (const [c, v] of Object.entries(codes)) {
|
|||
// credentials this build does not have, and they are the ONLY things the unchecked list
|
||||
// should ever name. A reader deciding where to work needs "we looked and it is not there"
|
||||
// to be distinguishable from "nobody looked".
|
||||
const SCANNED = 'ebible, arclight, door43, fcbh, scriptureearth'
|
||||
const NOT_SCANNED = 'grn, api_bible, dbl, progress_bible'
|
||||
const SCANNED = 'ebible, arclight, door43, fcbh, scriptureearth, grn'
|
||||
const NOT_SCANNED = 'api_bible, dbl, progress_bible'
|
||||
// SIGN LANGUAGES GET THEIR OWN PROVENANCE, because the generic list is actively
|
||||
// misleading for them. A Deaf group is not served by eBible (text), Door43 (text and
|
||||
// stories) or FCBH audio, so listing those as "checked" implies a search that was never
|
||||
|
|
@ -421,6 +426,29 @@ for (const [c, e] of Object.entries(scriptureEarth)) {
|
|||
seCodes++
|
||||
}
|
||||
|
||||
// GOSPEL AUDIO (GRN). For a language with no writing system this is the ONLY form the
|
||||
// gospel can take, which is why it belongs in the gap rather than beside it. It lifts a
|
||||
// group off tier 0 — a person can hear the gospel in their own language — and never reaches
|
||||
// tier 2, which stays reserved for text Scripture together with the JESUS Film.
|
||||
//
|
||||
// Typed as Gospel audio, NEVER as Scripture: "Words of Life" is gospel messages and Bible
|
||||
// stories with songs, not a translation of the canon. Calling it Scripture would be the
|
||||
// same error as filing a story set under Scripture, and it is the error that matters most.
|
||||
let grnCodes = 0
|
||||
let grnNew = 0
|
||||
let grnSignSkipped = 0
|
||||
for (const [c, g] of Object.entries(grn)) {
|
||||
// NEVER on a sign language. GRN's catalogue is AUDIO recordings, and 5fish having a page
|
||||
// titled "Pakistan Sign Language" says they index the language, not that they hold signed
|
||||
// video. Attaching it anyway told 20 Deaf groups — 11.5M people — that gospel audio was
|
||||
// available to them, which is both unverified and, for this audience, useless. The rule
|
||||
// for an unclear source is to leave it out and say why, so it is left out here.
|
||||
if (codes[c] && codes[c].sg === 1) { grnSignSkipped++; continue }
|
||||
if (!codes[c]) { codes[c] = { l: g.n || '', ck: SCANNED, un: NOT_SCANNED }; grnNew++ }
|
||||
codes[c].ga = { u: g.u, n: g.n }
|
||||
grnCodes++
|
||||
}
|
||||
|
||||
let zumeApproxCodes = 0
|
||||
for (const [c, a] of Object.entries(zumeApprox)) {
|
||||
// Never overwrite an exact join with an approximate one, and never create a code that
|
||||
|
|
@ -447,6 +475,7 @@ if (dead.size) {
|
|||
if (v.bv && dead.has(v.bv.u)) { delete v.bv; droppedDead++ }
|
||||
if (v.gp && dead.has(v.gp.u)) { delete v.gp; droppedDead++ }
|
||||
if (v.se && dead.has(v.se.u)) { delete v.se; droppedDead++ }
|
||||
if (v.ga && dead.has(v.ga.u)) { delete v.ga; droppedDead++ }
|
||||
if (v.d) {
|
||||
const before = v.d.length
|
||||
v.d = v.d.filter((x) => !dead.has(x.u))
|
||||
|
|
@ -504,14 +533,15 @@ for (const p of slim) {
|
|||
const hasA = !!(c && c.au)
|
||||
const hasBV = !!(c && c.bv)
|
||||
const hasSE = !!(c && c.se)
|
||||
if (hasS || hasF || hasD || hasA || hasBV || hasSE) withUrl++
|
||||
const hasGA = !!(c && c.ga)
|
||||
if (hasS || hasF || hasD || hasA || hasBV || hasSE || hasGA) withUrl++
|
||||
if (!hasS && !hasF && hasD) liftedByD43++
|
||||
if (!hasS && !hasF && !hasD && (hasA || hasBV)) liftedByAudio++
|
||||
if (!hasS && !hasF && !hasD && !hasA && !hasBV && hasSE) liftedBySE++
|
||||
// tier 2 = Scripture AND film · 1 = SOMETHING found · 0 = nothing in any pool checked.
|
||||
// A Door43 resource lifts a group out of tier 0 because "nothing found" is then simply
|
||||
// untrue for them — but it never reaches tier 2, which is reserved for Scripture + film.
|
||||
const tier = hasS && hasF ? 2 : (hasS || hasF || hasD || hasA || hasBV || hasSE) ? 1 : 0
|
||||
const tier = hasS && hasF ? 2 : (hasS || hasF || hasD || hasA || hasBV || hasSE || hasGA) ? 1 : 0
|
||||
pins.push([
|
||||
p.Name || '',
|
||||
Math.round(lat * 100) / 100,
|
||||
|
|
@ -552,6 +582,7 @@ console.log(`arclight codes ${Object.keys(arc).length} (${arcOnly} new, ${upgr
|
|||
console.log(`share urls ${Object.keys(share).length}`)
|
||||
console.log(`fcbh codes ${audioCodes} (${audioNew} new codes)`)
|
||||
console.log(`gospel tracts ${gospelCodes} (Cru/GodTools presentations; depth only, tier UNCHANGED)`)
|
||||
console.log(`gospel audio ${grnCodes} (${grnNew} new codes; GRN via 5fish — heard, not read; ${grnSignSkipped} sign language(s) skipped)`)
|
||||
console.log(`scripture earth ${seCodes} (${seNew} new codes; SIL partners — Scripture in their own language)`)
|
||||
console.log(`related variety ${relatedCodes} (nothing of their own; an ISO macrolanguage sibling has something — tier UNCHANGED)`)
|
||||
console.log(`fcbh bible video ${videoCodes} (a separate page; never counted as film) + ${videoMerged} merged into the Scripture link`)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const SOURCE = (u) =>
|
|||
: /bible\.is/i.test(u) ? 'Faith Comes By Hearing'
|
||||
: /knowgod\.com/i.test(u) ? 'Cru (GodTools)'
|
||||
: /scriptureearth\.org/i.test(u) ? 'Scripture Earth (SIL)'
|
||||
: /fivefish\.org|globalrecordings\.net/i.test(u) ? 'Global Recordings Network'
|
||||
: 'other'
|
||||
|
||||
const out = []
|
||||
|
|
@ -120,6 +121,10 @@ for (const [code, c] of Object.entries(codes)) {
|
|||
// translation partners publishing the text in this language. The scope column names the
|
||||
// FORMATS actually held, since a worker needs to know whether they are being sent to a
|
||||
// PDF, to recorded audio, or to an app before they set out.
|
||||
// Gospel audio is its own type. It is NOT Scripture — "Words of Life" is gospel messages
|
||||
// and Bible stories with songs — and for a language with no writing it may be the only
|
||||
// form the gospel can take, so it must be findable rather than folded into another row.
|
||||
if (c.ga) add(c.ga.n ? `Words of Life — ${c.ga.n}` : 'Gospel audio', 'Gospel audio', c.ga.u, 'Listen in this language')
|
||||
if (c.se) {
|
||||
const SE_KIND = { PDF: 'PDF', audio: 'audio', sab: 'app', study: 'study notes', viewer: 'read online' }
|
||||
const kinds = (c.se.k || []).map((k) => SE_KIND[k] || k).join(', ')
|
||||
|
|
|
|||
172
plugin-designs/gospel-resources/scripts/fetch-grn-index.mjs
Normal file
172
plugin-designs/gospel-resources/scripts/fetch-grn-index.mjs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* fetch-grn-index.mjs — Global Recordings Network, via 5fish, joined on ISO 639-3.
|
||||
*
|
||||
* WHY THIS SOURCE, AND WHY IT WAS MISSING SO LONG. GRN records the gospel as AUDIO for
|
||||
* languages that have no written Scripture at all, which is precisely the population left
|
||||
* in this map's gap. It is named in the brief, and every previous attempt recorded it as
|
||||
* unreachable — globalrecordings.net sits behind a proof-of-work bot shield that answers
|
||||
* even robots.txt with a challenge page. That shield is an access control and is not worked
|
||||
* around here. Their own delivery site, 5fish.org, is NOT shielded and its robots.txt allows
|
||||
* everything except /abyss/, /app.xml and /PrayerApp.json, none of which this touches.
|
||||
*
|
||||
* WE OBEY THEIR CRAWL-DELAY. 5fish asks for `Crawl-delay: 10`. This fetcher is therefore
|
||||
* SEQUENTIAL with a ten-second gap and a per-run cap, and it is RESUMABLE so the work is
|
||||
* spread across runs rather than taken all at once. A full sweep of the gap is some hours of
|
||||
* wall clock at that rate; that is their rate to set, not ours, and this is a small ministry's
|
||||
* server. Do not add concurrency here.
|
||||
*
|
||||
* A 200 IS NOT AN ANSWER — MEASURED, WITH CONTROLS. The host never 404s. Three states were
|
||||
* observed and the title is what separates them:
|
||||
* - "…| Learn about Christian beliefs in <Language>" a real language page
|
||||
* - "…| Languages Spoken in <Country>" a GEO FALLBACK: /zzz-no-such-language
|
||||
* -xyzzy returns this, 200, 151,042 bytes
|
||||
* - "…| The Gospel in Every Language" (bare) no such language
|
||||
* Both of the two largest gap codes, cjy and gan, return the bare form, which is consistent
|
||||
* with their being in the gap at all. Taking HTTP 200 as existence would have marked every
|
||||
* code on earth as covered.
|
||||
*
|
||||
* WHAT IS CLAIMED. A hit means GRN publishes at least one audio program in that language —
|
||||
* typically "Words of Life", gospel messages and Bible stories with songs. It is typed as
|
||||
* Gospel audio and NEVER as Scripture: it is not a translation of the canon, and the same
|
||||
* care applies here as to Bible Stories. It lifts a group off tier 0, because a person can
|
||||
* hear the gospel in their own language, and it never reaches tier 2.
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { writeSnapshot } from './_snapshot-guard.mjs'
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const TABLES = resolve(HERE, '../../../data/tables')
|
||||
const OUT = resolve(TABLES, 'grn-index.json')
|
||||
const PINS = resolve(TABLES, 'people-group-pins.json')
|
||||
const ORIGIN = 'https://fivefish.org'
|
||||
|
||||
const UA =
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
|
||||
|
||||
/** Their stated crawl-delay, in ms. Lowering this is a decision about someone else's server. */
|
||||
const CRAWL_DELAY_MS = Number(process.env.GRN_DELAY_MS || 10000)
|
||||
/** Codes probed per run. The sweep is resumable, so this bounds one sitting, not the total. */
|
||||
const MAX = Number(process.env.GRN_MAX || 150)
|
||||
const LIMIT_SMOKE = process.env.GRN_SMOKE === '1'
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
const titleOf = (html) => {
|
||||
const m = /<title>([^<]*)<\/title>/i.exec(html || '')
|
||||
return m ? m[1].trim() : ''
|
||||
}
|
||||
|
||||
/** @returns {'hit'|'none'|'geo'|'error'} and the language name when it is a hit. */
|
||||
function classify(title) {
|
||||
if (/Learn about Christian beliefs in\s+(.+)$/i.test(title)) {
|
||||
return { state: 'hit', name: /Learn about Christian beliefs in\s+(.+)$/i.exec(title)[1].trim() }
|
||||
}
|
||||
if (/Languages Spoken in/i.test(title)) return { state: 'geo' }
|
||||
if (title) return { state: 'none' }
|
||||
return { state: 'error' }
|
||||
}
|
||||
|
||||
async function probe(iso) {
|
||||
try {
|
||||
const r = await fetch(`${ORIGIN}/${encodeURIComponent(iso)}`, {
|
||||
headers: { 'user-agent': UA, accept: 'text/html,application/xhtml+xml' },
|
||||
redirect: 'follow',
|
||||
signal: AbortSignal.timeout(30000),
|
||||
})
|
||||
if (!r.ok) return { state: 'error' }
|
||||
return classify(titleOf(await r.text()))
|
||||
} catch {
|
||||
return { state: 'error' }
|
||||
}
|
||||
}
|
||||
|
||||
/* ── controls, in band, before anything is trusted ───────────────────────────────────── */
|
||||
const neg = await probe('zzz-no-such-language-xyzzy')
|
||||
await sleep(CRAWL_DELAY_MS)
|
||||
const pos = await probe('amh')
|
||||
if (neg.state === 'hit') {
|
||||
console.error('GRN: control FAILED — a nonsense code reported a real language page. Aborting.')
|
||||
process.exit(1)
|
||||
}
|
||||
if (pos.state !== 'hit') {
|
||||
console.error(`GRN: control FAILED — amh did not read as a language page (${pos.state}). Aborting.`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`GRN: controls pass — nonsense reads "${neg.state}", amh reads "${pos.name}".`)
|
||||
|
||||
/* ── what still needs deciding ───────────────────────────────────────────────────────── */
|
||||
const prev = existsSync(OUT) ? JSON.parse(readFileSync(OUT, 'utf8')) : { byIso: {}, none: [] }
|
||||
const decided = new Set([...Object.keys(prev.byIso || {}), ...(prev.none || [])])
|
||||
|
||||
const pins = JSON.parse(readFileSync(PINS, 'utf8'))
|
||||
/* The gap first, largest population first: a bounded run should buy the most people it can. */
|
||||
const pop = {}
|
||||
const gap = new Set()
|
||||
for (const r of pins.pins) {
|
||||
const c = (r[4] || '').toLowerCase()
|
||||
if (!c) continue
|
||||
if (r[6] === 0) {
|
||||
gap.add(c)
|
||||
pop[c] = (pop[c] || 0) + (r[5] || 0)
|
||||
}
|
||||
}
|
||||
const todo = [...gap].filter((c) => !decided.has(c)).sort((a, b) => pop[b] - pop[a])
|
||||
console.log(`GRN: ${gap.size} gap code(s); ${decided.size} already decided; ${todo.length} left.`)
|
||||
|
||||
const batch = todo.slice(0, LIMIT_SMOKE ? 3 : MAX)
|
||||
const byIso = { ...(prev.byIso || {}) }
|
||||
const none = [...(prev.none || [])]
|
||||
let hits = 0
|
||||
let errors = 0
|
||||
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const iso = batch[i]
|
||||
await sleep(CRAWL_DELAY_MS)
|
||||
const r = await probe(iso)
|
||||
if (r.state === 'hit') {
|
||||
byIso[iso] = { u: `${ORIGIN}/${iso}`, n: r.name }
|
||||
hits++
|
||||
} else if (r.state === 'none' || r.state === 'geo') {
|
||||
/* A geo fallback means the path did not resolve to a language, same as "none" for us. */
|
||||
none.push(iso)
|
||||
} else {
|
||||
errors++
|
||||
}
|
||||
if ((i + 1) % 25 === 0) console.log(` ${i + 1}/${batch.length} (${hits} hit)`)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`GRN: probed ${batch.length}, ${hits} with audio, ${batch.length - hits - errors} without, ${errors} error(s).`,
|
||||
)
|
||||
|
||||
if (LIMIT_SMOKE) {
|
||||
console.log('GRN: GRN_SMOKE=1 — writing nothing.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
writeSnapshot(
|
||||
OUT,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
source: `${ORIGIN}/<iso639-3> (5fish, Global Recordings Network)`,
|
||||
fetched: new Date().toISOString().slice(0, 10),
|
||||
_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: Object.keys(byIso).length,
|
||||
decidedNone: none.length,
|
||||
byIso,
|
||||
none,
|
||||
},
|
||||
null,
|
||||
1,
|
||||
)}\n`,
|
||||
(j) => Object.keys(j.byIso || {}).length + (j.none || []).length,
|
||||
'GRN code(s) decided',
|
||||
)
|
||||
console.log(`wrote ${OUT}`)
|
||||
|
|
@ -334,6 +334,7 @@ for (const v of Object.values(pins.codes)) {
|
|||
if (v.bv && v.bv.u) urls.add(v.bv.u)
|
||||
if (v.gp && v.gp.u) urls.add(v.gp.u)
|
||||
if (v.se && v.se.u) urls.add(v.se.u)
|
||||
if (v.ga && v.ga.u) urls.add(v.ga.u)
|
||||
for (const d of v.d || []) if (d.u) urls.add(d.u)
|
||||
}
|
||||
// Carry forward everything a previous run proved dead, so dropping a link cannot erase the
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"_comment": "Declares the composability tier. 'nested-mfe-spa' (display alias 'app-shell') = Capability-5: this plugin is a real cap-5 element, <plugin-studio>, the DEVELOPER STUDIO. Its vertical-tab sidebar is the composability ladder grouped by capability 5 -> 1; each row nests one plugin element (token-free elements mounted directly by their real tag; map elements nested as their own SPA in a lazy iframe) with its embed snippet. Unlike a static index.html gallery page, this is a compiled Vue custom element, so it supports all 5 deployment methods and can itself be nested/placed. This explicit meta is the ONLY way the Cat-5 tier is declared (discover.mjs never infers it). Filename must be <profile>.meta.json where profile = the root .vue name (plugin-studio). generate-gallery.readSourceTiers() reads this and bakes capability 5 for the plugin, so it appears in the studio manifest as the app-shell (nested-mfe-spa). 'composes' lists the element tags it can nest (best-effort; the runtime tab list is read live from the built manifest, grouped by capability).",
|
||||
"type": "nested-mfe-spa",
|
||||
"composes": [
|
||||
"bp-big-picture",
|
||||
"dashboard-plus-grid",
|
||||
"people-groups-grid",
|
||||
"doxa-research-map",
|
||||
"doxa-simple-map",
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
ONE element, <plugin-studio>, renders a VERTICAL-TAB layout whose sidebar is the composability
|
||||
ladder grouped by CAPABILITY (5 → 1). Under each capability heading it lists every element that
|
||||
tier holds; clicking a row nests that element in the main slot. Below the tiers, one collapsible
|
||||
"Inside <composite>" section per composite draws its full composition tree, so a defect can be
|
||||
pinned on the exact nested element responsible. This is the DX viewing surface:
|
||||
tier holds; clicking a row nests that element in the main slot. Directly under each composite's
|
||||
row, its full composition tree is drawn indented, so a defect can be pinned on the exact nested
|
||||
element responsible. This is the DX viewing surface:
|
||||
the spot to SEE every plugin element work, in one place, ordered by capability, with the embed
|
||||
snippet for each — so a developer (or the Driver) can review each element and give feedback.
|
||||
|
||||
|
|
@ -94,7 +94,10 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<!-- Toolbar strip: the active element's identity, its COMPOSED-OF panel, and copy-embed. -->
|
||||
<!-- Toolbar strip: the active element's identity and copy-embed. Kept to ONE line on purpose —
|
||||
the plugin is what the studio is for, so it gets the rest of the main area. (A "Composed of"
|
||||
panel used to sit here; the Driver removed it 2026-09-24 because it took the plugin's space.
|
||||
Composition lives in the rail, indented under each composite.) -->
|
||||
<template #toolbar>
|
||||
<div class="ps-toolbar" v-if="active">
|
||||
<div class="ps-bar">
|
||||
|
|
@ -106,36 +109,6 @@
|
|||
@click="embedOpen = !embedOpen" v-if="active.embed">Copy embed</button>
|
||||
</div>
|
||||
|
||||
<!-- ★ COMPOSED OF — the per-entity composition surface. Renders ONLY for a composite (a
|
||||
bundle whose manifest entry carries `composes`); a leaf plugin shows nothing at all,
|
||||
which is the honest answer and costs a newcomer no reading.
|
||||
ONE ROW PER DIRECTLY-NESTED CHILD, maxDepth 1: name, capability badge, and a link to
|
||||
that child's OWN canonical entry in the rail. It is a list of LINKS, never copies. -->
|
||||
<div class="ps-composed" v-if="composedOf.length || composedUnresolved.length">
|
||||
<h2 class="ps-composed-h" :id="composedHeadingId">
|
||||
Composed of
|
||||
<span class="ps-composed-n">{{ composedOf.length }} nested {{ composedOf.length === 1 ? 'element' : 'elements' }}</span>
|
||||
<span class="ps-composed-warn" v-if="composedUnresolved.length"
|
||||
:title="'Declared in the manifest but not published as a plugin: ' + composedUnresolved.join(', ')">
|
||||
+{{ composedUnresolved.length }} not published
|
||||
</span>
|
||||
</h2>
|
||||
<ul class="ps-composed-list" :aria-labelledby="composedHeadingId">
|
||||
<li v-for="c in composedOf" :key="c.key">
|
||||
<button type="button" class="ps-composed-row" :data-child-key="c.key"
|
||||
:aria-label="'Open ' + c.label + ', capability ' + c.cap + ', ' + c.capLabel"
|
||||
@click="select(c.key)">
|
||||
<!-- No glyph here: the capability badge on the right carries the tier as readable
|
||||
text. (Emoji used to lead these rows and rendered as empty tofu boxes on a box
|
||||
with no emoji font; the rail now uses local SVG icons — see CAP_ICON.) -->
|
||||
<span class="ps-composed-name">{{ c.label }}</span>
|
||||
<span class="ps-composed-tag"><{{ c.tag }}></span>
|
||||
<span class="ps-composed-badge" :data-cap="c.cap">CAP {{ c.cap }} · {{ c.capShort }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="ps-embed" v-if="embedOpen && active.embed">
|
||||
<label>script embed
|
||||
<button type="button" class="ps-btn" @click="copyText(active.embed, $event.target)">Copy</button>
|
||||
|
|
@ -207,25 +180,6 @@ const PS_SHADOW_CSS = `
|
|||
.ps-bar-sub { display:block; font-size:12px; color:#6d766e; }
|
||||
.ps-btn { font:inherit; font-size:12px; padding:5px 10px; border:1px solid #d1d4cd; border-radius:8px; background:#d1d4cd; color:#3b463d; cursor:pointer; white-space:nowrap; }
|
||||
.ps-btn:hover { background:#d1d4cd; }
|
||||
.ps-composed { border:1px solid #d1d4cd; border-radius:10px; background:#ffffff; padding:9px 10px; }
|
||||
.ps-composed-h { margin:0 0 7px; font-size:11px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; color:#6d766e; display:flex; align-items:baseline; gap:8px; }
|
||||
.ps-composed-n { font-weight:400; letter-spacing:0; text-transform:none; font-size:11px; color:#6d766e; }
|
||||
.ps-composed-warn { font-weight:400; letter-spacing:0; text-transform:none; font-size:11px; color:#6d766e; border:1px dashed #d1d4cd; border-radius:999px; padding:1px 7px; cursor:help; }
|
||||
/* ★ THE PANEL OWNS ITS OWN SCROLL, and this is load-bearing, not polish. VerticalTabsLayout is
|
||||
grid-template-rows:auto 1fr — the header (with #toolbar inside it) is the AUTO row and the body
|
||||
is 1fr — so a tall toolbar does not overflow, it SILENTLY SHRINKS THE PREVIEW. plugin-studio
|
||||
composes 10 children and doxa-maps-spa 6; ten un-capped rows would take ~330px straight out of
|
||||
the map below. Capping at ~4.5 rows keeps the panel a header, not a page: the count in the
|
||||
heading always tells the true total even when the list is scrolled. */
|
||||
.ps-composed-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px; max-height:154px; overflow-y:auto; overscroll-behavior:contain; }
|
||||
.ps-composed-row { display:flex; align-items:center; gap:8px; width:100%; text-align:left; font:inherit; font-size:13px; padding:6px 8px; border:1px solid transparent; border-radius:8px; background:#f3f3f1; color:#3b463d; cursor:pointer; }
|
||||
.ps-composed-row:hover { background:#d1d4cd; border-color:#d1d4cd; }
|
||||
.ps-composed-row:focus-visible { outline:2px solid #92b195; outline-offset:1px; }
|
||||
.ps-composed-name { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.ps-composed-tag { flex:1 1 auto; min-width:0; font:11px/1.35 ui-monospace,Menlo,monospace; color:#6d766e; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.ps-composed-badge { flex:0 0 auto; font-size:10px; font-weight:600; letter-spacing:.04em; padding:3px 7px; border:1px solid #d1d4cd; border-radius:999px; background:#ffffff; color:#6d766e; white-space:nowrap; }
|
||||
.ps-composed-badge[data-cap="5"] { border-color:#92b195; color:#3b463d; background:#f3f3f1; }
|
||||
.ps-composed-badge[data-cap="4"] { border-color:#92b195; color:#3b463d; }
|
||||
.ps-embed { display:flex; flex-direction:column; gap:6px; padding:8px; border:1px solid #d1d4cd; border-radius:8px; background:#f3f3f1; }
|
||||
.ps-embed label { font-size:12px; color:#6d766e; display:flex; justify-content:space-between; align-items:center; }
|
||||
.ps-embed textarea { width:100%; height:58px; font:11px/1.35 ui-monospace,Menlo,monospace; border:1px solid #d1d4cd; border-radius:6px; background:#ffffff; color:#3b463d; padding:6px; resize:vertical; }
|
||||
|
|
@ -294,10 +248,6 @@ const capFor = (t) => (PT[t] != null ? 5 - PT[t] : 1)
|
|||
// These were emoji until 2026-09-24, and on a box with no emoji font (measured: this one) every rail
|
||||
// row led with an empty tofu box — the first thing a developer saw on every plugin.
|
||||
const CAP_ICON = { 5: 'monitor', 4: 'tree', 3: 'layers', 2: 'grid', 1: 'embed' }
|
||||
// The same five names, shortened to fit a badge beside a child's name in the Composed-of panel.
|
||||
// Same vocabulary as CAP_LABEL, just the distinguishing word — "App shell", not "App shell ·
|
||||
// nested-mfe-spa" — because the badge sits inline and the full label would wrap every row.
|
||||
const CAP_SHORT = { 5: 'App shell', 4: 'Composite', 3: 'Multi-view', 2: 'Preset', 1: 'Single' }
|
||||
|
||||
// MAP ELEMENTS. These own a live WebGL context, so they are the rows the studio DESTROYS on
|
||||
// deactivate (see HEAVY_ELEMENTS use in the template + select()). They are NOT iframed: every
|
||||
|
|
@ -334,6 +284,14 @@ const HEAVY_ELEMENTS = new Set([
|
|||
'country-posters-map', 'key-cities-map', 'research-map-plus-grid'
|
||||
])
|
||||
|
||||
// SUPERSEDED BUNDLES — built, still embeddable, but NOT shown in the studio. big-picture is a dark
|
||||
// header of search + four filters over the people-groups grid with the grid's own (identical)
|
||||
// controls switched off: to a developer it is the grid a second time under a misleading name. The
|
||||
// Driver, 2026-09-24: "there should not be people groups grid twice … this is just a clone of people
|
||||
// groups grid". The real Big Picture is dashboard-plus-grid (the app shell's first tab), and
|
||||
// doxa-maps-spa's NOT_EXPERIMENTAL already hides big-picture for the same reason.
|
||||
const STUDIO_HIDDEN = new Set(['big-picture'])
|
||||
|
||||
// Load a bundle exactly once per URL; resolves after its define() has run.
|
||||
//
|
||||
// ★ A CLASSIC SCRIPT FIRES onload AND THEN FAILS, for a dev-server bundle. Measured 2026-09-22:
|
||||
|
|
@ -445,163 +403,54 @@ export default defineComponent({
|
|||
.filter(c => byCap[c] && byCap[c].length)
|
||||
.map(c => ({ cap: c, label: CAP_LABEL[c] || ('capability ' + c), rows: byCap[c] }))
|
||||
},
|
||||
// ★ COMPOSED OF — the composition model for the ACTIVE plugin only. One entry per DIRECTLY
|
||||
// nested child. Empty for a leaf, which is how the panel disappears for a non-composite.
|
||||
// The GROUPED rail model the shared VerticalTabsLayout paints: ONE list, highest capability to
|
||||
// lowest (Driver, 2026-09-24: "everything in studio organized by highest capability to lowest";
|
||||
// "the composability in the capability sidebar vertical tabs … to understand the interoperability
|
||||
// and the various elements that go into each plugin").
|
||||
//
|
||||
// ★ THE PANEL IS THE DEPTH-1 VIEW; the rail's composition trees (see compositionTrees) are the
|
||||
// full-depth one. The trees came back on 2026-09-24 as ALIAS rows — the reasoning below is why
|
||||
// they must never again be independent nodes.
|
||||
// ★ WHY COMPOSITION IS NOT SEPARATE NAVIGATION (researched over Storybook, Bit, Backstage, Figma,
|
||||
// Nx and Omlet, 2026-09-23). EVERY mature system SEPARATES THE NAVIGATION TREE FROM THE COMPOSITION GRAPH. None
|
||||
// of the six encodes "what composes what" as folder nesting, for one reason: a tree answers
|
||||
// WHERE DO I FIND IT, and it can only answer that if each thing lives in exactly one place.
|
||||
// What is INSIDE an entity is a different question and gets its own per-entity surface.
|
||||
// - Figma / Bit: a component has ONE canonical home; uses are references to it.
|
||||
// - Backstage: composition renders as a RELATIONS panel on an entity page, never as a
|
||||
// subfolder of the catalog tree.
|
||||
// - Storybook: the sidebar is capped at Category / Group / Component — two levels, as ours is.
|
||||
//
|
||||
// ★ AND WHY A MIRRORED CHILD MUST BE AN ALIAS. The cb07c55 folders listed each child inside every
|
||||
// composite that nested it as its own node: people-groups-grid appeared in FIVE rail folders
|
||||
// plus its own capability tier — six nav nodes for one plugin. No code was duplicated, but
|
||||
// IDENTITY was, which is precisely the "I can't name the plugin I'm reporting" problem. These
|
||||
// rows are LINKS to the one canonical entry (they call select() on the child's real key), so
|
||||
// the rail keeps exactly one node per plugin and this panel keeps the relationship.
|
||||
//
|
||||
// ★ maxDepth 1, deliberately. A nested cap-4 inside the cap-5 shell shows as ONE row here; it
|
||||
// does NOT expand its own children. Its breakdown is one click away, on its own page, where it
|
||||
// belongs. Anything deeper is a graph view, which is not a landing surface.
|
||||
//
|
||||
// ★ ONLY 8 of 21 bundles declare edges, and that is FINE — it is not a blocker and NOTHING here
|
||||
// fabricates one. Backstage runs at Spotify scale on hand-declared relations and its docs say
|
||||
// plainly that it "only displays relationships that have already been set up". A bundle with no
|
||||
// `composes` simply has no panel.
|
||||
//
|
||||
// ★ RESOLVING A `composes` ENTRY TO A ROW. Measured 2026-09-23: the manifest's `composes` arrays
|
||||
// are NOT one consistent shape —
|
||||
// - most entries are an ELEMENT TAG e.g. "people-groups-grid", "doxa-research-map"
|
||||
// - some (my-upg-100-list's and regions-dashboard's children) are a "<bundle>/<profile>" ref
|
||||
// that equals a ROW KEY verbatim: "my-upg-100-list/upg-100-list"
|
||||
// - one entry ("research-dashboard") is a BUNDLE name whose element tag is actually
|
||||
// "research-dashboard-map" — tag lookup misses, bundle lookup hits
|
||||
// so resolution tries, in order: exact row KEY → row TAG → row BUNDLE. First match wins (a tag
|
||||
// like "cities-internet-map" is shared by three profiles of one bundle; any one of them correctly
|
||||
// represents that child here). An unmatchable ref is DROPPED rather than rendered broken — a
|
||||
// manifest typo must cost one row, never the panel.
|
||||
composition() {
|
||||
const a = this.active
|
||||
const empty = { children: [], unresolved: [] }
|
||||
if (!a) return empty
|
||||
const b = this.manifestBundles.find(x => x.bundle === a.bundle)
|
||||
const refs = b && b.composes
|
||||
if (!refs || !refs.length) return empty
|
||||
const unresolved = []
|
||||
const seen = new Set()
|
||||
const out = []
|
||||
for (const ref of refs) {
|
||||
const row = this.resolveRef(ref, a.bundle)
|
||||
// ★ AN UNRESOLVABLE REF IS COUNTED, NOT SILENTLY SWALLOWED. Measured 2026-09-23:
|
||||
// big-picture declares composes:["bp-research-header","people-groups-grid"] but
|
||||
// "bp-research-header" is not a bundle, not any profile's element, and not any row key —
|
||||
// it is an internal sub-component of big-picture that was hand-declared here but never
|
||||
// published as its own plugin. Dropping the row is right (a dead link is worse than no
|
||||
// link), but rendering "1 nested element" under a manifest that declares 2 is a quiet
|
||||
// lie, and a quiet lie on the studio's own legibility surface is the exact failure this
|
||||
// panel exists to prevent. unresolved[] lets the heading say so out loud.
|
||||
if (!row) { unresolved.push(ref); continue }
|
||||
// Skip a self-reference and any ref that resolves to a row already listed: a panel that
|
||||
// named the same child twice would re-introduce, inside one screen, the identity problem
|
||||
// this whole surface exists to remove.
|
||||
if (row.key === a.key || seen.has(row.key)) continue
|
||||
seen.add(row.key)
|
||||
// ★ THE BADGE READS THE MANIFEST'S DECLARED TYPE, NEVER A HARDCODED MAP. row.cap was
|
||||
// produced by capFor(p.type) in rowsFromManifest straight from the manifest's profile
|
||||
// `type`, so retyping a profile in the manifest moves its badge with zero edits here. This
|
||||
// per-child badge is the one thing none of the six reference systems have — Backstage's
|
||||
// relations panel names a child but not its tier — so it has to be derived, not asserted.
|
||||
out.push({
|
||||
key: row.key,
|
||||
label: row.label,
|
||||
tag: row.tag,
|
||||
cap: row.cap,
|
||||
capLabel: CAP_LABEL[row.cap] || ('capability ' + row.cap),
|
||||
capShort: CAP_SHORT[row.cap] || ('cap ' + row.cap)
|
||||
})
|
||||
}
|
||||
return { children: out, unresolved }
|
||||
},
|
||||
composedOf() { return this.composition.children },
|
||||
// Refs the manifest declares that resolve to no published plugin (see composition). Named in
|
||||
// the heading so the panel's count can never quietly disagree with the manifest.
|
||||
composedUnresolved() { return this.composition.unresolved },
|
||||
// Unique per active row so the list's aria-labelledby always points at THIS panel's heading.
|
||||
composedHeadingId() { return 'ps-composed-h' },
|
||||
// The GROUPED rail model the shared VerticalTabsLayout paints, in two parts (Driver, 2026-09-24:
|
||||
// "clear capability sections 5→1, every plugin reachable, composition tree legible"):
|
||||
//
|
||||
// 1. CAPABILITY SECTIONS, CAP 5 → CAP 1 — where each plugin LIVES. Exactly one node per plugin,
|
||||
// so every plugin has one name to report it by. Collapsible folders, open by default.
|
||||
// 2. COMPOSITION TREES — what each composite is MADE OF. One collapsible section per composite
|
||||
// (closed by default so the rail stays short), headed by the composite itself, with every
|
||||
// element nested into it drawn as an indented tree, ordered by capability at each level.
|
||||
// A child repeats in every section that nests it — the Driver's spec: "duplication across
|
||||
// sections is intended", because the question here is "what composes THIS plugin".
|
||||
// CAP 5 → CAP 1 sections, collapsible, open by default. Every plugin has exactly ONE canonical
|
||||
// row, in its own tier. Directly under a COMPOSITE's row sits its composition tree: every element
|
||||
// nested into it, indented by depth, ordered by capability at each level. So reading down the
|
||||
// rail never jumps back up the ladder — the tree lives with the plugin it explains.
|
||||
//
|
||||
// ★ HOW THE REPEATS STAY ONE IDENTITY. Every tree row is an ALIAS (VerticalTabsLayout `selects`):
|
||||
// clicking people-groups-grid under big-picture selects the SAME canonical row as clicking it in
|
||||
// CAP 2, highlights both, and shows the same Composed-of panel. afe6abe removed the earlier rail
|
||||
// folders because six independent nodes for one plugin meant there was no single name to report
|
||||
// it by; an alias has no separate identity, so the tree and the one-name rule now coexist.
|
||||
// people-groups-grid under dashboard-plus-grid selects the SAME canonical row as its own CAP 2
|
||||
// entry and highlights both, and arrow keys skip aliases. afe6abe removed the first rail folders
|
||||
// because six independent nodes for one plugin left no single name to report it by; an alias has
|
||||
// no separate identity, so nesting and the one-name rule coexist. The section count counts
|
||||
// canonical plugins only.
|
||||
navGroups() {
|
||||
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
const rowModel = (r) => ({
|
||||
key: r.key,
|
||||
label: r.label,
|
||||
icon: r.icon,
|
||||
sub: '<code><' + esc(r.tag) + '></code>'
|
||||
})
|
||||
const tiers = this.groups.map(g => ({
|
||||
const treeOf = new Map(this.compositionTrees.map(t => [t.nodes[0].selects, t]))
|
||||
const rowsFor = (r) => {
|
||||
const out = [{ key: r.key, label: r.label, icon: r.icon, sub: '<code><' + esc(r.tag) + '></code>' }]
|
||||
const t = treeOf.get(r.key)
|
||||
if (t) {
|
||||
t.nodes.slice(1).forEach((n, i) => out.push({
|
||||
key: 'tree-' + r.key + '-' + i,
|
||||
selects: n.selects || ('none:' + r.key),
|
||||
depth: n.depth,
|
||||
label: n.label,
|
||||
badge: n.part ? 'part' : '',
|
||||
sub: n.part ? 'no standalone page' : 'CAP ' + n.cap + ' · <code><' + esc(n.tag) + '></code>'
|
||||
}))
|
||||
}
|
||||
return out
|
||||
}
|
||||
return this.groups.map(g => ({
|
||||
key: 'cap-' + g.cap,
|
||||
badge: 'CAP ' + g.cap,
|
||||
label: g.label,
|
||||
count: g.rows.length,
|
||||
folder: true,
|
||||
open: true,
|
||||
rows: g.rows.map(rowModel)
|
||||
rows: g.rows.flatMap(rowsFor)
|
||||
}))
|
||||
// A composite is named by its BUNDLE here, not its profile: "Inside my-upg-100-list" says which
|
||||
// plugin you are looking into; "Inside upg-100-mfe" made you already know the answer.
|
||||
const trees = this.compositionTrees.map(t => ({
|
||||
key: 'tree-' + t.bundle,
|
||||
label: 'Inside ' + t.bundle,
|
||||
count: t.nodes.length - 1,
|
||||
folder: true,
|
||||
open: false,
|
||||
rows: t.nodes.map((n, i) => ({
|
||||
key: 'tree-' + t.bundle + '-' + i,
|
||||
// An empty selects (the studio's own head, or a part inside it) still gets a key no row
|
||||
// has, so it stays an alias: click is a no-op and arrow keys skip it.
|
||||
selects: n.selects || ('none:' + t.bundle),
|
||||
depth: n.depth || null,
|
||||
label: n.label,
|
||||
badge: n.part ? 'part' : '',
|
||||
sub: n.part
|
||||
? 'no standalone page'
|
||||
: 'CAP ' + n.cap + ' · <code><' + esc(n.tag) + '></code>'
|
||||
}))
|
||||
}))
|
||||
// Two row-less plain groups act as part titles, so the eye can tell "where each plugin lives"
|
||||
// from "what each composite is made of" — the two halves otherwise share one heading style.
|
||||
const title = (key, label) => ({ key, label, rows: [] })
|
||||
return [
|
||||
title('part-caps', 'Every plugin, by capability'), ...tiers,
|
||||
...(trees.length ? [title('part-trees', 'What each composite is made of'), ...trees] : [])
|
||||
]
|
||||
},
|
||||
// ★ ONE TREE PER COMPOSITE, built ENTIRELY from the manifest's `composes` edges — no bundle names
|
||||
// or child lists live in this file, so a new composite appears here with zero edits.
|
||||
//
|
||||
// Depth is FULL, not 1 (the Composed-of panel is the depth-1 view). The Driver's acceptance test is
|
||||
// Depth is FULL. The Driver's acceptance test is
|
||||
// "can I point at a defect and NAME the single element responsible" — and a defect in the grid
|
||||
// inside big-picture inside this studio is two levels down, so a depth-1 list could not name it.
|
||||
// A child is expanded only when it is itself a composite (CAP 4+) whose bundle declares edges; a
|
||||
|
|
@ -629,31 +478,25 @@ export default defineComponent({
|
|||
kids.push(child)
|
||||
}
|
||||
kids.sort((a, b2) => b2.cap - a.cap || a.label.localeCompare(b2.label))
|
||||
for (const ref of parts) {
|
||||
nodes.push({ label: ref, part: true, selects: row.real ? row.key : '', depth: depth + 1 })
|
||||
}
|
||||
for (const k of kids) {
|
||||
nodes.push({ label: k.label, tag: k.tag, cap: k.cap, selects: k.key, depth: depth + 1 })
|
||||
if (!path.has(k.key)) walk({ ...k, real: true }, depth + 1, new Set([...path, k.key]), nodes)
|
||||
if (!path.has(k.key)) walk(k, depth + 1, new Set([...path, k.key]), nodes)
|
||||
}
|
||||
// Unpublished parts have no tier, so they sort LAST — below every capability — and open the
|
||||
// composite they live in.
|
||||
for (const ref of parts) nodes.push({ label: ref, part: true, selects: row.key, depth: depth + 1 })
|
||||
}
|
||||
for (const b of this.manifestBundles) {
|
||||
if (!b.composes || !b.composes.length) continue
|
||||
if (!b.composes || !b.composes.length || STUDIO_HIDDEN.has(b.bundle)) continue
|
||||
// The composite's own row: its highest-capability profile (rows are sorted cap desc).
|
||||
// ★ plugin-studio HAS NO ROW — it never lists itself as its own tab (mounting <plugin-studio>
|
||||
// inside itself is infinite regress). Its tree is still the one a developer most wants, so it
|
||||
// gets a head built from the manifest entry with an empty `selects`: select()'s dead-key guard
|
||||
// turns a click on it into a no-op rather than a blank pane.
|
||||
// plugin-studio has no row (it never lists itself), so it has no tree either: every row of
|
||||
// this rail already IS its composition.
|
||||
const own = this.rows.find(r => r.bundle === b.bundle)
|
||||
const p0 = (b.profiles && b.profiles[0]) || {}
|
||||
const head = own
|
||||
? { ...own, real: true }
|
||||
: { key: '', bundle: b.bundle, label: p0.profile || b.bundle, tag: p0.element || b.bundle, cap: capFor(p0.type || 'single'), real: false }
|
||||
const nodes = [{ label: head.label, tag: head.tag, cap: head.cap, selects: head.key, depth: 0 }]
|
||||
walk(head, 0, new Set([head.key]), nodes)
|
||||
if (nodes.length > 1) out.push({ bundle: b.bundle, label: head.label, cap: head.cap, nodes })
|
||||
if (!own) continue
|
||||
const nodes = [{ label: own.label, tag: own.tag, cap: own.cap, selects: own.key, depth: 0 }]
|
||||
walk(own, 0, new Set([own.key]), nodes)
|
||||
if (nodes.length > 1) out.push({ bundle: b.bundle, nodes })
|
||||
}
|
||||
out.sort((a, b) => b.cap - a.cap || a.bundle.localeCompare(b.bundle))
|
||||
return out
|
||||
},
|
||||
active() { return this.rows.find(r => r.key === this.activeKey) || null },
|
||||
|
|
@ -674,8 +517,8 @@ export default defineComponent({
|
|||
this.loadManifest()
|
||||
},
|
||||
methods: {
|
||||
// Resolve one `composes` entry to a studio row. The manifest's refs are NOT one shape (see
|
||||
// composition): an element TAG, a "<bundle>/<profile>" row KEY, a BUNDLE name whose tag differs,
|
||||
// Resolve one `composes` entry to a studio row. The manifest's refs are NOT one shape: an
|
||||
// element TAG, a "<bundle>/<profile>" row KEY, a BUNDLE name whose tag differs,
|
||||
// or a bare SIBLING profile name (gospel-resources composes "resources-map", "data-tables" — its
|
||||
// own profiles). Try sibling → key → tag → bundle; first match wins. null = published nowhere.
|
||||
resolveRef(ref, fromBundle) {
|
||||
|
|
@ -747,7 +590,7 @@ export default defineComponent({
|
|||
const out = []
|
||||
for (const b of (m.bundles || [])) {
|
||||
// never list the studio itself as one of its own tabs
|
||||
if (b.bundle === 'plugin-studio') continue
|
||||
if (b.bundle === 'plugin-studio' || STUDIO_HIDDEN.has(b.bundle)) continue
|
||||
for (const p of (b.profiles || [])) {
|
||||
const tag = p.element || (b.elements && b.elements[0]) || b.bundle
|
||||
const cap = capFor(p.type || 'single')
|
||||
|
|
@ -949,24 +792,6 @@ export default defineComponent({
|
|||
.ps-bar-sub { display: block; font-size: 12px; color: #6d766e; }
|
||||
.ps-btn { font: inherit; font-size: 12px; padding: 5px 10px; border: 1px solid #d1d4cd; border-radius: 8px; background: #d1d4cd; color: #3b463d; cursor: pointer; white-space: nowrap; }
|
||||
.ps-btn:hover { background: #d1d4cd; }
|
||||
/* COMPOSED OF panel — mirror of the injected copy above. One row per directly-nested child, with a
|
||||
capability badge derived from the manifest's declared profile type. Keep the two copies in sync. */
|
||||
.ps-composed { border: 1px solid #d1d4cd; border-radius: 10px; background: #ffffff; padding: 9px 10px; }
|
||||
.ps-composed-h { margin: 0 0 7px; font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: #6d766e; display: flex; align-items: baseline; gap: 8px; }
|
||||
.ps-composed-n { font-weight: 400; letter-spacing: 0; text-transform: none; font-size: 11px; color: #6d766e; }
|
||||
.ps-composed-warn { font-weight: 400; letter-spacing: 0; text-transform: none; font-size: 11px; color: #6d766e; border: 1px dashed #d1d4cd; border-radius: 999px; padding: 1px 7px; cursor: help; }
|
||||
/* THE PANEL OWNS ITS OWN SCROLL — keep in sync with the injected copy above. The layout's header
|
||||
row is `auto`, so an uncapped 10-row panel does not overflow, it shrinks the preview beneath it
|
||||
by ~330px. The heading's count stays truthful when the list is scrolled. */
|
||||
.ps-composed-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; max-height: 154px; overflow-y: auto; overscroll-behavior: contain; }
|
||||
.ps-composed-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; font: inherit; font-size: 13px; padding: 6px 8px; border: 1px solid transparent; border-radius: 8px; background: #f3f3f1; color: #3b463d; cursor: pointer; }
|
||||
.ps-composed-row:hover { background: #d1d4cd; border-color: #d1d4cd; }
|
||||
.ps-composed-row:focus-visible { outline: 2px solid #92b195; outline-offset: 1px; }
|
||||
.ps-composed-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.ps-composed-tag { flex: 1 1 auto; min-width: 0; font: 11px/1.35 ui-monospace, Menlo, monospace; color: #6d766e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.ps-composed-badge { flex: 0 0 auto; font-size: 10px; font-weight: 600; letter-spacing: .04em; padding: 3px 7px; border: 1px solid #d1d4cd; border-radius: 999px; background: #ffffff; color: #6d766e; white-space: nowrap; }
|
||||
.ps-composed-badge[data-cap="5"] { border-color: #92b195; color: #3b463d; background: #f3f3f1; }
|
||||
.ps-composed-badge[data-cap="4"] { border-color: #92b195; color: #3b463d; }
|
||||
.ps-embed { display: flex; flex-direction: column; gap: 6px; padding: 8px; border: 1px solid #d1d4cd; border-radius: 8px; background: #f3f3f1; }
|
||||
.ps-embed label { font-size: 12px; color: #6d766e; display: flex; justify-content: space-between; align-items: center; }
|
||||
.ps-embed textarea { width: 100%; height: 58px; font: 11px/1.35 ui-monospace, Menlo, monospace; border: 1px solid #d1d4cd; border-radius: 6px; background: #ffffff; color: #3b463d; padding: 6px; resize: vertical; }
|
||||
|
|
|
|||
2
public/assets/tables/gospel-gaps.json
vendored
2
public/assets/tables/gospel-gaps.json
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/assets/tables/people-group-pins.json
vendored
2
public/assets/tables/people-group-pins.json
vendored
File diff suppressed because one or more lines are too long
1004
public/doxa-maps/assets/tables/country-gap.json
generated
1004
public/doxa-maps/assets/tables/country-gap.json
generated
File diff suppressed because it is too large
Load diff
280
public/doxa-maps/assets/tables/gospel-by-country.json
generated
280
public/doxa-maps/assets/tables/gospel-by-country.json
generated
|
|
@ -14,29 +14,29 @@
|
|||
"country": "People's Republic of China",
|
||||
"region": "Asia",
|
||||
"groups": 391,
|
||||
"no_resource": 125,
|
||||
"pop_no_resource": 150271700,
|
||||
"no_resource": 111,
|
||||
"pop_no_resource": 128103100,
|
||||
"scripture": 118,
|
||||
"film": 227,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 274.2,
|
||||
"growth": -0.12,
|
||||
"pct_no_resource": 32,
|
||||
"band": "high"
|
||||
"pct_no_resource": 28.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "LAO",
|
||||
"country": "Lao People's Democratic Republic",
|
||||
"region": "Asia",
|
||||
"groups": 158,
|
||||
"no_resource": 79,
|
||||
"pop_no_resource": 1306185,
|
||||
"no_resource": 72,
|
||||
"pop_no_resource": 982785,
|
||||
"scripture": 55,
|
||||
"film": 58,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 15.5,
|
||||
"growth": null,
|
||||
"pct_no_resource": 50,
|
||||
"pct_no_resource": 45.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -44,14 +44,14 @@
|
|||
"country": "India",
|
||||
"region": "Asia",
|
||||
"groups": 2146,
|
||||
"no_resource": 63,
|
||||
"pop_no_resource": 2157770,
|
||||
"no_resource": 55,
|
||||
"pop_no_resource": 1320695,
|
||||
"scripture": 1757,
|
||||
"film": 2044,
|
||||
"growing_unreached": 38,
|
||||
"deaths_day": 721.2,
|
||||
"growth": 0.89,
|
||||
"pct_no_resource": 2.9,
|
||||
"pct_no_resource": 2.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -74,14 +74,14 @@
|
|||
"country": "Chad",
|
||||
"region": "Africa",
|
||||
"groups": 150,
|
||||
"no_resource": 79,
|
||||
"pop_no_resource": 4813200,
|
||||
"no_resource": 69,
|
||||
"pop_no_resource": 2565700,
|
||||
"scripture": 46,
|
||||
"film": 49,
|
||||
"growing_unreached": 37,
|
||||
"deaths_day": 68.4,
|
||||
"growth": 4.95,
|
||||
"pct_no_resource": 52.7,
|
||||
"pct_no_resource": 46,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -89,14 +89,14 @@
|
|||
"country": "Sudan",
|
||||
"region": "Africa",
|
||||
"groups": 185,
|
||||
"no_resource": 62,
|
||||
"pop_no_resource": 3220900,
|
||||
"no_resource": 58,
|
||||
"pop_no_resource": 2809900,
|
||||
"scripture": 96,
|
||||
"film": 117,
|
||||
"growing_unreached": 36,
|
||||
"deaths_day": 359.6,
|
||||
"growth": 0.81,
|
||||
"pct_no_resource": 33.5,
|
||||
"pct_no_resource": 31.4,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -104,14 +104,14 @@
|
|||
"country": "Nigeria",
|
||||
"region": "Africa",
|
||||
"groups": 433,
|
||||
"no_resource": 187,
|
||||
"pop_no_resource": 8978825,
|
||||
"no_resource": 180,
|
||||
"pop_no_resource": 6759525,
|
||||
"scripture": 105,
|
||||
"film": 188,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 44,
|
||||
"growth": null,
|
||||
"pct_no_resource": 43.2,
|
||||
"pct_no_resource": 41.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -119,14 +119,14 @@
|
|||
"country": "Indonesia",
|
||||
"region": "Asia",
|
||||
"groups": 669,
|
||||
"no_resource": 321,
|
||||
"pop_no_resource": 5140960,
|
||||
"no_resource": 320,
|
||||
"pop_no_resource": 4968960,
|
||||
"scripture": 222,
|
||||
"film": 265,
|
||||
"growing_unreached": 23,
|
||||
"deaths_day": 47.2,
|
||||
"growth": 0.81,
|
||||
"pct_no_resource": 48,
|
||||
"pct_no_resource": 47.8,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -164,14 +164,14 @@
|
|||
"country": "Bhutan",
|
||||
"region": "Asia",
|
||||
"groups": 36,
|
||||
"no_resource": 19,
|
||||
"pop_no_resource": 238225,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 233625,
|
||||
"scripture": 11,
|
||||
"film": 16,
|
||||
"growing_unreached": 13,
|
||||
"deaths_day": 2.1,
|
||||
"growth": 0.65,
|
||||
"pct_no_resource": 52.8,
|
||||
"pct_no_resource": 50,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -194,14 +194,14 @@
|
|||
"country": "Vietnam",
|
||||
"region": "Asia",
|
||||
"groups": 74,
|
||||
"no_resource": 29,
|
||||
"pop_no_resource": 1860175,
|
||||
"no_resource": 27,
|
||||
"pop_no_resource": 1540175,
|
||||
"scripture": 28,
|
||||
"film": 38,
|
||||
"growing_unreached": 11,
|
||||
"deaths_day": 5.7,
|
||||
"growth": 0.63,
|
||||
"pct_no_resource": 39.2,
|
||||
"pct_no_resource": 36.5,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -209,14 +209,14 @@
|
|||
"country": "Islamic Republic of Iran",
|
||||
"region": "Asia",
|
||||
"groups": 47,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 625400,
|
||||
"no_resource": 10,
|
||||
"pop_no_resource": 445400,
|
||||
"scripture": 27,
|
||||
"film": 32,
|
||||
"growing_unreached": 10,
|
||||
"deaths_day": 55.3,
|
||||
"growth": 1.05,
|
||||
"pct_no_resource": 23.4,
|
||||
"pct_no_resource": 21.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -239,14 +239,14 @@
|
|||
"country": "South Sudan",
|
||||
"region": "Africa",
|
||||
"groups": 80,
|
||||
"no_resource": 22,
|
||||
"pop_no_resource": 577100,
|
||||
"no_resource": 20,
|
||||
"pop_no_resource": 546600,
|
||||
"scripture": 39,
|
||||
"film": 52,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 59,
|
||||
"growth": 3.93,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -254,14 +254,14 @@
|
|||
"country": "Mali",
|
||||
"region": "Africa",
|
||||
"groups": 40,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 1066800,
|
||||
"no_resource": 10,
|
||||
"pop_no_resource": 1000800,
|
||||
"scripture": 20,
|
||||
"film": 26,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 54.8,
|
||||
"growth": 2.94,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -269,14 +269,14 @@
|
|||
"country": "Malaysia",
|
||||
"region": "Asia",
|
||||
"groups": 160,
|
||||
"no_resource": 67,
|
||||
"pop_no_resource": 2650950,
|
||||
"no_resource": 65,
|
||||
"pop_no_resource": 1918550,
|
||||
"scripture": 75,
|
||||
"film": 68,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 8.2,
|
||||
"growth": 1.22,
|
||||
"pct_no_resource": 41.9,
|
||||
"pct_no_resource": 40.6,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -284,14 +284,14 @@
|
|||
"country": "Cameroon",
|
||||
"region": "Africa",
|
||||
"groups": 260,
|
||||
"no_resource": 129,
|
||||
"pop_no_resource": 3656425,
|
||||
"no_resource": 126,
|
||||
"pop_no_resource": 3210925,
|
||||
"scripture": 72,
|
||||
"film": 84,
|
||||
"growing_unreached": 8,
|
||||
"deaths_day": 3.4,
|
||||
"growth": 2.61,
|
||||
"pct_no_resource": 49.6,
|
||||
"pct_no_resource": 48.5,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -299,14 +299,14 @@
|
|||
"country": "Thailand",
|
||||
"region": "Asia",
|
||||
"groups": 62,
|
||||
"no_resource": 16,
|
||||
"pop_no_resource": 1528800,
|
||||
"no_resource": 15,
|
||||
"pop_no_resource": 1069800,
|
||||
"scripture": 34,
|
||||
"film": 37,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 12.6,
|
||||
"growth": -0.05,
|
||||
"pct_no_resource": 25.8,
|
||||
"pct_no_resource": 24.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -359,14 +359,14 @@
|
|||
"country": "Nepal",
|
||||
"region": "Asia",
|
||||
"groups": 171,
|
||||
"no_resource": 47,
|
||||
"pop_no_resource": 1259050,
|
||||
"no_resource": 46,
|
||||
"pop_no_resource": 1035050,
|
||||
"scripture": 97,
|
||||
"film": 111,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 34,
|
||||
"growth": null,
|
||||
"pct_no_resource": 27.5,
|
||||
"pct_no_resource": 26.9,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -389,14 +389,14 @@
|
|||
"country": "Burkina Faso",
|
||||
"region": "Africa",
|
||||
"groups": 76,
|
||||
"no_resource": 20,
|
||||
"pop_no_resource": 1500700,
|
||||
"no_resource": 17,
|
||||
"pop_no_resource": 727700,
|
||||
"scripture": 41,
|
||||
"film": 37,
|
||||
"growing_unreached": 6,
|
||||
"deaths_day": 13.4,
|
||||
"growth": 2.25,
|
||||
"pct_no_resource": 26.3,
|
||||
"pct_no_resource": 22.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -449,14 +449,14 @@
|
|||
"country": "Democratic Republic of the Congo",
|
||||
"region": "Africa",
|
||||
"groups": 215,
|
||||
"no_resource": 105,
|
||||
"pop_no_resource": 12428000,
|
||||
"no_resource": 89,
|
||||
"pop_no_resource": 7568000,
|
||||
"scripture": 50,
|
||||
"film": 95,
|
||||
"growing_unreached": 5,
|
||||
"deaths_day": 10.9,
|
||||
"growth": 3.24,
|
||||
"pct_no_resource": 48.8,
|
||||
"pct_no_resource": 41.4,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -479,14 +479,14 @@
|
|||
"country": "United Republic of Tanzania",
|
||||
"region": "Africa",
|
||||
"groups": 132,
|
||||
"no_resource": 22,
|
||||
"pop_no_resource": 4760400,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 3289400,
|
||||
"scripture": 81,
|
||||
"film": 101,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 126.9,
|
||||
"growth": 2.87,
|
||||
"pct_no_resource": 16.7,
|
||||
"pct_no_resource": 13.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -509,14 +509,14 @@
|
|||
"country": "Benin",
|
||||
"region": "Africa",
|
||||
"groups": 57,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 500000,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 277000,
|
||||
"scripture": 29,
|
||||
"film": 38,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 21.9,
|
||||
"growth": 2.46,
|
||||
"pct_no_resource": 7,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -524,14 +524,14 @@
|
|||
"country": "Libya",
|
||||
"region": "Africa",
|
||||
"groups": 27,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 329300,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 317800,
|
||||
"scripture": 16,
|
||||
"film": 20,
|
||||
"growing_unreached": 4,
|
||||
"deaths_day": 0,
|
||||
"growth": 1.03,
|
||||
"pct_no_resource": 22.2,
|
||||
"pct_no_resource": 18.5,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -569,15 +569,15 @@
|
|||
"country": "Comoros",
|
||||
"region": "Africa",
|
||||
"groups": 11,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 445500,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 45500,
|
||||
"scripture": 6,
|
||||
"film": 6,
|
||||
"growing_unreached": 3,
|
||||
"deaths_day": 8.3,
|
||||
"growth": 1.89,
|
||||
"pct_no_resource": 36.4,
|
||||
"band": "high"
|
||||
"pct_no_resource": 27.3,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "BRN",
|
||||
|
|
@ -629,14 +629,14 @@
|
|||
"country": "Mozambique",
|
||||
"region": "Africa",
|
||||
"groups": 49,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 2882000,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 1032000,
|
||||
"scripture": 31,
|
||||
"film": 41,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 26.4,
|
||||
"growth": 2.92,
|
||||
"pct_no_resource": 12.2,
|
||||
"pct_no_resource": 8.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -659,14 +659,14 @@
|
|||
"country": "Uganda",
|
||||
"region": "Africa",
|
||||
"groups": 54,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 2970200,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 460200,
|
||||
"scripture": 38,
|
||||
"film": 46,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 23,
|
||||
"growth": 2.75,
|
||||
"pct_no_resource": 11.1,
|
||||
"pct_no_resource": 7.4,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -674,14 +674,14 @@
|
|||
"country": "Peru",
|
||||
"region": "Americas",
|
||||
"groups": 90,
|
||||
"no_resource": 12,
|
||||
"pop_no_resource": 1160875,
|
||||
"no_resource": 11,
|
||||
"pop_no_resource": 660875,
|
||||
"scripture": 73,
|
||||
"film": 57,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 5.4,
|
||||
"growth": 1.09,
|
||||
"pct_no_resource": 13.3,
|
||||
"pct_no_resource": 12.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -704,14 +704,14 @@
|
|||
"country": "Niger",
|
||||
"region": "Africa",
|
||||
"groups": 25,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 1153500,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 1138500,
|
||||
"scripture": 16,
|
||||
"film": 18,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 2.1,
|
||||
"growth": 3.28,
|
||||
"pct_no_resource": 20,
|
||||
"pct_no_resource": 16,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -749,14 +749,14 @@
|
|||
"country": "Sweden",
|
||||
"region": "Europe",
|
||||
"groups": 32,
|
||||
"no_resource": 6,
|
||||
"pop_no_resource": 65500,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 62500,
|
||||
"scripture": 26,
|
||||
"film": 26,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 1.1,
|
||||
"growth": 0.31,
|
||||
"pct_no_resource": 18.8,
|
||||
"pct_no_resource": 15.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -764,14 +764,14 @@
|
|||
"country": "Republic of The Gambia",
|
||||
"region": "Africa",
|
||||
"groups": 17,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 430000,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 35000,
|
||||
"scripture": 6,
|
||||
"film": 13,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 0.6,
|
||||
"growth": 2.28,
|
||||
"pct_no_resource": 23.5,
|
||||
"pct_no_resource": 17.6,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -779,14 +779,14 @@
|
|||
"country": "Republic of the Congo",
|
||||
"region": "Africa",
|
||||
"groups": 64,
|
||||
"no_resource": 30,
|
||||
"pop_no_resource": 1344200,
|
||||
"no_resource": 27,
|
||||
"pop_no_resource": 907200,
|
||||
"scripture": 16,
|
||||
"film": 27,
|
||||
"growing_unreached": 2,
|
||||
"deaths_day": 1,
|
||||
"growth": 2.4,
|
||||
"pct_no_resource": 46.9,
|
||||
"pct_no_resource": 42.2,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -959,14 +959,14 @@
|
|||
"country": "Cote d'Ivoire",
|
||||
"region": "Africa",
|
||||
"groups": 83,
|
||||
"no_resource": 21,
|
||||
"pop_no_resource": 1167500,
|
||||
"no_resource": 19,
|
||||
"pop_no_resource": 541500,
|
||||
"scripture": 44,
|
||||
"film": 53,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 3.7,
|
||||
"growth": 2.44,
|
||||
"pct_no_resource": 25.3,
|
||||
"pct_no_resource": 22.9,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -1094,14 +1094,14 @@
|
|||
"country": "Central African Republic",
|
||||
"region": "Africa",
|
||||
"groups": 69,
|
||||
"no_resource": 34,
|
||||
"pop_no_resource": 1496250,
|
||||
"no_resource": 31,
|
||||
"pop_no_resource": 1025250,
|
||||
"scripture": 16,
|
||||
"film": 28,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 0.6,
|
||||
"growth": 3.4,
|
||||
"pct_no_resource": 49.3,
|
||||
"pct_no_resource": 44.9,
|
||||
"band": "high"
|
||||
},
|
||||
{
|
||||
|
|
@ -1574,15 +1574,15 @@
|
|||
"country": "Liechtenstein",
|
||||
"region": "Europe",
|
||||
"groups": 4,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 36400,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 400,
|
||||
"scripture": 2,
|
||||
"film": 2,
|
||||
"growing_unreached": 1,
|
||||
"deaths_day": 0,
|
||||
"growth": 1.5,
|
||||
"pct_no_resource": 50,
|
||||
"band": "high"
|
||||
"pct_no_resource": 25,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
"iso3": "FRO",
|
||||
|
|
@ -2009,14 +2009,14 @@
|
|||
"country": "Namibia",
|
||||
"region": "Africa",
|
||||
"groups": 29,
|
||||
"no_resource": 7,
|
||||
"pop_no_resource": 992225,
|
||||
"no_resource": 5,
|
||||
"pop_no_resource": 113225,
|
||||
"scripture": 4,
|
||||
"film": 19,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 3.3,
|
||||
"growth": 2.24,
|
||||
"pct_no_resource": 24.1,
|
||||
"pct_no_resource": 17.2,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2054,14 +2054,14 @@
|
|||
"country": "Türkiye",
|
||||
"region": "Asia",
|
||||
"groups": 40,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 1175000,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 855000,
|
||||
"scripture": 35,
|
||||
"film": 37,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 3.7,
|
||||
"growth": 0.23,
|
||||
"pct_no_resource": 5,
|
||||
"pct_no_resource": 2.5,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2144,14 +2144,14 @@
|
|||
"country": "Philippines",
|
||||
"region": "Asia",
|
||||
"groups": 156,
|
||||
"no_resource": 28,
|
||||
"pop_no_resource": 4727275,
|
||||
"no_resource": 26,
|
||||
"pop_no_resource": 3398275,
|
||||
"scripture": 108,
|
||||
"film": 90,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 0.2,
|
||||
"growth": 0.83,
|
||||
"pct_no_resource": 17.9,
|
||||
"pct_no_resource": 16.7,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2264,14 +2264,14 @@
|
|||
"country": "United States of America",
|
||||
"region": "Americas",
|
||||
"groups": 623,
|
||||
"no_resource": 41,
|
||||
"pop_no_resource": 465640,
|
||||
"no_resource": 38,
|
||||
"pop_no_resource": 462510,
|
||||
"scripture": 517,
|
||||
"film": 549,
|
||||
"growing_unreached": 0,
|
||||
"deaths_day": 0.1,
|
||||
"growth": 0.96,
|
||||
"pct_no_resource": 6.6,
|
||||
"pct_no_resource": 6.1,
|
||||
"band": "partial"
|
||||
},
|
||||
{
|
||||
|
|
@ -2772,14 +2772,14 @@
|
|||
"country": "Singapore",
|
||||
"region": "Asia",
|
||||
"groups": 38,
|
||||
"no_resource": 3,
|
||||
"pop_no_resource": 237500,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 218500,
|
||||
"scripture": 33,
|
||||
"film": 35,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 7.9,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2836,14 +2836,14 @@
|
|||
"country": "Ghana",
|
||||
"region": "Africa",
|
||||
"groups": 96,
|
||||
"no_resource": 15,
|
||||
"pop_no_resource": 2402300,
|
||||
"no_resource": 14,
|
||||
"pop_no_resource": 637300,
|
||||
"scripture": 71,
|
||||
"film": 70,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 15.6,
|
||||
"pct_no_resource": 14.6,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2868,14 +2868,14 @@
|
|||
"country": "Canada",
|
||||
"region": "Americas",
|
||||
"groups": 304,
|
||||
"no_resource": 18,
|
||||
"pop_no_resource": 183000,
|
||||
"no_resource": 16,
|
||||
"pop_no_resource": 157400,
|
||||
"scripture": 256,
|
||||
"film": 270,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 5.9,
|
||||
"pct_no_resource": 5.3,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -2900,14 +2900,14 @@
|
|||
"country": "Switzerland",
|
||||
"region": "Europe",
|
||||
"groups": 13,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 5612500,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 92500,
|
||||
"scripture": 11,
|
||||
"film": 11,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 15.4,
|
||||
"pct_no_resource": 7.7,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3140,15 +3140,15 @@
|
|||
"country": "Trinidad and Tobago",
|
||||
"region": "Americas",
|
||||
"groups": 11,
|
||||
"no_resource": 4,
|
||||
"pop_no_resource": 388500,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 176000,
|
||||
"scripture": 7,
|
||||
"film": 6,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 36.4,
|
||||
"band": "high",
|
||||
"pct_no_resource": 18.2,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
{
|
||||
|
|
@ -3156,14 +3156,14 @@
|
|||
"country": "Timor-Leste",
|
||||
"region": "Asia",
|
||||
"groups": 17,
|
||||
"no_resource": 13,
|
||||
"pop_no_resource": 704500,
|
||||
"no_resource": 12,
|
||||
"pop_no_resource": 448500,
|
||||
"scripture": 2,
|
||||
"film": 1,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 76.5,
|
||||
"pct_no_resource": 70.6,
|
||||
"band": "critical",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3252,14 +3252,14 @@
|
|||
"country": "Italy",
|
||||
"region": "Europe",
|
||||
"groups": 38,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 872000,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 600000,
|
||||
"scripture": 30,
|
||||
"film": 36,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 5.3,
|
||||
"pct_no_resource": 2.6,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
@ -3364,15 +3364,15 @@
|
|||
"country": "Ukraine",
|
||||
"region": "Europe",
|
||||
"groups": 10,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 334000,
|
||||
"no_resource": 0,
|
||||
"pop_no_resource": 0,
|
||||
"scripture": 8,
|
||||
"film": 9,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 10,
|
||||
"band": "partial",
|
||||
"pct_no_resource": 0,
|
||||
"band": "covered",
|
||||
"from_pins_only": true
|
||||
},
|
||||
{
|
||||
|
|
@ -3412,14 +3412,14 @@
|
|||
"country": "Poland",
|
||||
"region": "Europe",
|
||||
"groups": 9,
|
||||
"no_resource": 2,
|
||||
"pop_no_resource": 394500,
|
||||
"no_resource": 1,
|
||||
"pop_no_resource": 384000,
|
||||
"scripture": 7,
|
||||
"film": 7,
|
||||
"growing_unreached": null,
|
||||
"deaths_day": null,
|
||||
"growth": null,
|
||||
"pct_no_resource": 22.2,
|
||||
"pct_no_resource": 11.1,
|
||||
"band": "partial",
|
||||
"from_pins_only": true
|
||||
},
|
||||
|
|
|
|||
2
public/doxa-maps/assets/tables/gospel-gaps.json
generated
2
public/doxa-maps/assets/tables/gospel-gaps.json
generated
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"meta": {
|
||||
"title": "Gospel resource link verification",
|
||||
"generated": "2026-09-24T17:38:08.638Z",
|
||||
"generated": "2026-09-24T18:33:00.458Z",
|
||||
"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": 7491,
|
||||
"checkedThisPass": 921,
|
||||
"records": 7565,
|
||||
"checkedThisPass": 74,
|
||||
"carriedForward": 74,
|
||||
"pruned": 0,
|
||||
"broken": 74,
|
||||
"verified": 7412,
|
||||
"verified": 7486,
|
||||
"unknown": 5
|
||||
},
|
||||
"byHost": {
|
||||
|
|
@ -130,6 +130,9 @@
|
|||
},
|
||||
"scriptureearth.org": {
|
||||
"verified": 921
|
||||
},
|
||||
"fivefish.org": {
|
||||
"verified": 74
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -37588,6 +37591,376 @@
|
|||
"state": "verified",
|
||||
"detail": "HTTP 200",
|
||||
"at": "2026-09-24T17:38:08.356Z"
|
||||
},
|
||||
"https://fivefish.org/cpx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/421?language=Chinese%2C%20Puxian)",
|
||||
"at": "2026-09-24T18:32:54.859Z"
|
||||
},
|
||||
"https://fivefish.org/dio": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/9250?language=Dibo)",
|
||||
"at": "2026-09-24T18:32:54.880Z"
|
||||
},
|
||||
"https://fivefish.org/xmc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13359?language=eMarrevoni)",
|
||||
"at": "2026-09-24T18:32:54.901Z"
|
||||
},
|
||||
"https://fivefish.org/buf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1281?language=Bushongo)",
|
||||
"at": "2026-09-24T18:32:54.928Z"
|
||||
},
|
||||
"https://fivefish.org/njx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2864?language=Kikouni)",
|
||||
"at": "2026-09-24T18:32:54.954Z"
|
||||
},
|
||||
"https://fivefish.org/lej": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1526?language=Kilengola)",
|
||||
"at": "2026-09-24T18:32:54.967Z"
|
||||
},
|
||||
"https://fivefish.org/aba": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2554?language=Abe)",
|
||||
"at": "2026-09-24T18:32:55.077Z"
|
||||
},
|
||||
"https://fivefish.org/gew": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2003?language=Gyere)",
|
||||
"at": "2026-09-24T18:32:55.144Z"
|
||||
},
|
||||
"https://fivefish.org/sgd": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/279?language=Surigaonon)",
|
||||
"at": "2026-09-24T18:32:55.424Z"
|
||||
},
|
||||
"https://fivefish.org/nkw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/94?language=Lonkutu)",
|
||||
"at": "2026-09-24T18:32:55.496Z"
|
||||
},
|
||||
"https://fivefish.org/brf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/303?language=Kibira)",
|
||||
"at": "2026-09-24T18:32:55.538Z"
|
||||
},
|
||||
"https://fivefish.org/hmg": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6168?language=Miao%2C%20Shai%20Lou%20Zhenningxian)",
|
||||
"at": "2026-09-24T18:32:55.554Z"
|
||||
},
|
||||
"https://fivefish.org/koo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1101?language=Konzo)",
|
||||
"at": "2026-09-24T18:32:55.570Z"
|
||||
},
|
||||
"https://fivefish.org/bar": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3202?language=Austrian%3A%20Tyrolean)",
|
||||
"at": "2026-09-24T18:32:55.595Z"
|
||||
},
|
||||
"https://fivefish.org/ish": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2202?language=Ishan)",
|
||||
"at": "2026-09-24T18:32:55.709Z"
|
||||
},
|
||||
"https://fivefish.org/bli": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1856?language=Lolia)",
|
||||
"at": "2026-09-24T18:32:55.743Z"
|
||||
},
|
||||
"https://fivefish.org/bgx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/7617?language=Balkan%20Gagauz%20Turkish%3A%20Yuruk)",
|
||||
"at": "2026-09-24T18:32:55.992Z"
|
||||
},
|
||||
"https://fivefish.org/ckl": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1777?language=Chibuku)",
|
||||
"at": "2026-09-24T18:32:56.011Z"
|
||||
},
|
||||
"https://fivefish.org/slx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1275?language=Salampasu)",
|
||||
"at": "2026-09-24T18:32:56.047Z"
|
||||
},
|
||||
"https://fivefish.org/eto": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/9663?language=Eton)",
|
||||
"at": "2026-09-24T18:32:56.176Z"
|
||||
},
|
||||
"https://fivefish.org/mgm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4612?language=Mambae)",
|
||||
"at": "2026-09-24T18:32:56.190Z"
|
||||
},
|
||||
"https://fivefish.org/qxp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/15876?language=Quechua%2C%20Puno)",
|
||||
"at": "2026-09-24T18:32:56.211Z"
|
||||
},
|
||||
"https://fivefish.org/bta": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/7843?language=Bata)",
|
||||
"at": "2026-09-24T18:32:56.297Z"
|
||||
},
|
||||
"https://fivefish.org/smm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/14441?language=Musasa)",
|
||||
"at": "2026-09-24T18:32:56.422Z"
|
||||
},
|
||||
"https://fivefish.org/msi": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13428?language=Malay%2C%20Sabah)",
|
||||
"at": "2026-09-24T18:32:56.569Z"
|
||||
},
|
||||
"https://fivefish.org/bpj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1278?language=Bindji)",
|
||||
"at": "2026-09-24T18:32:56.636Z"
|
||||
},
|
||||
"https://fivefish.org/kqk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4888?language=Kotafon)",
|
||||
"at": "2026-09-24T18:32:56.656Z"
|
||||
},
|
||||
"https://fivefish.org/ttj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/984?language=Tooro)",
|
||||
"at": "2026-09-24T18:32:56.747Z"
|
||||
},
|
||||
"https://fivefish.org/khc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/17734?language=Tukangbesi%20North%3A%20Wanci)",
|
||||
"at": "2026-09-24T18:32:56.759Z"
|
||||
},
|
||||
"https://fivefish.org/bnx": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1353?language=Kibangubangu)",
|
||||
"at": "2026-09-24T18:32:56.906Z"
|
||||
},
|
||||
"https://fivefish.org/suj": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4528?language=Kisubi)",
|
||||
"at": "2026-09-24T18:32:56.929Z"
|
||||
},
|
||||
"https://fivefish.org/mnp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/507?language=Hockchia)",
|
||||
"at": "2026-09-24T18:32:56.958Z"
|
||||
},
|
||||
"https://fivefish.org/kel": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1309?language=Ikela)",
|
||||
"at": "2026-09-24T18:32:57.128Z"
|
||||
},
|
||||
"https://fivefish.org/czh": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6237?language=Chinese%2C%20Huizhou)",
|
||||
"at": "2026-09-24T18:32:57.237Z"
|
||||
},
|
||||
"https://fivefish.org/rue": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4289?language=Ruthenian)",
|
||||
"at": "2026-09-24T18:32:57.264Z"
|
||||
},
|
||||
"https://fivefish.org/nbm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1384?language=Ngbaka%20Mabo)",
|
||||
"at": "2026-09-24T18:32:57.295Z"
|
||||
},
|
||||
"https://fivefish.org/zeh": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/5123?language=Zhuang%3A%20Guigang%20Guiping)",
|
||||
"at": "2026-09-24T18:32:57.316Z"
|
||||
},
|
||||
"https://fivefish.org/bqv": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4543?language=Koro%20Wachi)",
|
||||
"at": "2026-09-24T18:32:57.639Z"
|
||||
},
|
||||
"https://fivefish.org/abr": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6021?language=Abron)",
|
||||
"at": "2026-09-24T18:32:57.687Z"
|
||||
},
|
||||
"https://fivefish.org/nto": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1858?language=Ntomba)",
|
||||
"at": "2026-09-24T18:32:57.724Z"
|
||||
},
|
||||
"https://fivefish.org/rkm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3068?language=Maraka)",
|
||||
"at": "2026-09-24T18:32:57.732Z"
|
||||
},
|
||||
"https://fivefish.org/shc": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1593?language=Kilua-Kisonde)",
|
||||
"at": "2026-09-24T18:32:57.806Z"
|
||||
},
|
||||
"https://fivefish.org/trf": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3495?language=Trinidadian%20English%20Creole)",
|
||||
"at": "2026-09-24T18:32:57.878Z"
|
||||
},
|
||||
"https://fivefish.org/daw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/283?language=Davaweno)",
|
||||
"at": "2026-09-24T18:32:57.965Z"
|
||||
},
|
||||
"https://fivefish.org/mde": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2971?language=Maba)",
|
||||
"at": "2026-09-24T18:32:58.215Z"
|
||||
},
|
||||
"https://fivefish.org/sip": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/678?language=Sikkimese)",
|
||||
"at": "2026-09-24T18:32:58.276Z"
|
||||
},
|
||||
"https://fivefish.org/tek": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4529?language=Kiteke)",
|
||||
"at": "2026-09-24T18:32:58.311Z"
|
||||
},
|
||||
"https://fivefish.org/wof": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4592?language=Wolof%2C%20Gambian)",
|
||||
"at": "2026-09-24T18:32:58.405Z"
|
||||
},
|
||||
"https://fivefish.org/jya": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/11143?language=Jiarong)",
|
||||
"at": "2026-09-24T18:32:58.428Z"
|
||||
},
|
||||
"https://fivefish.org/ndo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1952?language=Ndonga)",
|
||||
"at": "2026-09-24T18:32:58.476Z"
|
||||
},
|
||||
"https://fivefish.org/xac": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/619?language=Bodo%3A%20Kachari)",
|
||||
"at": "2026-09-24T18:32:58.491Z"
|
||||
},
|
||||
"https://fivefish.org/rwk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1065?language=Rwa)",
|
||||
"at": "2026-09-24T18:32:58.808Z"
|
||||
},
|
||||
"https://fivefish.org/bbp": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1742?language=Banda)",
|
||||
"at": "2026-09-24T18:32:58.818Z"
|
||||
},
|
||||
"https://fivefish.org/gsw": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2909?language=German%2C%20Swiss%3A%20Basel)",
|
||||
"at": "2026-09-24T18:32:58.853Z"
|
||||
},
|
||||
"https://fivefish.org/bjo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1495?language=Banda%2C%20Mid-Southern%3A%20Yakpa)",
|
||||
"at": "2026-09-24T18:32:58.951Z"
|
||||
},
|
||||
"https://fivefish.org/zag": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2939?language=Zaghawa)",
|
||||
"at": "2026-09-24T18:32:59.067Z"
|
||||
},
|
||||
"https://fivefish.org/dse": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19011?language=Sign%20Language%20of%20the%20Netherlands)",
|
||||
"at": "2026-09-24T18:32:59.114Z"
|
||||
},
|
||||
"https://fivefish.org/yix": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4983?language=Axi)",
|
||||
"at": "2026-09-24T18:32:59.196Z"
|
||||
},
|
||||
"https://fivefish.org/pht": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1711?language=Phu%20Thai)",
|
||||
"at": "2026-09-24T18:32:59.278Z"
|
||||
},
|
||||
"https://fivefish.org/sjo": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4943?language=Xibe)",
|
||||
"at": "2026-09-24T18:32:59.300Z"
|
||||
},
|
||||
"https://fivefish.org/fsl": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19026?language=French%20Sign%20Language)",
|
||||
"at": "2026-09-24T18:32:59.368Z"
|
||||
},
|
||||
"https://fivefish.org/kfa": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4078?language=Coorgi)",
|
||||
"at": "2026-09-24T18:32:59.516Z"
|
||||
},
|
||||
"https://fivefish.org/bdm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1771?language=Buduma)",
|
||||
"at": "2026-09-24T18:32:59.578Z"
|
||||
},
|
||||
"https://fivefish.org/tma": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/3511?language=Tama)",
|
||||
"at": "2026-09-24T18:32:59.632Z"
|
||||
},
|
||||
"https://fivefish.org/avd": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6913?language=Alviri)",
|
||||
"at": "2026-09-24T18:32:59.666Z"
|
||||
},
|
||||
"https://fivefish.org/gow": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1028?language=Fiome)",
|
||||
"at": "2026-09-24T18:32:59.818Z"
|
||||
},
|
||||
"https://fivefish.org/tyr": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/17053?language=Tai%20Daeng)",
|
||||
"at": "2026-09-24T18:32:59.831Z"
|
||||
},
|
||||
"https://fivefish.org/twm": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/14200?language=Monpa%2C%20Tawang)",
|
||||
"at": "2026-09-24T18:32:59.946Z"
|
||||
},
|
||||
"https://fivefish.org/wni": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/6031?language=Shindzwani)",
|
||||
"at": "2026-09-24T18:32:59.994Z"
|
||||
},
|
||||
"https://fivefish.org/mne": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/2966?language=Naba%3A%20Kouka)",
|
||||
"at": "2026-09-24T18:33:00.059Z"
|
||||
},
|
||||
"https://fivefish.org/hmb": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/4849?language=Songhay%2C%20Humburi%20Senni%3A%20Maranse)",
|
||||
"at": "2026-09-24T18:33:00.068Z"
|
||||
},
|
||||
"https://fivefish.org/rui": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/1067?language=Rufiji)",
|
||||
"at": "2026-09-24T18:33:00.101Z"
|
||||
},
|
||||
"https://fivefish.org/pks": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/19209?language=Pakistan%20Sign%20Language)",
|
||||
"at": "2026-09-24T18:33:00.233Z"
|
||||
},
|
||||
"https://fivefish.org/vmk": {
|
||||
"state": "verified",
|
||||
"detail": "HTTP 200 (via https://fivefish.org/13367?language=Exirima)",
|
||||
"at": "2026-09-24T18:33:00.244Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
413
public/doxa-maps/assets/tables/grn-index.json
generated
Normal file
413
public/doxa-maps/assets/tables/grn-index.json
generated
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
{
|
||||
"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,
|
||||
"byIso": {
|
||||
"mnp": {
|
||||
"u": "https://fivefish.org/mnp",
|
||||
"n": "Hockchia"
|
||||
},
|
||||
"pks": {
|
||||
"u": "https://fivefish.org/pks",
|
||||
"n": "Pakistan Sign Language"
|
||||
},
|
||||
"gsw": {
|
||||
"u": "https://fivefish.org/gsw",
|
||||
"n": "German, Swiss: Basel"
|
||||
},
|
||||
"czh": {
|
||||
"u": "https://fivefish.org/czh",
|
||||
"n": "Chinese, Huizhou"
|
||||
},
|
||||
"cpx": {
|
||||
"u": "https://fivefish.org/cpx",
|
||||
"n": "Chinese, Puxian"
|
||||
},
|
||||
"abr": {
|
||||
"u": "https://fivefish.org/abr",
|
||||
"n": "Abron"
|
||||
},
|
||||
"koo": {
|
||||
"u": "https://fivefish.org/koo",
|
||||
"n": "Konzo"
|
||||
},
|
||||
"ttj": {
|
||||
"u": "https://fivefish.org/ttj",
|
||||
"n": "Tooro"
|
||||
},
|
||||
"zeh": {
|
||||
"u": "https://fivefish.org/zeh",
|
||||
"n": "Zhuang: Guigang Guiping"
|
||||
},
|
||||
"sgd": {
|
||||
"u": "https://fivefish.org/sgd",
|
||||
"n": "Cantilangnon"
|
||||
},
|
||||
"fsl": {
|
||||
"u": "https://fivefish.org/fsl",
|
||||
"n": "French Sign Language"
|
||||
},
|
||||
"vmk": {
|
||||
"u": "https://fivefish.org/vmk",
|
||||
"n": "Exirima"
|
||||
},
|
||||
"kel": {
|
||||
"u": "https://fivefish.org/kel",
|
||||
"n": "Ikela"
|
||||
},
|
||||
"ndo": {
|
||||
"u": "https://fivefish.org/ndo",
|
||||
"n": "Ndonga"
|
||||
},
|
||||
"pht": {
|
||||
"u": "https://fivefish.org/pht",
|
||||
"n": "Phu Thai"
|
||||
},
|
||||
"xmc": {
|
||||
"u": "https://fivefish.org/xmc",
|
||||
"n": "eMarrevoni"
|
||||
},
|
||||
"mne": {
|
||||
"u": "https://fivefish.org/mne",
|
||||
"n": "Naba: Kouka"
|
||||
},
|
||||
"ish": {
|
||||
"u": "https://fivefish.org/ish",
|
||||
"n": "Ishan"
|
||||
},
|
||||
"msi": {
|
||||
"u": "https://fivefish.org/msi",
|
||||
"n": "Malay, Sabah"
|
||||
},
|
||||
"mde": {
|
||||
"u": "https://fivefish.org/mde",
|
||||
"n": "Maba"
|
||||
},
|
||||
"rui": {
|
||||
"u": "https://fivefish.org/rui",
|
||||
"n": "Rufiji"
|
||||
},
|
||||
"rkm": {
|
||||
"u": "https://fivefish.org/rkm",
|
||||
"n": "Maraka"
|
||||
},
|
||||
"zag": {
|
||||
"u": "https://fivefish.org/zag",
|
||||
"n": "Zaghawa"
|
||||
},
|
||||
"qxp": {
|
||||
"u": "https://fivefish.org/qxp",
|
||||
"n": "Quechua, Puno"
|
||||
},
|
||||
"gew": {
|
||||
"u": "https://fivefish.org/gew",
|
||||
"n": "Gyere"
|
||||
},
|
||||
"suj": {
|
||||
"u": "https://fivefish.org/suj",
|
||||
"n": "Kisubi"
|
||||
},
|
||||
"eto": {
|
||||
"u": "https://fivefish.org/eto",
|
||||
"n": "Eton"
|
||||
},
|
||||
"tek": {
|
||||
"u": "https://fivefish.org/tek",
|
||||
"n": "Kiteke"
|
||||
},
|
||||
"nto": {
|
||||
"u": "https://fivefish.org/nto",
|
||||
"n": "Ntomba"
|
||||
},
|
||||
"wni": {
|
||||
"u": "https://fivefish.org/wni",
|
||||
"n": "Shindzwani"
|
||||
},
|
||||
"nbm": {
|
||||
"u": "https://fivefish.org/nbm",
|
||||
"n": "Ngbaka Mabo"
|
||||
},
|
||||
"hmg": {
|
||||
"u": "https://fivefish.org/hmg",
|
||||
"n": "Miao, Shai Lou Zhenningxian"
|
||||
},
|
||||
"wof": {
|
||||
"u": "https://fivefish.org/wof",
|
||||
"n": "Wolof, Gambian"
|
||||
},
|
||||
"bnx": {
|
||||
"u": "https://fivefish.org/bnx",
|
||||
"n": "Kibangubangu"
|
||||
},
|
||||
"bpj": {
|
||||
"u": "https://fivefish.org/bpj",
|
||||
"n": "Bindji"
|
||||
},
|
||||
"bbp": {
|
||||
"u": "https://fivefish.org/bbp",
|
||||
"n": "Banda"
|
||||
},
|
||||
"bta": {
|
||||
"u": "https://fivefish.org/bta",
|
||||
"n": "Bata"
|
||||
},
|
||||
"aba": {
|
||||
"u": "https://fivefish.org/aba",
|
||||
"n": "Abbey"
|
||||
},
|
||||
"buf": {
|
||||
"u": "https://fivefish.org/buf",
|
||||
"n": "Bushongo"
|
||||
},
|
||||
"jya": {
|
||||
"u": "https://fivefish.org/jya",
|
||||
"n": "Jiarong"
|
||||
},
|
||||
"rue": {
|
||||
"u": "https://fivefish.org/rue",
|
||||
"n": "Ruthenian"
|
||||
},
|
||||
"brf": {
|
||||
"u": "https://fivefish.org/brf",
|
||||
"n": "Kibira"
|
||||
},
|
||||
"bgx": {
|
||||
"u": "https://fivefish.org/bgx",
|
||||
"n": "Balkan Gagauz Turkish: Yuruk"
|
||||
},
|
||||
"tma": {
|
||||
"u": "https://fivefish.org/tma",
|
||||
"n": "Tama"
|
||||
},
|
||||
"bar": {
|
||||
"u": "https://fivefish.org/bar",
|
||||
"n": "Austrian: Tyrolean"
|
||||
},
|
||||
"sip": {
|
||||
"u": "https://fivefish.org/sip",
|
||||
"n": "Sikkimese"
|
||||
},
|
||||
"rwk": {
|
||||
"u": "https://fivefish.org/rwk",
|
||||
"n": "Rwa"
|
||||
},
|
||||
"slx": {
|
||||
"u": "https://fivefish.org/slx",
|
||||
"n": "Salampasu"
|
||||
},
|
||||
"hmb": {
|
||||
"u": "https://fivefish.org/hmb",
|
||||
"n": "Songhay, Humburi Senni: Maranse"
|
||||
},
|
||||
"dio": {
|
||||
"u": "https://fivefish.org/dio",
|
||||
"n": "Dibo"
|
||||
},
|
||||
"mgm": {
|
||||
"u": "https://fivefish.org/mgm",
|
||||
"n": "Mambae"
|
||||
},
|
||||
"lej": {
|
||||
"u": "https://fivefish.org/lej",
|
||||
"n": "Kilengola"
|
||||
},
|
||||
"bli": {
|
||||
"u": "https://fivefish.org/bli",
|
||||
"n": "Lolia"
|
||||
},
|
||||
"ckl": {
|
||||
"u": "https://fivefish.org/ckl",
|
||||
"n": "Chibuku"
|
||||
},
|
||||
"nkw": {
|
||||
"u": "https://fivefish.org/nkw",
|
||||
"n": "Lonkutu"
|
||||
},
|
||||
"tyr": {
|
||||
"u": "https://fivefish.org/tyr",
|
||||
"n": "Tai Daeng"
|
||||
},
|
||||
"smm": {
|
||||
"u": "https://fivefish.org/smm",
|
||||
"n": "Musasa"
|
||||
},
|
||||
"kqk": {
|
||||
"u": "https://fivefish.org/kqk",
|
||||
"n": "Kotafon"
|
||||
},
|
||||
"trf": {
|
||||
"u": "https://fivefish.org/trf",
|
||||
"n": "Trinidadian English Creole"
|
||||
},
|
||||
"shc": {
|
||||
"u": "https://fivefish.org/shc",
|
||||
"n": "Kilua-Kisonde"
|
||||
},
|
||||
"xac": {
|
||||
"u": "https://fivefish.org/xac",
|
||||
"n": "Bodo: Kachari"
|
||||
},
|
||||
"kfa": {
|
||||
"u": "https://fivefish.org/kfa",
|
||||
"n": "Coorgi"
|
||||
},
|
||||
"twm": {
|
||||
"u": "https://fivefish.org/twm",
|
||||
"n": "Monpa, Tawang"
|
||||
},
|
||||
"bjo": {
|
||||
"u": "https://fivefish.org/bjo",
|
||||
"n": "Banda, Mid-Southern: Yakpa"
|
||||
},
|
||||
"bdm": {
|
||||
"u": "https://fivefish.org/bdm",
|
||||
"n": "Buduma"
|
||||
},
|
||||
"daw": {
|
||||
"u": "https://fivefish.org/daw",
|
||||
"n": "Davawenyo"
|
||||
},
|
||||
"sjo": {
|
||||
"u": "https://fivefish.org/sjo",
|
||||
"n": "Xibe"
|
||||
},
|
||||
"gow": {
|
||||
"u": "https://fivefish.org/gow",
|
||||
"n": "Fiome"
|
||||
},
|
||||
"avd": {
|
||||
"u": "https://fivefish.org/avd",
|
||||
"n": "Alviri"
|
||||
},
|
||||
"dse": {
|
||||
"u": "https://fivefish.org/dse",
|
||||
"n": "Sign Language of the Netherlands"
|
||||
},
|
||||
"yix": {
|
||||
"u": "https://fivefish.org/yix",
|
||||
"n": "Axi"
|
||||
},
|
||||
"bqv": {
|
||||
"u": "https://fivefish.org/bqv",
|
||||
"n": "Koro Wachi"
|
||||
},
|
||||
"njx": {
|
||||
"u": "https://fivefish.org/njx",
|
||||
"n": "Kikouni"
|
||||
},
|
||||
"khc": {
|
||||
"u": "https://fivefish.org/khc",
|
||||
"n": "Tukangbesi North: Wanci"
|
||||
}
|
||||
},
|
||||
"none": [
|
||||
"cjy",
|
||||
"gan",
|
||||
"csl",
|
||||
"inl",
|
||||
"ayh",
|
||||
"mmr",
|
||||
"nsi",
|
||||
"zlj",
|
||||
"acx",
|
||||
"nos",
|
||||
"qcd",
|
||||
"zgb",
|
||||
"eth",
|
||||
"esl",
|
||||
"psp",
|
||||
"cps",
|
||||
"czo",
|
||||
"tsm",
|
||||
"hab",
|
||||
"abv",
|
||||
"gsg",
|
||||
"bfi",
|
||||
"now",
|
||||
"zmi",
|
||||
"thz",
|
||||
"auh",
|
||||
"tsq",
|
||||
"mfu",
|
||||
"fia",
|
||||
"ise",
|
||||
"sfs",
|
||||
"aju",
|
||||
"ssp",
|
||||
"mhm",
|
||||
"suw",
|
||||
"ysm",
|
||||
"bww",
|
||||
"asp",
|
||||
"vic",
|
||||
"aed",
|
||||
"qns",
|
||||
"han",
|
||||
"qiq",
|
||||
"mry",
|
||||
"kxd",
|
||||
"afg",
|
||||
"fmp",
|
||||
"bah",
|
||||
"ugn",
|
||||
"wxa",
|
||||
"phu",
|
||||
"pso",
|
||||
"xms",
|
||||
"mlm",
|
||||
"hmh",
|
||||
"zlq",
|
||||
"kdg",
|
||||
"sdl",
|
||||
"nsp",
|
||||
"shz",
|
||||
"diz",
|
||||
"sjg",
|
||||
"tza",
|
||||
"zhk",
|
||||
"prl",
|
||||
"gex",
|
||||
"xml",
|
||||
"noq",
|
||||
"ypg",
|
||||
"jbn",
|
||||
"mzc",
|
||||
"vsl",
|
||||
"qry",
|
||||
"gse",
|
||||
"ras",
|
||||
"mny",
|
||||
"soe",
|
||||
"hnm",
|
||||
"qkp",
|
||||
"amj",
|
||||
"btw",
|
||||
"ayc",
|
||||
"qbf",
|
||||
"bog",
|
||||
"xki",
|
||||
"cri",
|
||||
"sbm",
|
||||
"kiv",
|
||||
"tiz",
|
||||
"kwl",
|
||||
"grj",
|
||||
"gdq",
|
||||
"qsg",
|
||||
"qso",
|
||||
"lws",
|
||||
"bpd",
|
||||
"dec",
|
||||
"lap",
|
||||
"gsm",
|
||||
"cds",
|
||||
"tor",
|
||||
"csg",
|
||||
"dux",
|
||||
"ajz",
|
||||
"mdi",
|
||||
"psi"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
26
public/doxa-maps/manifest.json
generated
26
public/doxa-maps/manifest.json
generated
|
|
@ -596,7 +596,7 @@
|
|||
"bundle": "gospel-resources",
|
||||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"output": "doxa-maps/gospel-resources/gospel-resources.js",
|
||||
"integrity": "sha384-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2",
|
||||
"integrity": "sha384-uMCRwdkQhMDk/yyoDE+EaQyScTVu6VxHZ4Zkufh/MZvzsOhz8L5eCsvsAKzc2RqL",
|
||||
"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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-DLpCR/4TKWn/tY/3M4MYKfBcueIKSL1WhSTGSWs49t6raFYF5vJit++4TpmCdNJ2\" 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-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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh",
|
||||
"integrity": "sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++",
|
||||
"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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh\" 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-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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh\" 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-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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -799,12 +799,12 @@
|
|||
"bundle": "plugin-studio",
|
||||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"output": "doxa-maps/plugin-studio/plugin-studio.js",
|
||||
"integrity": "sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO",
|
||||
"integrity": "sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS",
|
||||
"elements": [
|
||||
"plugin-studio"
|
||||
],
|
||||
"composes": [
|
||||
"bp-big-picture",
|
||||
"dashboard-plus-grid",
|
||||
"people-groups-grid",
|
||||
"doxa-research-map",
|
||||
"doxa-simple-map",
|
||||
|
|
@ -827,7 +827,7 @@
|
|||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-jGD6GdGzjAWNQ/APgKvUZDcXvryVdhvFXGLWNVENRfjZMurTwcMGmOUXDGF3wvnh" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-asXvArHUl8v5nT2vz0AwC2xs1jXvyOLPwiuWH/WI/ZceZ9NJvrPfbHvrmWDoF5++" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
39
public/doxa-maps/plugin-studio/plugin-studio.js
generated
39
public/doxa-maps/plugin-studio/plugin-studio.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../plugin-studio.js" integrity="sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO" crossorigin="anonymous"></script>
|
||||
<script src="../plugin-studio.js" integrity="sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../../plugin-studio.js" integrity="sha384-EvwAOYsSENxqsDaAJcyANtACSyVvdFI7Eh3BiB4dHIFjln2tqx+G/VUpLC1zurMO" crossorigin="anonymous"></script>
|
||||
<script src="../../plugin-studio.js" integrity="sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue