49 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e1df0d5a7c |
Add Scripture Earth, and lift 28 groups off "nothing found"
SIL and its translation partners publish Scripture for small languages the big catalogues skip, which is exactly the population left in the gap: 2,901 groups had nothing in eBible, Arclight, Door43 or FCBH. 921 codes now carry a Scripture Earth link and 28 groups that had nothing at all — 339,550 people — have Scripture in their own language for the first time on this map. Tier 0 falls 2,901 to 2,873. Most of this source is not what it appears to be, and the fetcher throws away both false positives rather than shipping them. Their open index lists 3,477 language directories, but 764 of the ones we can join are empty stubs: trusting the listing alone would have handed 716 people groups a link to an empty folder. And a "video" directory holds JESUSFilm-<iso>.txt metadata, not playable film — 1,588 codes have nothing else, so video alone never qualifies a code. Only PDF, audio, Scripture App Builder, study notes and SFM text count. All 921 URLs verified HTTP 200 before shipping, and the artefacts were opened by hand first: Baka's is 14.4 MB of application/pdf, Foodo's 40.7 MB, and a nonexistent sibling 404s, so a 200 here means a file. The coverage gate, the verifier and the links table each had to be taught the new field. Leaving any one of them ignorant would have been worse than not adding the source: the gate caught all 28 tier changes as inconsistent, and the links table filed 921 rows under the "other" publisher — the fallback that exists to make an unscreened domain impossible to hide. The sheet's "checked" line now names the pool too, since it is searched. |
||
|
|
22712eb43a |
The map said "someone still needs prayer" and gave you no way to pray
Its toast has always read "Tap a coloured dot — someone still needs prayer". Tapping one showed Scripture, film, audio, video, stories, tracts and training, and no way to pray. doxa.life publishes a page per people group and sibling plugins in this repo already link to it; this map simply never did. 1,723 groups now carry a "Pray for <group>" action with the number already praying beside it. THIS IS THE ONE JOIN HERE THAT IS NOT BY LANGUAGE, and it needed justifying rather than assuming. Every other source keys on ISO 639-3 because a translation serves everyone who speaks the language; prayer does not work that way — you pray for a people, in a place. So this keys on (group name, country), which is a NAME join, the thing this map refuses everywhere else because names collide. Allowed only because the key was tested first: on the API side (name, country) is perfectly unique — 2,107 entries, 2,107 distinct keys, 0 collisions — and on the pin side 12,379 pins give 12,369 distinct keys. The ambiguous ones are DROPPED, not guessed. One entry was lost that way, and 383 more have no pin here at all. pray.doxa.life is the fourth host in this bundle that answers 200 with identical bytes for any URL: /abai-sungai and /zzz-not-a-real-group are both 3,903 bytes. Rendered it does discriminate — a real slug titles the page "Abai Sungai - App" with 1,863 characters, a bogus one titles it "Doxa.Life" with zero — but existence comes from the API listing, which is the publisher's own statement and costs one request rather than 1,723 renders. The lookup is COMPUTED, not baked into groupAt. groupAt runs the instant a pin is clicked, so a value read there is whatever had arrived by then and never updates when the lazy fetch lands — the same shape as the link-verification state that used to hang off openPin's country branch. Verified with a negative control: a group in the index renders an anchor whose href matches the expected slug URL exactly, and German, which is not in it, renders nothing. My probe reported FAIL twice against correct code by waiting a guessed 900ms; it now waits for the data. Screenshot read — the Lati, 2,900 people with nothing found in any pool, can now be prayed for. |
||
|
|
f89e5eb370 |
The bridge table was recommending bridges on stale resource flags
languages-by-country is the bridge view: one row per country and language, ranked by prevalence, flagging which languages could carry a resource to groups that lack one. Its curated work — prevalence_rank, bridge_candidate, groups_speaking — is the reason it exists and is untouched. Its resource flags were not curated, and they had drifted like the other two legacy tables: 75 rows wrong about Scripture, 39 wrong about whether the language carries anything at all, 1 about film. That matters more here than elsewhere, because this table is consulted to CHOOSE a bridge: a language flagged as carrying nothing when it carries audio Scripture is a bridge somebody was told not to use. Refreshed from the pin index, which is the same source the map draws. 0 of 613 rows now disagree, and all 613 still carry their curated columns. full_bible is deliberately NOT refreshed. The pin index knows a scope only for eBible rows, so downgrading a curated "full Bible" on that basis would lose information rather than correct it. Also recorded rather than acted on: this table covers 175 countries while the map has 242 with pins, so 67 countries and 1,537 people groups have no bridge analysis at all. That is a gap in curated coverage, not a contradiction — the rows that exist are now correct — and filling it means doing the prevalence work, not copying numbers across. |
||
|
|
ffa49709ac |
The gap by country is now a table you can export, not just a sheet you can read
gospel-by-country carries 242 countries with real per-country counts, and the map uses them for the choropleth and the "where the gap concentrates" sheet. The tables profile had no country table at all, so those numbers lived only inside the map — readable, not exportable — while every other tab there offers CSV and JSON. A mobiliser deciding where to send a team cannot work from a sheet they have to screenshot. Derived, not a second source: every row is read out of gospel-by-country, which is itself refreshed from the pin index, so the table and the map cannot disagree. Three columns because they rank differently and each is somebody's question — groups with nothing, people with nothing, and the share of the country each represents. The default sort is by groups, and the share column is what makes rows like Vanuatu legible: 79 groups with nothing out of 114, 69.3%, which ranks ninth by count and first by share. Region is carried through so the table can be grouped the same way the map's region tab ranks. Curated columns come through as-is and stay null where the curated analysis does not cover the country — null, never zero, because zero is a claim this build cannot make. Verified against the PIN INDEX rather than against the table being tested: 242 countries with pins, 2,901 groups with nothing, worst is PNG at 469. The rendered tab reports 242 rows and its first row reads Papua New Guinea / PNG / Oceania / 831 / 469 / 56.4, with 650 real cells. No page errors. Screenshot read. |
||
|
|
04c8a816d0 |
Oceania is 51% unreached by group, and nothing on this map said so
region was empty on all 242 country rows, so the map had no way to answer "what share of this part of the world is unreached" — only how many groups a country holds and how many people live in them. The source carries UNm49Region, and it was verified before being trusted: only 2, 9, 19, 142 and 150 appear — exactly the five UN M49 top-level regions — every country maps to exactly ONE of them with 0 conflicts, and the five sum to 12,379, the whole corpus. So the code resolves to a real name. It is backfilled on every row, not just the new ones, and all 242 now carry a region with none blank. The rate is a genuinely different answer from the two already on offer: Oceania 51.2% of its 1,325 groups have nothing — 679 groups — 3.1M people Africa 30.0% of 3,074 — 923 — 68.2M Asia 19.3% of 5,073 — 978 — 198.7M Americas 11.4% of 2,117 — 241 — 6.1M Europe 10.1% of 790 — 80 — 11.6M Oceania is half unreached by group and the smallest population of the five; Asia is the reverse on both. Ranked by rate, because count and population are what the other two tabs already say and a third copy of either would be noise. All three are offered rather than one picked for the reader. The rows are plain divs, not buttons: a region is not somewhere this map can fly to, and a control that looks clickable and does nothing is worse than a row that does not pretend. Verified against an offline computation over the same index: all five regions match exactly on rate, group count and population. The sheet header is now three-way rather than showing the country prose in region mode. All five profiles still mount clean. |
||
|
|
6062b080f7 |
60 countries carrying pins had no country row at all
Set out to verify the map's second mode, "Resources found", which had never been tested. It works: the legend switches, the arithmetic is an exact complement of the gap view — 0 of 170 countries disagreed — and the two modes genuinely repaint, 98/46/12/6 polygons by band in gap against 85/66/9/2 in coverage. Proving that took three attempts and the third one found the real defect. Comparing the fill expression proved nothing, because it is ["get","color"] in BOTH modes — the colour is baked onto each feature and switching modes rewrites the source data. Comparing the features instead showed 157 coloured and 5 transparent against 170 rows in the table, and that mismatch was the thread worth pulling. The previous refresh of this table only UPDATED rows that already existed, and the table came from the curated 2,106-record set. So 60 countries carrying pins had no row at all — 150 people groups with nothing found, invisible to every country-level surface. Canada 20, Ghana 15, Timor-Leste 13, Australia 7. 72 countries are now added from the pins, and 0 countries with bare groups are missing; all 242 rows agree with the pins on groups and no_resource. Curated columns on the new rows are null, never zero: this build does not know a growth rate for Canada, and zero is a claim. They are flagged from_pins_only so the different basis is visible rather than implied. Names come from people-groups-slim's CountryName, because a row labelled "CAN" renders "CAN" as the sheet's title. UNm49Region was deliberately NOT used for region — it is a numeric code, and "19" is worse than blank. One row stays a bare code: Kosovo, whose non-standard KOS has no name in the source, and inventing one for a single row is not worth it. The boundary file turns out to hold only 162 polygons and has no Canada, Australia, Timor-Leste or Taiwan, so this paints five previously-transparent countries and fixes the rest where it actually shows: clicking a pin in Canada now opens a sheet titled "Canada" with 304 groups, matching the pins exactly, where before it fell through to a bare ISO code with no statistics. |
||
|
|
0c64fdd856 |
A caveat naming three pools sat directly under rows from eight
The dashboard is the one profile never opened on a phone. Structurally it holds up at 390x844 — no horizontal overflow anywhere in either shadow tree, canvas 390x675, the table half renders 20 real rows and 60 cells, no page errors. What does not hold up is what it says. Under the table, the Pools Caveat read "Pools checked: eBible.org, JESUS Film (Arclight), Door43/unfoldingWord". On the same screen, visible immediately above it, were rows typed Audio Scripture, Bible video, Zume Training and Gospel presentation — four pools the caveat denied having checked. It is the most direct version of a mistake this bundle has now hit repeatedly: a sentence written by hand goes stale the moment a source is added, and this one had gone stale four times over. It is no longer written by hand. The checked list is now counted off the rows themselves, so it cannot disagree with the table it sits under: "Cru (GodTools), Door43 (unfoldingWord), Faith Comes By Hearing, JESUS Film, PNG.Bible, Zume Training, eBible.org, eBible.org (mirror)". The NOT-checked half stays prose, because it names things that are absent and no row can testify to those — GRN, api.bible, the Digital Bible Library and Progress Bible, each needing a credential or an agreement. The page subtitle had the same shape of error, offering "scripture, film or audio" over tables carrying seven media. It now names the question rather than a list that will go stale again. Verified on the phone viewport after the change: table half still 20 rows / 60 cells, no overflow, no page errors, and the rendered caveat lists all eight sources. Screenshot read. |
||
|
|
445131cf94 |
Merge main into bundler-3/work — settle the 21-file conflict, keep both campaigns
Only 2 files conflicted in source: package.json and its CLI doc, both taken to main's refactored gate pipeline (gates:pre/gates:post/build:bundles) after verifying every check the old inline chain named still runs there. The other 19 were generated: public/assets/tables mirrors keep main's deliberate deletion, and public/doxa-maps is regenerated by the build that follows. |
||
|
|
e0fd61eb3e |
Gospel presentations in 94 languages, from Cru's API rather than its parked domain
Every pool here so far is Scripture, film, Bible video or a course. None is a GOSPEL PRESENTATION — the short translated thing a person actually opens with someone. Cru publishes those, in the same organisation that makes the JESUS Film, so the doctrine question is settled the same way the brief settles it for the film. Only their gospel tracts are included, from a fixed allow-list; the lessons and survey tools are not. IT CLOSES NO GAP AND SAYS SO. All 94 of these languages already have something, so this is depth rather than coverage and moves no tier. Tiers are byte-identical at 2901/4767/4711. It reaches 3,270 pins and 3.97bn people. godtools.org, the obvious host, IS A PARKED DOMAIN. It answers 200 to everything with a 114-byte body, and rendered, every URL — including /zz/nosuchtool-xyzzy — reads "godtools.org is parked free, courtesy of GoDaddy.com". Shipping links there would have sent people to a GoDaddy ad. The real front-end is knowgod.com, and it does serve per-language tracts: /en/kgp reads "Knowing God Personally", /sw/kgp reads "Kumjua Mungu Kibinafsi". But ONLY once rendered. By fetch it is a uniform 3,864-byte shell for every URL valid or not — the third host in this bundle with that shape, after jesusfilm's old ?languageId= and live.bible.is. So existence is proved from Cru's own API and never from the page: a (language, tool) pair exists exactly when the resource's latest-translations intersect that language's translations. Checked against the browser before being trusted — en/kgp and sw/kgp both intersect and both render real localised text, and the bogus pair does not intersect. In band, the verifier's controls are now 7 negative all non-verified and 7 positive all verified, with a bogus knowgod pair among the negatives. All 94 URLs verified. Clause I keeps its closed set: Gospel presentation only on knowgod.com, and knowgod.com only that type. Poison self-test still 12/12. Screenshot read: German shows six distinct resource types, including "Gospel presentation - Knowing God Personally". |
||
|
|
b46ce03a8b |
The country colours were drawn from a different dataset than the dots on top of them
gospel-by-country supplies band and no_resource, which fill every country polygon on the map. Those were computed on 2026-08-01 over the curated 2,106-record set. The pins drawn ON TOP of that fill come from the 12,379-pin index. The two disagreed for 135 of 170 countries, and the worst cases are not close: PNG coloured "critical" from ONE curated group, under 831 pins (56.4% bare -> high) USA coloured "covered" from three groups, under 623 pins (7.1% bare -> partial) PHL coloured "covered" from one group, under 156 pins (17.9% bare -> partial) IDN coloured "critical" from 36 groups, under 669 pins (48.0% -> high) A reader sees the colour first and the dots second. Telling them a country is covered while plotting bare groups across it, or critical on the evidence of a single group, is the map arguing with itself - and the fill is the louder half. groups, no_resource, pop_no_resource, scripture, film, pct_no_resource and band are now counted from the pins, under the same band rule as before (critical >=60%, high >=30%, partial >0, covered 0). growing_unreached, deaths_day and growth are NOT derivable from the pins - they come from the curated country analysis over a narrower set, the country sheet already labels them as a different basis, and they are left exactly as they were. Verified: 0 of 170 countries still disagree with the pins on groups, no_resource or band. Bands are now 8 critical / 52 high / 100 partial / 10 covered. The legend was making a claim that stopped being true two changes ago: "Audio is unchecked for every group here". Faith Comes By Hearing has been searched since. It now names all five pools that were checked and isolates GRN as the one that cannot be - 403 on every request including its own robots.txt - which is the honest version of the same warning. |
||
|
|
85a3d61780 |
Two curated tables stopped disagreeing with the map about what exists
gaps-and-opportunities and people-group-resources predate everything added this flight.
Their curated columns — the bridge-language analysis, internet access, messaging
channels — are the reason they exist and are untouched. Their resource booleans had
drifted into saying things the map disproves:
80 rows heart_has_any=false while the index has a resource (6M people)
317 rows "no scripture" while the index has TEXT Scripture
277 rows the same, for the bridge language
5 rows "no film" while the index has one
2,106 rows has_audio = the string "unknown", written when no audio pool had been
searched. Faith Comes By Hearing has been searched since.
Only the facts the pin index is authoritative for are rewritten. Rebuilding these tables
would have destroyed curated work to fix a few columns.
I GOT THE DERIVED FIELDS WRONG FIRST AND CAUGHT IT BEFORE COMMITTING. `gap` reads
naturally as "no resource in the heart language", and defining it that way changed 416
rows. The table's own meta defines it as "nothing found in the heart language AND no
in-country bridge language carries anything" — my version would have silently discarded
half its meaning while looking like a correction. Every formula is now taken from the
table's documented meaning and was verified to reproduce the existing column on all
2,106 rows BEFORE anything was rewritten: gap, opportunity (which also requires the
bridge to be spoken by more than one group in the country), aligned, and status. With
the right formula, gap moves 12 rows, not 416.
has_audio stays THREE-STATE and never false: true where FCBH publishes audio, "unknown"
otherwise. GRN is the main audio pool and answers 403 to everything, so "no audio
exists" is a claim this build cannot make. 1,297 true, 809 unknown.
Verified after the fact, not assumed: zero remaining contradictions between either
table and the pin index, and all four derived formulas still reproduce their columns
2,106/2,106. Both tables written through the snapshot guard. Tables tab still renders
real cells.
|
||
|
|
78e9f3cc2f |
The Deaf Gap tab was missing more than half the Deaf gap
Two tables behind the brief's resources view were describing a different dataset from
the map beside them.
"Where Nothing Was Found" still listed 3,457 groups. The map draws 2,901. Its builder
already used the current tier; it simply had not been re-run since audio Scripture and
Bible video started lifting groups out of tier 0. A mobiliser exporting that tab would
have worked through 556 groups who now have a resource. Regenerated, and it matches the
map exactly.
"The Deaf Gap" was worse, and structurally so. It came from the curated 2,106-record
set and reported 73 sign languages, 120 people groups, 19.35M people. The map draws
150 sign languages, 234 groups, 84.2M people. The tab named for the Deaf gap was
missing more than half of it. Rebuilt from the pins - the same index the map draws, so
the two cannot disagree - and it now measures 150 languages, 234 groups, 84,159,050
people, of which 136 languages / 183 groups / 55,199,840 people have nothing found.
Kept deliberately: the bridge rule, verbatim, because it is the most important sentence
in the table - a sign language is bridged ONLY by another sign language, and text or
audio in a spoken language does not reach a Deaf community. That is also why the
related-variety reasoning added for spoken macrolanguages must never be applied to
these rows. Private-use codes are counted rather than hidden: 40 of these languages
carry qaa-qtz codes that no resource pool indexes, so "nothing found" for them is a
statement about the join as much as about the world, and each such row says so.
MY FIRST VERSION OF THIS SHIPPED AN EMPTY TABLE AND MY PROBE PASSED IT. DataTable
renders a { meta, columns, rows } document and the rebuild omitted `columns`, so the
tab drew its header, its "234 rows" caption and five pages of pagination over a
completely empty body. The probe had asserted the caption - a number the page prints
about itself, which is not evidence it drew anything. It now counts rendered rows AND
cells, and is proven against the defect: with `columns` removed it reports 50 row
shells and 0 cells and FAILS; restored, it passes. The row count alone would still have
missed it.
Verified: both tabs' captions match offline counts from the same index (2,901 and 234),
both render real cells, no page errors. Screenshot read.
|
||
|
|
77ac7e3436 |
The two largest gaps on this map were Chinese varieties of a resourced language
Read top-down, the gap list sent you to "Chinese, Jinyu - 60,310,000 people - nothing found", then "Chinese, Gan - 41,280,000". Both statements are true about those ISO codes and both are misleading as priorities: ISO 639-3 files them under the macrolanguage zho beside cmn, Mandarin, which has Scripture, film, audio and training here. Measured across the whole map: 84 pins covering 140M people sit at "nothing found" while a sibling variety of their own ISO macrolanguage carries resources. Those groups now carry a related-variety note, on the pin sheet and inline in the ranked list, because the list is the thing that gets read top-down. 63 codes have one. WHAT IT DOES NOT SAY. It is not a claim of mutual intelligibility, and the wording refuses to imply one - spoken Jinyu and spoken Mandarin are not mutually intelligible; what they share is a written standard. Only ISO's own relation is asserted, the reader is told exactly that, and it changes NO tier and closes NO gap. Tiers are byte-identical at 2901/4767/4711. The chips still read "none found", because that is still true. The macrolanguage table is fetched from SIL rather than transcribed, and written through the snapshot guard like every other catalogue. Also corrected: the gap list header still said "Audio has never been searched for any of them". That stopped being true when Faith Comes By Hearing was added, and a stale caveat misleads exactly as much as a stale number. It now says audio HAS been searched and these groups have none there either, with GRN still named as the one pool nobody can look in. And a gate defect that produced three runs of false signal. verify-gospel-map adopted any server answering 200 on the default port. Port 4173 turned out to be held by an unrelated python3 process from another worktree, so the gate reported on a build that was not this one - first a PASS, then a mount timeout that looked exactly like a regression in code that had nothing to do with it. Answering 200 is not the same question as serving this tree. It now fingerprints the served bundle against the bytes in public/ and refuses to adopt a server whose build differs, starting its own instead. Proven: it now prints "serves a DIFFERENT build than public/ - not adopting it", starts its own, and passes. Screenshot read: the ranked list shows "related: Chinese, Mandarin has resources" under Jinyu, Gan and Min Bei, and correctly nothing under Deaf Chinese or Deaf Pakistani, which have no macrolanguage sibling. |
||
|
|
270fae1a02 |
Verification survives being killed, and every record now means the same thing
A full pass is ~6,500 requests over a quarter of an hour, and it was killed three times by system memory pressure - twice at around 2,500 and once at 1,000. Results lived only in memory until the final write, so each kill threw away every request it had made. The file on disk survived, which is correct, but the pass never landed and the bandwidth was spent for nothing. Every record now carries `at`, the moment it was checked, and the table is written after each batch. A killed run loses at most one batch, and rerunning with VERIFY_MAX_AGE_H skips anything already stamped. Writing mid-pass is safe because a checkpoint only ADDS to or REFRESHES records - it can never shrink the table, which is the failure the snapshot guard exists for elsewhere. Proven rather than asserted: killed mid-pass, 300 stamped records survived on disk; the restart reported "resuming - 600 URL(s) already checked within 24h, 5876 left to do" and skipped them. The fourth memory kill then arrived unplanned during the real run and cost 500 records instead of 1,900. With the pass finally complete, the point of it: bible.is verification switched from a body-size heuristic to their API two changes ago, but only the URLs added since had been re-checked. 2,609 of 2,868 records still asserted "verified" on the older basis. All 2,708 now come from /api/bibles, and none from the heuristic. The last 160 were neither shipped nor broken - leftovers from filesets no build has referenced since the FCBH ranking changed, quietly carrying the old method. A full pass now prunes any record for a URL that nothing ships and nothing proved dead, so the table means exactly one thing: every URL this map currently ships, plus the history of everything ever proven dead. Broken records are never pruned; forgetting a dead link is how it gets shipped again. That prune sat behind an early exit that fired whenever nothing was stale, so the first run to reach it reported success and pruned nothing. Removed. Table: 6,476 records, 6,397 verified, 74 broken and dropped, 5 uncheckable, 0 unstamped, 0 on the old method. |
||
|
|
e99399c87c |
The Deaf gap is not a search problem, and the map now says so
136 of the 150 sign languages here have no resource at all, across 234 people groups
and 84,159,050 people. This tick searched for more rather than assuming, and the answer
is that there are none to find in the open catalogues:
Arclight / JESUS Film 14 sign-language entries across 13 ISO codes - every one of
them was ALREADY in this index
Faith Comes By Hearing none at all, in 3,602 rows
deaf.bible 200, but a 3,894-byte app shell
deafbible.com the same 3,894-byte shell
door.international 200, 114 bytes
So the gap closes through an agreement with Deaf Bible Society or DOOR International,
not through more searching, and every sign-language group now says that in those words
instead of leaving a reader to assume somebody simply had not looked yet.
Two real honesty defects went with it. The provenance line claimed "ebible, arclight,
door43, fcbh" as CHECKED for a Deaf group - eBible and Door43 are text and FCBH is
audio, none of which serves a Deaf group - and named GRN, an AUDIO pool, as the thing
still unchecked, while omitting the two organisations that actually publish for the
Deaf. A sign language now reads "arclight (14 sign languages, all captured), fcbh
(none)" as checked and "deaf_bible_society, door_international - both key-gated" as not.
And the sheet showed a Deaf group an "Audio - none at FCBH, GRN unsearchable" chip:
noise about a medium they cannot use, pushing the one line that matters further down.
It is suppressed for sign languages. Where FCBH does hold something for them it is
text, and that link still shows.
Verified against the data rather than the prose: 150 sign languages, 13 with a film, 0
with a text Scripture, 0 with both - so the standing claim that none yet has both is
still true - and 84,159,050 is the population the page renders. Screenshot read: Deaf
Nicaraguan shows the exhausted-catalogue paragraph naming both ministries, with no
audio chip.
Jehovah's Witnesses publish extensively in sign language and remain excluded. The size
of a gap is not a reason to relax the doctrinal filter.
|
||
|
|
c4fbc469ac |
Bible video from FCBH, with the real book scope on every link
The previous change left video_date out because the column does not say what the video
IS. Establishing it took measurement, and the first samples were misleading. Three
video-only filesets all rendered ...S2DV pages opening on "Intro & Garden of Eden" with
Drama/Non-Drama controls, which suggested one Old Testament series. Reading
GET /api/bibles/<bible_id>, which returns the exact books array in one call, for all
1,669 video languages says otherwise:
209 27 parts, Matthew to Revelation (the whole New Testament)
116 27 parts, San Mateo to Apocalipsis (same, Spanish editions)
110 12 parts, Intro & Garden of Eden ... (Covenant, Old Testament)
27 Mark 26 Acts 23 Luke
1,006 distinct scopes in all
Generalising from three samples would have described the bulk of it exactly backwards.
Every link therefore ships its own scope rather than a shared description, and the 3
languages whose book list could not be read were dropped: an undescribed video is not
shipped.
It is typed "Bible video", never "Film". Film on this map means the JESUS Film, and
bible.is itself treats the two as different - its pages call a separate Arclight
endpoint, /api/arclight/jesus-film/languages?iso=..., to find it. So Bible video lifts a
group off "nothing found", which is true, and contributes nothing to the Scripture+film
tier. 1,666 languages carry it: 287 as their own link reaching 4,375 pins and 5.76bn
people, and 1,379 merged into the Scripture link whose media list now reads "audio,
text, video" instead of advertising half of what the page holds.
Verification for bible.is switched from a body-size heuristic to their own API. The page
answers 200 for any id, so the first version compared body sizes against a shell
measured at run time. That was a heuristic on a number the site controls, and it stopped
working when their WAF began answering HEAD with 403 and a 520-byte body for real and
bogus alike. The run-time calibration caught exactly that and ABORTED rather than mark
thousands of links verified - which is the whole reason it was measured and not
hard-coded. The API needs no heuristic: /api/bibles/OKEBSN is 200, /api/bibles/ZZZZZZ is
404. It is a tenth of the weight, it describes the same fileset the page renders, and
GET is used because HEAD is what the WAF blocks.
Also fixed: a fetch with no timeout hung the fetcher for thirteen minutes with no
output. A stall looks exactly like slow work, so every fetch in it now carries a
timeout, and the scope lookups run with bounded concurrency and 403 backoff.
6,404 links screened, 6,557 verified, 74 broken and dropped, 5 uncheckable. Screenshot
read: German shows "Bible video - 12 parts, Intro & Garden of Eden to After the Forty
Years / End Credits" as its own chip, distinct from "Film - link checked".
|
||
|
|
659ca6e244 |
FCBH text as well as audio, and 207 text-only filesets stop claiming to be audio
The FCBH integration used only audio_date. Their rows also carry text_date, and it is unambiguous - they publish Bible text on bible.is. 698 codes carry text where this map had no text Scripture at all, reaching 2,176 pins and 1.38bn people. Codes go 2,192 -> 2,400 and the gap falls 2,927 -> 2,908. ONE LINK PER LANGUAGE, NOT ONE PER MEDIUM. A bible.is fileset is a single page carrying whatever media it has, so two chips pointing at the same URL would be noise. The link now states what it carries: 1,465 codes audio+text, 677 audio only, 207 text only. That last number was a live falsehood. Because the builder never copied the media field across, all 2,349 bible.is rows were typed "Audio Scripture" - including 207 filesets with no recording at all. A group with text-only Scripture was being sent to look for a recording that does not exist. Both the chip and the table type now follow the media. Ranking is by media count, then AUDIO PRESENT, then recency. The middle term is not decoration: without it a newer text-only fileset outranks an audio-only one on date alone and the language silently loses its recording, which is the medium an oral-preference group actually needs. Measured while it was missing: 3 codes lost audio. video_date is deliberately left out. The column does not say what the video IS - a Luke film, a sign-language edition, something else - and counting it as "film" beside the JESUS Film would assert something nobody here has checked. 400 codes with no film today carry one, so it is worth establishing later, but not by guessing. Clause I now accepts BOTH Audio Scripture and Scripture on bible.is, because a fileset really can be either, while keeping the set closed and keeping Audio Scripture wrong on every other host. Proven by poisoning both directions: Film on bible.is and Audio Scripture on ebible.org are each caught. Poison self-test 11/11. Also verified, not assumed: the brief's requirement that a pin or country click filters the table. iso=deu shows 4 of 5,889, country=IND shows 502 of 5,889. It works; an earlier reading of mine that suggested otherwise was measuring the wrong thing. All 6,117 links screened and verified. Screenshot read: Ninigo (ssg) shows "Scripture, text - 2012 Wycliffe Bible Translators, Inc." where it would have claimed audio. |
||
|
|
a11e19a6b0 |
Audio Scripture in 2,121 languages: every pin used to say "not searched"
Every pin on this map has read "Audio - not searched (GRN)" for its whole life, because
Global Recordings Network sits behind Bunny Shield and answers 403 to everything, its
own robots.txt included. Re-probed this tick: still 403. But GRN was never the only
audio pool.
Faith Comes By Hearing needed no key. Their 4.dbt.io API wants one and answers 422,
which is where the earlier note stopped. Their own recordings-database page does not
use that API - it is client-rendered and pulls a STATIC public file from their CDN,
bible_recordings.json, 3,602 rows of { iso, lang_name, bible_name, bible_id, country,
video_date, audio_date, text_date }, already keyed by ISO 639-3. 2,192 codes carry a
dated audio recording; 2,121 survive verification.
Audio lifts a group off tier 0, unlike Zume, because a people group with an audio Bible
in their own language does not have "nothing" - for an oral-preference group it may be
the most useful thing on the list. It does not create tier 2, which stays reserved for
text Scripture together with film. Measured: 533 pins and 59.5M people leave the gap,
which falls 3,460 -> 2,927. Coverage 72.0% -> 76.4%. 8,094 pins now carry audio.
A 200 FROM live.bible.is PROVES NOTHING and this almost shipped wrong. It answers 200
for a fileset that does not exist, returning a fixed-size not-found shell, so the
discriminator is BODY SIZE and HEAD gives content-length for free. The shell size is
measured at run time, never hard-coded: it was 7,961 bytes on one path and 7,860 on
another within the same hour. Both fetcher and verifier abort if a bogus id ever
returns the same size as a known-good one. It caught 73 dead filesets in FCBH's own
data, all dropped.
The first full pass collected 466 HTTP 403s from bible.is - rate limiting, not dead
links; the same URL answered 200 when retried alone. Requests are now paced per host
and 403/429 retried with backoff, which cut those to 48, and a VERIFY_RETRY_UNKNOWN
pass resolved all 48. Final: 5,882 verified, 73 broken, 5 uncheckable.
Three bugs found and fixed on the way:
- The dead-link drop ran BEFORE the audio merge, so 71 dead filesets attached after the
cleanup and shipped anyway while the pass reported "dropped 0". A cleanup that runs
before the thing it cleans is worse than none, because its count measures an empty
room. Moved after every source attaches.
- A VERIFY_LIMIT smoke run overwrote the real table, destroying the broken-link history
the previous change added. A limit run is now a smoke test that writes nothing.
- The browser gate screened 3,768 of 5,889 links because its harvest never learned the
new field.
Added VERIFY_MISSING=1 so adding one resource costs one request, not a quarter-hour
pass over 5,960 URLs.
Charts, list and map were corrected together: all three still asserted audio had never
been searched, and charts deliberately drew it as a hatched "not measured" row. That
bar is now a real count, decided by hasField() from the data itself. The checked-pools
caveat on every group now names fcbh.
Screenshot read: Uab Meto (aoz) - no Scripture, no film, no stories, not in Zume - now
shows a verified audio link and the caveat lists fcbh as checked.
|
||
|
|
2bf4001d7e |
Door43 spelled 32 major languages with two letters, so 2.87bn people saw none of it
The Door43 fetcher kept only 3-letter codes and dropped 2-letter ones on the grounds that turning one into ISO 639-3 would be a guess. The caution was right and the conclusion was wrong: ISO 639-3 publishes the correspondence itself, in the Part1 column of iso-639-3.tab. The file is now fetched rather than transcribed, so it cannot go stale, and the run aborts if it comes back with fewer than 150 pairs. Measured cost of the old behaviour: 32 languages spoken by 2,576 pins - Bengali, Hindi, Spanish, Amharic, Hausa, English and more - carried Scripture and film but no Door43 resource at all, purely over a spelling. Door43 reach goes from 169 codes and 1,051 pins to 208 codes and 3,627 pins, 3.58bn people. Table rows 226 -> 391. ONE EXCLUSION, AND IT IS LOAD-BEARING: a tag with an '-x-' private-use subtag is skipped on this path. Door43 publishes el-x-koine, Koine Greek, whose base 'el' maps to 'ell' - Modern Greek, which 13 people groups on this map speak. Folding it in would have handed those 13 groups a Koine Greek text as though it were their own language. Region and script subtags (es-419, pt-br, sr-latn, ur-deva, zh-hant) are not private use and are the same language, so they still fold to the base. Verified: ell is absent from the index. Fixed a self-erasing bug in the previous change. Dropping a dead link removed it from the corpus, so the next verification run never saw it, so the next build put it back - measured, both links came straight back and REAL URL returned to 8,922. The verification table is now a RECORD, not a snapshot: it carries forward every URL a previous run proved broken and keeps re-testing them, which is also what lets a link the publisher later repairs come back on its own. Proven by dropping them and re-running: "re-testing 2 URL(s) a previous run recorded broken", still broken, build now stable across two full cycles. Clause K's 95% coverage floor is gone, because that floor is exactly how this hid: two missing URLs out of 3,768 is 99.9%. Every shipped URL must now appear in the record. Poison self-test 11/11. All 3,768 links re-verified: 3,761 live, 5 uncheckable, 2 broken and dropped. Also fixed the chip overflow this change caused - a Door43 entry can be "Aligned Bible (translationCore-Create-BCS) - Door43 - Portions of both Testaments", which ran off the edge of a 330px sheet unreadable. Long chips wrap now. Screenshot read: Hindi shows seven verified Door43 resources, all inside the panel, where it read "none found". |
||
|
|
101653e3fd |
Verify all 3,603 links: 2 were dead, and the map showed "unchecked" on the rest
The brief's rule is to verify a URL resolves before shipping it. The table that did that was written in August against a corpus of 426 links, while the map now ships 3,603 - and the UI matched exactly TWO of them. Two silent causes: its eBible keys were http:// while every shipped URL now passes through httpsify, and its 306 film keys were the old ?languageId= URLs that were later replaced wholesale by Arclight shareUrl deep links. Links that HAD been verified displayed as "unchecked", which a reader cannot tell apart from "nobody looked". The old note said the film page was unverifiable by fetch, and for the URL shape it was written against that was right: ?languageId=13562 and ?languageId=99999999 return byte-identical HTML, so a status check there is a false green. The shareUrl migration changed that. A path discriminates: /watch/jesus/german-standard -> 200, 944,516 bytes /watch/jesus/pashto-yusufzai -> 200, 810,741 bytes (distinct body) /watch/jesus/definitely-not-xyzzy -> 404 So 2,049 film links became checkable for the first time. The 5 remaining ?languageId= URLs stay "unknown" with that reason - never verified, never broken. Every host was probed with a known-good and a known-bad before being trusted, and all answer HEAD and discriminate: ebible.org, png.bible, alkitab.pw, jesusfilm.org, arc.gt, git.door43.org, zume.training. HEAD is deliberate - a GET over 2,049 film pages is ~1.6GB of someone else's bandwidth to learn a status code. Five negative and five positive controls run in band and the script aborts rather than write a table if either arm misbehaves. Result over 3,603 unique URLs in 365s: 3,596 verified, 5 unknown, and 2 genuinely dead - arc.gt/092vn (Yakpa, 2 pins) and /watch/jesus/4101 (Zhuang Liujiang, 1 pin), both re-confirmed 404 with a browser UA and a GET. The builder now drops any link verification proved dead BEFORE tiers are computed, so a code whose only film is a 404 has no film and its tier says so. The gap filter moves 3,457 -> 3,460, which is exactly those three pins. Also fixed: the verification fetch hung off openPin's country branch, so a tap landing before the country index had loaded left the whole session reading "unchecked". It is now fetched unconditionally. Lookup coverage goes from 2/3,605 to 3,603/3,603. New gate clause K fails below a 95% coverage floor or if any shipped URL is recorded broken; poison self-test now 11/11. Verified in a browser on the exact case that failed - one immediate pin tap - which now fetches the table and renders "Scripture - link checked" and "Film - link checked"; screenshot read to confirm both chips are green where they were grey. |
||
|
|
e04509a94d |
Pashto: 47.6M speakers were told Zume had nothing, while its Pashto site was live
Last change joined Zume by exact ISO 639-3 and left pus unjoined, which turned into a false statement on the map: 13 people groups covering 47.6M speakers read "Discipleship training - not in Zume's 54" while https://zume.training/pus was live and in Pashto the whole time. Absence of a variety label is not absence of a resource. Research for something that selects a variety found nothing that does. The publisher's launch article scopes Pashto as Afghanistan's other official language and a major language of northwestern Pakistan, especially Khyber Pakhtunkhwa and parts of Balochistan - territory spanning all three of pbu, pbt and pst rather than selecting one. The locale pages declare no language metadata at all: no hreflang alternates, no BCP-47 tag, and og:locale is the site-wide en_US on every locale including this one. So it ships as an APPROXIMATE match rather than an exact join or a silence: its own field, its own dashed chip that reads "Zume, in Pashto (variety not stated)", its own scope text in the table, never counted as an exact join and unable to move a tier. The gap filter still cuts 12,379 features to 3,457. It is a curated three-code list, not a runtime name match - an approximate join a reader cannot see and a gate cannot check is the one that eventually ships a wrong link. Why this is defensible here and would not be for film: Zume in Pashto is a written course in one literary standard, where the pbu/pbt/pst split is largely spoken variety. The JESUS Film is already in this index under separate per-variety dubs (pashto-yusufzai, pashto-southern) because a dub differs by pronunciation. The medium decides. New gate clause J fails if any code carries both a confirmed and an approximate training link, or if an approximate entry lacks a url, language or note - that is the corruption that would make the qualified claim render as the confirmed one. Poison self-test now 10/10. Both gates also harvest the new field, so its URLs are screened rather than invisible; the browser gate screens 3,605. Verified in a browser with three controls: deu renders gm-chip ok with a real href, kri renders gm-chip meh with no href, pbu renders gm-chip approx linking https://zume.training/pus and carrying "variety not stated". Screenshot read: the dashed chip is visibly distinct from both. |
||
|
|
2ed595c0f8 |
Zume Training: 52 languages of discipleship course, joined to 3,479 pins
The brief names zume.training every tick and an earlier pass recorded the list as unobtainable. That was wrong. robots.txt Disallows /api/ and the homepage switcher is client-rendered, both true, but robots.txt also DECLARES a sitemap, and the publisher maintains /articles/zume-languages for exactly this purpose. Neither is behind a Disallow. The real figure is 62 locales, not the ~40 estimated: 54 serve a landing page that answers 200. The two sources disagree in both directions - Dari and Pashto ship sites but are missing from the article's table, and eight table rows serve no page yet - so fetch-zume-index.mjs takes the union and then fetches every landing URL. A locale ships a link only if its own page answered. Naming and linking stay separate facts. Pashto is deliberately left unjoined: pus is a macrolanguage, the people-group data carries pbu, pbt and pst, and nothing the publisher gives selects one. Training is not Scripture and not film, so it sits in its own field and its own type and moves NO tier. Measured: the 52 joined languages reach 3,479 pins and 4.78bn people, and none of them are tier 0 - every Zume language already has Scripture or film. Folding training into the tiers would have cut the gap count without anyone having translated anything. The gap filter still cuts 12,379 features to 3,457. Both gates had a hole this exposed. The coverage gate harvested URLs from s, f and d only, so the 52 new URLs existed in no pin record it could see; and clause I checked Film, Scripture and Bible Stories by host but let any other type fall through unchecked. Both now cover training, and a poisoned row typed Scripture on zume.training is caught. The browser gate screened 3,550 links and silently ignored the new ones; it now screens all 3,602. Verified in a browser with a negative control: deu opens with a chip linking https://zume.training/de, kri opens with "not in Zume's 54" and no href, no page errors. |
||
|
|
f35063b0ca |
Say we looked, when we looked: 2,608 rows understated their own evidence
The performance worry that started this tick was unfounded and measured as such: the new gaps table reaches first rows in 1,021ms, second-fastest of the four large tabs and ahead of Gaps & Opportunities at 1,910ms. 901KB raw but 106KB gzipped, and smaller than the 1.8MB tables already shipping. Nothing to fix. Reading that table found something that did need fixing. 2,608 of its 3,457 rows — 75% — carried an EMPTY "pools checked" and listed eBible, Arclight and Door43 as NOT checked. That is backwards. This build scans all three catalogues in full; a code reaching the naming pass is absent from every one of them, verified — 0 of the 2,248 such codes appear in any catalogue. "Not checked" reads as "we do not know". The truth is the stronger and far more useful "we searched three catalogues and it is not there", which is exactly the distinction a mobiliser needs when deciding where to work. A FURTHER 849 ROWS had the same understatement in milder form, claiming only "ebible, arclight" because their records predate the Door43 pass — though Door43 had in fact been searched for them too. Provenance is now stamped once, uniformly, over every code: the three catalogues this build actually scans are named as checked, and the four that genuinely were not — GRN, api.bible, DBL, Progress Bible, all credential-gated — are the only things the unchecked list ever names. 3,456 of 3,457 rows now state it correctly. THE ONE REMAINING ROW IS HONESTLY EMPTY and now says why. "Arab, Gazan", 2,330,000 people, has no ISO 639-3 code in the source at all, so nothing could be looked up — there was nothing to look up WITH. Two blank cells read as an oversight; the row now reads "no language code in the source — no lookup possible". This moves the map's claims in the direction of knowing MORE, not less, which is the rarer correction here — the previous eleven went the other way. Verified: 0 rows left with blank provenance, the coverage gate and map gate pass, the gaps tab still renders with both export buttons, and the country filter still isolates Chad at 94 and Nepal at 54. |
||
|
|
29e4b153a4 |
List what is missing, in a form you can take away
Every table in this bundle lists what EXISTS. The map can filter to the gap and rank its 40 largest, but 40 is a preview and a map is not something you hand to a prayer group or work through in a spreadsheet. There was no exportable list of what is missing. "Where Nothing Was Found" is that list: 3,457 people groups, 347,030,459 people, 183 of them speaking a sign language. Largest first, with CSV and JSON export already provided by the shared table. IT IS NOT THE EXISTING "GAPS & OPPORTUNITIES" TABLE and could not have been folded into it. That one is one row per group across the curated 2,106-record set, so it structurally cannot see these 3,457. It keeps its own job — bridge-language analysis for the groups it does cover — and this sits beside it. EVERY ROW CARRIES ITS OWN PROVENANCE: checked_pools and unchecked_pools travel with the data, so an exported CSV cannot be read as a finding about the world. Rows with an EMPTY "pools checked" are groups whose language was in no resource table at all, and they say so rather than looking identical to a group that was searched and came back empty. The meta states outright that audio has never been searched for any group here. THE COUNTRY CLICK NOW REACHES IT. The deep-link filter applied only to the resource table, so a country click could show what a country HAS and never what it lacks — the half a mobiliser came for. Chad: 94 of 3,457. Nepal: 54. Both match the map's own country sheet exactly, which is the check that the two surfaces are describing the same groups. 180 ROWS PRINTED A RAW ISO3 IN THE COUNTRY COLUMN — "CHE", plus Canada, Taiwan, Timor-Leste, Mexico, Argentina. gospel-by-country knows 170 countries and the boundaries file 162, while the pins span 242. Names now come from people-groups-slim, the source the pins were built from, so coverage matches by construction rather than by luck; the curated spellings still win where they exist. 180 -> 1. The one left is Kosovo, whose ISO coding is genuinely contested across datasets, and its row is named "Deaf of Kosovo" so no reader is misled — one honest code beats starting a hand-written country table. Verified: the tab renders 3,457 rows with all eight columns and both export buttons, the country filter isolates Chad and Nepal correctly, and the coverage gate plus the table and deep-link probes all pass. |
||
|
|
3a468c2bc3 |
The poster print preview rasterises too, and a deployment can ship sources without rasters
Some checks are pending
build-gate / build (push) Waiting to run
posters/*.webp is not shipped by default (42MB; bundling pushed the tree to 80MB,
git-packed to 57MiB, and the catalog rejected the push). The DOWNLOAD path has
handled that since
|
||
|
|
02351725ba |
Gate the shape of last tick's bug, not just the instance
70 story sets were typed as Scripture for seven ticks because NOTHING compared a row's label to where it points. Clause H, added last tick, catches that exact mislabel. It would not have caught the same mistake made anywhere else. Clause I asserts the general shape: Film only on jesusfilm.org or arc.gt, Bible Stories only on git.door43.org and only under that name, Scripture never on a film host. Audited across all 3,550 rows first — 0 violations, plus 0 duplicate (language, url) pairs — so this ships as a guard rather than a fix. The 8 languages carrying more than one Scripture URL were checked and are correct: Cebuano really does have an eBible ULB alongside Door43's ULB and UDB, which is three translations, not three copies. THE PASS LINE NOW NAMES EVERY CLAUSE. It listed four of nine, which reads as a narrower check than it is, and the next reader calibrates their trust on that line rather than on the source. Nine clauses, nine names, and --poison still fails 9/9 on demand. The table also states its scope breakdown where a reader meets it — 855 New Testament, 254 Portions of both Testaments, 136 Full Bible, 14 Old Testament, 13 Portions — with the reason it is counted from each publication's own book list rather than its subject label. "How much Scripture exists" is the question under every other number in that table and it was previously visible only by sorting a column. Verified: gate passes, self-test 9/9, the table renders its new Scope column, and pins, scope and deep-link probes all pass. |
||
|
|
25405d571a |
Scope the Door43 Scripture, and stop 70 story sets being typed as Scripture
Door43 entries carry their own book list. Each ingredient is categorised bible-ot or bible-nt, so a Door43 Scripture entry can say how much Scripture it is, exactly as an eBible one does — and it is already in the response we fetch, so this costs no extra requests. 35 entries scoped: 15 New Testament, 13 Portions, 4 Full Bible, 3 Portions of both Testaments. THE SUBJECT LABEL IS NOT THE SCOPE, which is why it is counted from the data. An entry filed under "Bible" can be 27 NT books and nothing else. Calling that a Bible because of its folder name is the same error as calling Open Bible Stories Scripture. ★ AND THAT ERROR WAS ALREADY SHIPPING, 70 ROWS OF IT, BY MY OWN HAND. Tick 15 added owner disambiguation so two same-named entries could be told apart — "Open Bible Stories (OBS-TLF)". The type test was `d.s === 'Open Bible Stories'`, an exact match, so every disambiguated label fell through to Scripture. 70 story sets have been typed as Scripture for seven ticks: Bhojpuri, Maithili and 68 more told a reader they had a translation when they have 50 illustrated narratives. Found only because the type counts moved when this tick changed the test to a prefix. An exact match against a label that can grow a suffix is the whole defect, and the fix is not enough on its own — so clause H now asserts that nothing named "Open Bible Stories" is typed as Scripture, poisoned with the rest. 8/8 clauses fail on demand. The Door43 chip in the sheet had the same exact-match and now reads "Bible Stories (OBS-TLF) · Door43 · <scope>". The one `===` left in the fetcher is correct and was checked rather than blanket-replaced: it compares the RAW subject from the API, which never carries an owner suffix. Every Scripture row on the map now has a scope. The 2,278 blanks reconcile exactly to 2,087 film plus 191 Bible Stories — none of which is a portion of the canon. Verified: gate passes, its self-test fails 8/8 on demand, and the Door43, pins, table, scope and deep-link probes all pass. |
||
|
|
91dc9e6b33 |
Name and scope every Scripture link, including the 24 on an older edition
Last tick left 547 Scripture links with no scope and 24 with no name. Both had a cause worth finding rather than papering over. ★ THE 547 WERE A PATCH THAT SILENTLY DID NOTHING. Two code paths create an eBible entry. The edit that added `ss` matched a string containing httpsify(ee.u); the second path reads `s: ee.u` without it, so the replacement found no match, reported success, and changed nothing. 523 entries kept a title and a full-Bible flag while losing their scope — a half-applied change is harder to see than a failed one, because every field you happen to check is present. That branch now sets every field the other branch sets. Scope coverage 690 -> 1,213. THE 24 POINT AT AN OLDER EDITION than the catalogue's current pick — tel2010 where the catalogue says tel2017, ben2006 where it says benobcv. BOTH RESOLVE, checked 8 of 8 at 200, so the curated choice is a choice and fill-never-overwrite keeps it. What was missing was only the description, and the language index cannot supply it: it answers "the best translation for this code", not "what is THIS link". The catalogue is now also indexed BY URL, 1,545 entries, so a link is described by the edition it actually points to. Telugu keeps ebible.org/tel2010/ and is now named "Telugu Easy Reading Version — Full Bible". RESULT: 1,237 of 1,237 Scripture links carry a real translation name and a scope. Zero rows still read "Scripture in this language". 840 New Testament, 251 Portions of both Testaments, 132 Full Bible, 14 Old Testament. The 2,311 blank scopes in the table reconcile exactly — 2,087 film, 121 Bible Stories and 103 Door43 Scripture entries, which are not eBible publications and have no book counts to scope. Blank there is the honest answer, not a gap. Verified: the scope line renders on a real group and is ABSENT on a group with no Scripture, so it still discriminates; coverage gate passes; pins, table and deep-link probes pass. |
||
|
|
cc350514f3 |
Say which Scripture it is, and how much of it
Every Scripture row on this map said "Scripture". The catalogue knows the translation's actual name for 1,213 of the 1,237 languages that have one, and the difference it hides is the one a mobiliser most needs: "Ahirani Matthew and Mark" is two Gospels, "Bhadrawahi Bible" is a Bible. The table said "Scripture in this language" on all 1,237 rows and the sheet showed a bare chip. The sheet now names the translation and its scope — "German ELBBK Bible — Full Bible" — and the table carries the real name in its Resource column plus a new Scope column: 101 Full Bible, 461 New Testament, 122 Portions of both Testaments, 6 Old Testament. Scope is its own column rather than folded into Type, so Type stays a clean three-value filter. SCOPE DESCRIBES THE TRANSLATION WE ACTUALLY LINK TO. A curated scripture_url pointing at the same eBible page inherits it; one pointing elsewhere does NOT, because the catalogue's book counts would then be describing a different publication. That distinction recovered 96 entries and correctly leaves 547 blank rather than guessing. EBIBLE LINKS WERE THE TICK'S ACTUAL QUESTION and they came back clean: ebible.org/cmn2006/ renders the Mandarin Bible with its full book list in-script, png.bible/aai/ renders Minaifia NT from Matthew to Revelation. They land where a person can read, which is what the film links did not do. The scope work came out of reading those pages. ★ THE DEEP-LINK PROBE FAILED ON THIS COMMIT AND THE PRODUCT WAS FINE. It read the ISO code from cell index 4; inserting the Scope column moved it to 5, so it reported a filter failure while the filter was returning exactly the right 2 rows. It now finds the column by its HEADER. A column index is a position, a header is a contract — the same mistake the tooltip probe made two ticks ago by asserting wording instead of the rule. Verified: the scope line renders "German ELBBK Bible — Full Bible" on a real group and is ABSENT on a group with no scope, so it discriminates. Coverage gate passes, its self-test still fails 7/7 on demand, and pins, table, deep-link, tooltip and sign probes all pass. |
||
|
|
7b27bf1b83 |
Every link https, and a gate so it stays that way
1,237 of the URLs we ship were plain http — all of eBible.org, png.bible and alkitab.pw, with the host spelled `eBible.org` on six of them. Every one of those hosts serves https directly (sampled 12/12 at 200) and redirects http there anyway, so the plain link bought nothing but a redirect hop and a downgraded scheme in a field worker's browser, on a page that may itself be embedded over https. NORMALISED WHERE A URL ENTERS THE INDEX, not per fetcher. Doing it in the eBible fetcher first left 120 links behind — the curated scripture_url values, which fill-never-overwrite preserves and which therefore never pass through a fetcher at all. One httpsify at every emit point takes it to 3,548 https and 0 http. Scheme and HOST only. The path is left exactly as published, because a path is case-sensitive and "tidying" it would break the link rather than improve it. ★ THE GATE CAUGHT MY OWN DRIFT, ten seconds after I added the clause. Clause G failed — but as clause E, "1237 table URL(s) exist in no pin record": I had rebuilt the pins and not the table, so the two surfaces disagreed about every eBible link in the corpus. That is precisely the class this gate was written for last tick, and it found it on its first real use rather than three ticks later by hand. Clause G is poisoned in the self-test alongside the rest: 7/7 clauses now fail on demand. Verified: 0 plain-http URLs remain, six upgraded links sampled live all answer 200 over https, the coverage gate passes, and pins, table, deep-link, tooltip and list probes all still pass. |
||
|
|
799345cba2 |
Give the film links a destination: 2,049 real per-language deep links
Last tick established that ?languageId= is ignored by the site — a real id and a bogus id render the same generic library — so 2,087 groups had a film link that did not take them to the film. Arclight publishes the missing piece: media-components/1_jf-0-0/languages/<id> returns a shareUrl, and that IS a deep link — jesusfilm.org/watch/jesus/<language-slug>. 2,082 of 2,087 languages now carry one; 304 existing links were upgraded in place and the rest were written as deep links from the start. The 5 with no shareUrl keep the old URL and their tooltip says so, rather than every group getting the same hedge. FETCHED, NOT DERIVED, and that was the judgement call. The slug looks derivable — "Pashto, Southern" -> pashto-southern, and 8 of 8 sampled names transformed correctly. Eight is not 2,087, and a wrong slug is not a broken link but a link to SOMEONE ELSE'S language, which is the worst failure available here. So the API was asked 2,087 times, rate-limited at 250ms, cached and resumable so it is asked once. 5 missing, 0 wrong. ★ AND THIS CHECK CAN FAIL, which none of the previous jesusfilm checks could. A real slug returns a page carrying the media component id `1_jf-0-0`; a bogus slug returns 0 of them. Sampled 8 deep links: 8 real, 0 bad, and the bogus control returns 0 hits. In a browser the Bhilali link renders "FEATURE FILM JESUS · Watch now · 128 min FHD" while the bogus slug renders "404 PAGE NOT FOUND". That is the first time a jesusfilm URL has been verified rather than assumed in either direction. THE TOOLTIP PROBE FAILED ON THIS COMMIT, correctly: it asserted the old caveat wording, so fixing the links broke the test that described the defect. It now asserts the CONTRACT — a deep link must NOT carry the caveat and a legacy link MUST — which survives the next change to the words. SOURCES-CHECKED-AND-REJECTED.md records the shareUrl endpoint and the `1_jf-0-0` discriminator, so the next agent inherits the answer instead of the dead end. Verified: coverage gate, pins, table and deep-link probes all pass; the film chip on German renders the real deep link with the accurate tooltip. |
||
|
|
117467f7e7 |
Stop claiming Scripture in a language recorded as "Undetermined"
Reading the list profile's own screenshot turned up a row for "Undetermined (und)" — 14 people groups — offering two Open Bible Stories links. ISO 639-3 reserves und, mul, mis, zxx and qaa-qtz for exactly the case where the language is NOT known, so a resource attached to one is attached to nothing. Telling 14 groups there is Scripture in their language when the record says the language is undetermined is the single worst thing this map can do, and it took looking at the page to see it. Door43 publishes 4 entries under und; 16 pins sit on a special code. All are now excluded at the index. COVERAGE GOES DOWN, 8,935 -> 8,921 pins with a real URL. That is the right direction: the 14 were false. The same screenshot showed Dogri rendering "Open Bible Stories Open Bible Stories" — two links a reader cannot tell apart. 40 languages carry more than one entry under the same subject, because a language can have more than one independent translation effort. The owner now disambiguates, and ONLY where it is needed: Dogri reads "Open Bible Stories (translationCore-Create-BCS)" and "(OBS-TLF)", while a language with one entry is unchanged. The list profile also gained the pool it predated. It bundles its own resources.json at build time, so Door43 never reached it: 49 languages, 63 links. Checked before writing — 120 of its 862 languages already carry a scripture link and those are exactly the 120 the enriched index has, so nothing there needed correcting, and its curated 862-language scope is this profile's subject rather than staleness. The enrichment is idempotent: it drops what a previous run added before re-adding, or a stale title would survive the index learning to disambiguate. NOT DONE, DELIBERATELY: the 506 zero-reach rows still never match a COUNTRY filter. Arclight carries a primaryCountryId that could attribute them, but the only alpha-2 to alpha-3 mapping in this repo disagrees with itself on 18 countries and cannot resolve 51 of the 168 codes. A wrong attribution would point a real link at the wrong field, so the behaviour is documented in the table meta instead of guessed at. Verified: und/mul/mis/zxx absent from the index, the list renders 49 Door43 anchors where it rendered none, three sampled new links answer 200, and pins, table, deep-link, country and sign-language probes all still pass. |
||
|
|
694f3859d6 |
List every link, including the 377 languages no pin speaks
The table is titled "every link" and was silently dropping 506 real resources across 377 languages, because the generator skipped any code with no people group on this map. They are not edge cases: Ahirani has Scripture, a film and Open Bible Stories; Pahari Mahasui has Scripture, a film and five Door43 resources. Someone searching for that language still needs to find it, and the reach columns reading 0 is the true answer to "how many groups on THIS map speak it" rather than a reason to hide the resource. 3,043 -> 3,549 rows, 2,307 -> 2,684 languages. The meta says what a zero-reach row means so the column is not read as "this resource reaches nobody". THE INCLUSION EXPOSED A BLANK. Those codes are named nowhere in the people-group dataset — it only names languages some group speaks — so their rows rendered a bare ISO code with an empty language column, which tells a reader looking for a language precisely nothing. The eBible index now carries languageNameInEnglish alongside the translation title (two different things), and a final pass names any remaining code from whichever catalogue knows it. Codes carrying a resource with no language name: 377 -> 0. An audit of the reach columns against an independent recount off the pins found 0 disagreements on people_groups and 0 on speakers, so the table and the map agree about who speaks what. Verified: a seeded sample of 12 newly-exposed rows — 9 resolved 200, 3 skipped as unverifiable, because jesusfilm.org/watch answers 200 for any id and a status check there proves nothing. Three Door43 links first returned 000, which is curl failing to CONNECT rather than an HTTP status; retried with spacing they all answer 200, so the failure was my own request rate and not a dead link. Table, deep-link, pins, gap-list and country probes all still pass, and the country sheet still never fetches the retired 1.4MB file. |
||
|
|
45354c95ec |
Say what "none found" means for a Deaf group, because it does not mean the same thing
A sign language cannot be served by a text translation at all. eBible is a text pool, so its silence for a Deaf group describes OUR SEARCH, not their need — and the sheet was reporting it as "Scripture · none found" exactly as it does for a spoken language. That is the map telling a true-sounding thing that misleads. Sign languages are now flagged in the pin build and the sheet says so: what counts for them is Scripture FILMED in that sign language, and the text pool was never the right place to look. Counts are computed, not typed — 150 sign languages on the map covering 84,159,050 people, and the copy reads "none yet has both" only while that is true, since it is generated from the same pins the layer draws. THE SCALE: 234 pins on 150 sign languages, 183 of them with nothing found, 0 with both a film and a text Scripture. 13 have a JESUS Film through Arclight and 1 has Open Bible Stories through Door43; everything else is empty. ★ A NOTE LEFT IN THE BUILD FOR WHOEVER FILLS THIS GAP. The largest producer of sign-language Bible content by a wide margin is the Watchtower/JW organisation, which is excluded by standing rule — added canon. Anyone researching this will meet jw.org first and it will look like the obvious answer. The legitimate partners are the Deaf Bible Society and DOOR International, and both need credentials this build does not have: deafbible.com is a key-gated app, doorinternational.org answers 403. sign.bible is NOT a source — it is a parked domain listed for sale, which a status-code check would have happily accepted as live. No sign-language resource is added here, because none could be verified. What changed is that the map stops implying the absence was measured against the right pool. Verified on the served tree with a negative control: the note renders for Deaf Nicaraguan (Nicaraguan Sign Language, ncs) and does NOT render for German, so it discriminates rather than always firing. The rendered counts match the build's own tally exactly. Pins, deep-link, filter, gap-list and language-naming probes all still pass. |
||
|
|
f68ffc33ba |
Rank the gap, and name the languages that have nothing
The filter shows WHERE the gap is; it could not say which part of it is biggest. The 3,444 groups with nothing were reachable only by tapping dots until a large one turned up, which is not how anyone chooses a field. "See the 40 largest" now opens a ranked list in the sheet — the slot the country and group views already use, so no new geometry and no new collision with the control rail. Sorted by population, because choosing where to go is choosing how many people are behind the decision. Tapping a row flies there and opens that group. Fly first, THEN open: opening sets `selected`, which swaps the sheet away from the list, so the other order loses the map movement behind a re-render. THE LIST EXPOSED A REAL DEFECT. The largest gaps are exactly the groups with no entry in any pool, so they had no record to carry a language name and the sheet rendered "Language: (blank) cjy" for a 60-million-speaker group. Every language is now named from the people-group dataset, creating the entry where no pool has one: 2,251 codes are named-only — the language is known, nothing was found for it anywhere. That makes "has a code entry" ~100% by construction, so it is relabelled in the build output as naming and NOT coverage. Coverage is the line under it and still reads 8,935 of 12,379, 72.2%. A metric that reaches 100% because its meaning changed is worse than no metric. Verified on the served tree: 40 rows, populations strictly descending 60,310,000 -> 974,000 so "largest" is not a claim taken on trust; tapping row 1 moves zoom 1.3 -> 4.2 and opens Han Chinese Jinyu, whose scripture_url and film_url are empty and whose Door43 list is empty — so the list is not mislabelling groups that have something. 200 sampled zero-resource groups all name their language, where the screenshot before this change shows that field blank. Pins, table, deep-link and filter probes all still pass, and the full a11y sweep passes 810 of 810 buttons across 21 leaf pages. |
||
|
|
86a3c3bb99 |
Experimental roundup: FIVE legacy maps resurrected, keyless, in Experimental
Some checks are pending
build-gate / build (push) Waiting to run
The pre-bundler upg-neighbor IIFE holds five lost experimental maps --
send-map, upg-neighbors (cockpit), lang-cluster, bloom-cockpit,
flower-mesh. One wrapper bundle now rescues all five as preset
instances, each mounted in a sandboxed iframe whose host page:
- loads our self-hosted MapLibre and aliases it as the mapboxgl
global with a patched Map constructor that rewrites the one
mapbox:// style to keyless OpenFreeMap Positron -- the whole set
runs WITHOUT a Mapbox token;
- shims relative /api fetches to the live API and sets
MAP_APP_API_URL (the cockpit fails closed without it);
- passes the legacy profile-config contract (the elements share the
modern loader lineage).
Wrapper reads profile-config as a PROP (direct-defined element --
inject('profileConfig') is null outside ProfileLoader; the send-map
default masked the miss because send-map is instance #1). The
vocabulary patch rides in the shipped copy ('Lower pressure').
Verified by screenshots read: send-map renders the full 4-D SEND JOIN
app; flower-mesh blooms (India, 2,040 UPGs); the cockpit renders map,
charts and bloom pane. Auto-flows into the SPA's Experimental section
via manifest discovery.
|
||
|
|
f9808d0c8b |
Harvest the whole Arclight catalogue: film coverage 306 -> 2,087 languages
Same shape as the eBible finding, same cause. Arclight was ALREADY a checked pool and only 306 language codes carried a film link, while the catalogue lists 2,330 media languages with 2,087 carrying a feature film. Being inside a pool is not the same as having been read. COVERAGE: pins with a real URL 6,985 -> 8,935, 56.4% -> 72.2%. Language codes 2,068 -> 3,196. 1,950 pins lift off "nothing found" and 1,214 move to Scripture AND film. The resource table grows 1,497 -> 3,043 rows across 2,307 languages. ★ THE VERIFICATION HAD TO BE REDONE, and that is the part worth keeping. The obvious check is to sample watch URLs and confirm 200. Ten of ten passed. The check was worthless: https://www.jesusfilm.org/watch/?languageId=<id> is a single-page app and answers 200 for EVERY id — measured, `0` and `abcdef` both return 200. A check that cannot fail proves nothing, and that one would have certified 1,781 links on a status code that is always 200. The API discriminates where the page does not: media-languages/<id> returns a real counts.featureFilm for a genuine language and 404 for a bogus one. So existence is established from the API and the watch URL is the deep link for a language already confirmed. Re-verified that way: 10/10 sampled languages carry featureFilm >= 1, and the negative control — a bogus id — returns featureFilm 0, so the check can fail and does, on the case that should fail. featureFilm >= 1 IS THE COVERAGE TEST, not roster membership: 21 of the 2,330 listed media languages have no feature film and are dropped. Only featureFilm is taken; Arclight also carries short films and other media that were not screened. FILL, NEVER OVERWRITE, as with eBible — a curated film_url stands, and the catalogue only supplies a link where there was none. 1,695 of the codes had no row in the resources table at all. Verified: all browser probes pass — pins, table, deep link — 12,379 pins rendered, and the map now reads green across Papua New Guinea, Indonesia, Brazil and Central Africa where it was red, leaving the remaining red concentrated in the Sahel, the Horn, north India and Central Asia. The pin table is 1.1MB but 281KB gzipped; the four repeated pool strings compress 4:1, so interning them would buy nothing on the wire. |
||
|
|
4716e62ec5 |
Harvest the whole eBible catalogue, not a tenth of it
eBible was ALREADY one of the two pools this repo checked, and that is what made the gap invisible: 120 language codes carried an eBible link while the catalogue lists 1,550 translations across 1,239 codes with canonical books. The pool had been read at roughly a tenth of its content, and ~1,600 people groups were being told "nothing found" about a published translation in their own language. COVERAGE: pins carrying a real URL 5,349 -> 6,985, 43.2% -> 56.4%. Language codes 978 -> 2,068. The resource table grows 581 -> 1,497 rows across 1,314 languages. 1,090 of the new codes had no row in the resources table at all, which is why filling gaps in the existing 862 would have missed most of it. FILL, NEVER OVERWRITE. An existing scripture_url was curated and verified by an earlier pass; the catalogue is broad but not hand-checked, so it only supplies a link where there was none. Where the two disagree, curation wins. CANON TEST: OTbooks + NTbooks > 0. A deuterocanonical-only publication is not the 66-book canon and is dropped; DC books ALONGSIDE OT/NT are not a reason to drop, since the 66 are present either way. Measured on this snapshot: 3 rows carry no books at all. DOMAINS ARE ALLOW-LISTED AFTER A SCREEN, because publicationURL does not always point at ebible.org. Four domains appear and each was fetched and read: ebible.org (1,242), png.bible (302, "Papua New Guinea Scriptures"), alkitab.pw (3, an eBible mirror), and inscript.org (1) — EXCLUDED, because its page establishes no publisher or doctrinal provenance and an unclear source is left out. A domain not on the list is dropped, so a future catalogue update cannot introduce a publisher nobody screened. TWO BROKEN LINKS CAUGHT STRUCTURALLY: nko and nlx publish 'http:///nkoNT/' — a malformed URL with an EMPTY host, broken in eBible's own data. Requiring a non-empty host rejects them; a looser check for the 'http://' prefix passes them happily, which is how a "real" link that resolves nowhere gets shipped. badUrl and badDomain are counted separately for the same reason — folded together, this hid. Verified: the CSV parser was diffed against a reference parser across all 1,550 rows, 0 mismatches, so no URL is attached to the wrong language. A seeded random sample of 20 catalogue URLs resolved 20/20 at 200. All five browser probes still pass — pins, table, deep link, Door43 lift, and 12,379 pins rendered — and the map now shows Papua New Guinea, Indonesia and South America carrying resource colour where they were solid red. |
||
|
|
dfb4f755be |
Carry the map selection into the resource table
The table listed every link but had no idea what you had just tapped, so answering "what exists for THIS group" meant reading a language name off the map and typing it into a filter box. The map and the table are separate PAGES, so the selection travels as a URL: ?tab=links&iso=<code> from a people group, &country=<ISO3> from a country. FILTERED IN THE PROFILE, NOT IN THE SHARED COMPONENT. DataTable has other consumers and none of them need a filter prop; teaching it one would put a change with repo-wide blast radius behind a feature only this bundle uses. The rows are filtered before they are handed over, which is invisible to every other caller. COUNTRY FILTERING WITHOUT A SECOND FETCH. Rows carry country_codes, the ISO3 set the language is spoken in, so a country filter is a membership test on data already loaded rather than pulling the 679KB pin table just to resolve which languages a country has. +23KB for the column, against 679KB avoided. THE EMPTY CASE SAYS WHY. A selection with no match renders "nothing found for this selection in the pools checked, which is not proof that nothing exists" rather than an empty grid, which would read as a broken filter or, worse, as proof of absence. Switching to a tab that has no language key says so too instead of showing zero rows. Verified end to end on the served tree: clicked a real pin, followed the link THE SHEET RENDERED rather than one built in the test, and landed on 2 rows both carrying cmn with "Showing 2 of 581 resources for Chinese, Mandarin". Country path checked the same way — Nepal 51, Chad 22, unfiltered 581 with no banner, and an unmatched code showing the honest empty state. |
||
|
|
410f101846 |
A table of every resource and its link, keyed by the resource not the group
Every table in this bundle is keyed by PEOPLE GROUP and answers "who has nothing". None of them answered the question a mobiliser asks first: what exists, what is it called, and where do I open it. Answering it from a group-grained table means reading the same JESUS Film link off forty rows. 581 rows, one per language x resource, across 413 languages, together reaching 5,349 people groups — the same 5,349 the pin build counts as carrying a real URL, so the two tables cannot drift without one of them being wrong. Reach is MEASURED, not assumed: people_groups, speakers and countries are aggregated across the pins that actually speak that language, so a row says how many people that one link serves. Sorted by reach, because the link serving the most people is the one to open first. The column is named resource_url because DataTable keys on /_url$/ to render a real anchor instead of printed text. That is a contract with the shared component, not a naming preference — rename it and every link silently becomes a string. TYPES STAY HONEST. 146 Scripture, 306 Film, 129 Bible Stories. Open Bible Stories is typed as Bible Stories and never as Scripture, and the meta says why. The pools caveat and the doctrine boundary both render under the table rather than living in a file nobody opens: only 66-book-canon publishers are included, and the excluded ones are named so their absence reads as a rule rather than an oversight. Verified at 1280x900 on the served tree: the tab loads first, renders all ten columns and 50 rows a page, and 50 rows carry real anchors to ebible.org, jesusfilm.org and door43 — clicked through from the page, not read out of the JSON. |
||
|
|
af10d727c4 |
Add Door43 as a third pool, so 240 groups stop reading "nothing found"
Two pools had ever been checked — eBible and JESUS Film — and the map said "nothing found" for everyone outside them. For 240 people groups that claim was simply untrue: unfoldingWord publishes a released resource in their language and we were not looking. WHAT WAS ADDED. A dated snapshot of the Door43 catalog (stage=prod only) keyed by ISO 639-3, 165 codes across Open Bible Stories, Bible and Aligned Bible. Cached rather than fetched live so the pin table is reproducible offline and does not depend on someone else's uptime; the source URLs travel in the file. stage=prod IS THE WHOLE CARE. Door43 LISTS more languages than it HOSTS, and this repo already recorded that trap — "roster membership is not coverage". Released entries only, and the result reconciles cleanly against what was already known: of 38 languages previously flagged as having Open Bible Stories, 38 are confirmed and 0 contradicted. It also found 11 flagged false that do have a published resource. THE BIGGER HALF WAS INVISIBLE. 116 of the 165 codes have no row in people-group-resources.json at all, so there was no record to hang a link on and their pins stayed "nothing found" no matter what Door43 published. Those codes are now created from the catalog, which is where 213 of the 240 lifted pins come from. COVERAGE: pins carrying a real URL 5,109 -> 5,349 (41.3% -> 43.2%); codes 862 -> 978. Tier 1 now means SOMETHING was found rather than "one of scripture/film", because that is what the colour has to mean once a third pool exists. Tier 2 still means Scripture AND film — a story set does not get promoted into a Bible. OBS IS LABELLED AS STORIES. The chip reads "Bible Stories · Door43", never Scripture: Open Bible Stories is 50 illustrated narratives drawn from the canon, and telling a reader they have a translation when they have a story set is the one lie this map cannot afford. 2-letter and region-suffixed tags are DROPPED rather than guessed into 639-3 — a wrong language match ships a real link to the wrong people. Verified on the served tree: the Kir people group (Mandeali, mjl) previously showed nothing and now renders a real anchor to git.door43.org/OBS-TLF/mjl_obs, which answers 200. The existing pin probe still passes 12,379 rendered with both Han Chinese links at 200, so the new pool added coverage without disturbing the old. |
||
|
|
b05d1f13b4 |
Pin every people group, and link what exists in their own language
The map shaded COUNTRIES and stopped there, so the one question it could not answer was the only one that matters in the field: for THIS group, what exists right now? 12,379 people-group pins now sit on it, each one clickable into the resources for its language. THE JOIN. people-groups-slim.json carries Lat/Long per people-group-in-country; people-group-resources.json carries the real scripture_url/film_url per LANGUAGE. The key is ISO 639-3 (slim.LanguageCode == resources.iso639_3), never the group name — a group is reached in its language, not its nationality, and names collide across unrelated languages. MEASURED at build time: 5,981 of 12,379 pins (48.3%) join a resource record and 5,109 (41.3%) join one carrying a real URL, across 862 language codes. The other 59% is not a gap in the data to be hidden — it IS the subject. A pin with nothing found sorts ABOVE a covered one, so the gap wins the z-fight at world zoom rather than disappearing under a green dot. NO URL IS INVENTED. Every link is copied from the resources table, which records its own checked_pools/unchecked_pools, and the sheet keeps saying "none found means absent from the pools checked, never proof that nothing exists". has_audio keeps its third state: absent means the pool was never queried, not a no. PIN COLOURS READ OFF BANDS rather than declaring a fourth palette, so a pin and the country under it can never disagree about what "nothing found" looks like. The table is generated into data/tables/ — the REPO ROOT copy, which is what generate-tree publishes. The plugin-local data/tables/ is a stale divergent duplicate that nothing serves; building into it ships a file no page fetches. Verified on the served tree at 1280x800: 12,379 pins loaded AND rendered, layer and source present, and a click on Han Chinese returns ebible.org/cmn2006/ plus jesusfilm.org languageId=53352, both rendered as real anchors in the sheet and both answering 200. window.__grState() exposes those counts so the next check does not have to guess at screen coordinates to hit a 3px dot. |
||
|
|
3c96917dbe |
Vendor Vue for the gallery shell, so the self-hosted rung finally exists
Some checks are pending
build-gate / build (push) Waiting to run
generate-gallery.mjs:542 requests assets/vendor/vue/vue.global.prod.js, and generate-tree.mjs staged only vendor/tabulator -- so that request has 404'd on every build there has ever been. It survived because loadVue() degrades in three rungs: self-hosted, then the jsDelivr CDN, then a plain-DOM render. On any networked machine rung 2 silently covers for rung 1, and the only symptom was a 404 in `bun run smoke`, a gate nobody was running. Good degradation hid a missing build step. It mattered anyway: the comment above loadVue promises "self-hosted first (portable / offline / no-CDN)". Measured in a browser with no outbound network, the gallery fell all the way to the plain-DOM degrade -- usable, but not the Vue shell, and the offline claim was false. A tree dropped on a foreign host or opened over file:// is exactly what that rung is for. Vendored from node_modules/vue 3.5.42, placed in data/vendor/ (the real source -- generate-tree rmSyncs public/assets/vendor and re-copies from data/), tracked in both places exactly as vendor/tabulator already is. Verified: build exit 0 with zero missing-asset warnings; the gallery now loads Vue 3.5.42 FROM THE VENDORED COPY (not the CDN), the Vue app mounts, nav renders 5 rows. `bun run smoke` went from 4 failures to 2. A correction en route: I first blamed `bun run clean` for deleting the file. clean.mjs only wipes public/doxa-maps, never public/assets -- the real cause was generate-tree.mjs:1189 rmSyncing public/assets/vendor and re-copying it from data/vendor, which is why tabulator persists and a hand-placed file does not. |
||
|
|
2d64e21fbf | Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) | ||
|
|
b7d91e9aca |
Public data ships without working notes; unused data files stay out of the drop-in
- data/ tables and the people-groups file: meta blocks reduced to provenance (source, url, downloaded, rows, grain, attribution); the link-verification table keeps only state and detail per link; generators write the same shape - dropin.exclude in the build config: people_groups.csv, open-doors.json, platform-reachability.json and tables/index.json are not shipped (nothing loads them); sync-dropin honours it - public-hygiene --strict flags note-style meta keys in shipped data - Rebuilt bundles and gallery |
||
|
|
12d1fee133 |
Public build folder is three entries: index.html, assets/, doxa-maps/
- index.html is generated by internal/generate-spa.mjs from doxa-maps/manifest.json: one self-contained page that lists every bundle, profile and instance, mounts the chosen one, and offers Open page / Copy embed; build ends with `bun run spa` (the material shell is no longer installed; nav.js, icons.js, languages.js, pane-wrapper/, external/ and _headers leave public/) - The website drop-in follows the same contract: dropin.include in the build config, sync-dropin --from-config --prune, public-hygiene --strict fails on any other root entry - Production bundles no longer carry CSS comments: a build-only Vite plugin strips them from every shadow-style block (about 140 per map bundle, 5% smaller); remaining comments reworded - Share pages and the pin renderer drop their note-style comments |
||
|
|
3fee91a193 |
Release governance: field-access data is private, never shipped
data-private/ is born: datasets whose publication could help close doors stay out of the web-root and out of public repos, permanently. field-access-visas moved there; the tables index no longer lists it; the key-cities Field Access tab is removed from the public build; the table generator routes it (and any future PRIVATE_TABLES entry) to data-private/ and excludes it from the index. Public-good data (internet gaps, gospel-resource gaps, people-group pairings) ships as before. |
||
|
|
5ef3c01c8d |
Data out of public/ (phase 2): posters + tables live in data/
posters/ (42M webp set the poster walls fetch) and tables/ (the merged served table set — its index.json is the merge; per-profile data/tables remain the build-time sources) now live in data/ with build copy-steps. Byte parity verified; served paths unchanged. public/ is now FULLY regenerable — every source-of-truth folder has a home outside the web-root. |
||
|
|
eca13ac2ff |
Data out of public/: sources live in data/, the web-root is generated
public/ was confusing because three source-of-truth folders lived ONLY inside it. They now live in data/ (people-groups, vendor, external-bundles) and the build copies them into the served tree (same pattern as boundaries), so served paths are unchanged and public/ is fully regenerable. data/README.md states the law. |