Commit graph

174 commits

Author SHA1 Message Date
doxa-maps-88655c70
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.
2026-09-24 07:06:21 +00:00
doxa-maps-88655c70
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.
2026-09-24 06:58:12 +00:00
doxa-maps-88655c70
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.
2026-09-24 06:47:23 +00:00
doxa-maps-88655c70
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.
2026-09-24 06:40:37 +00:00
doxa-maps-88655c70
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.
2026-09-24 06:15:58 +00:00
doxa-maps-88655c70
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.
2026-09-24 06:09:11 +00:00
doxa-maps-88655c70
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.
2026-09-24 05:22:59 +00:00
doxa-maps-88655c70
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.
2026-09-24 05:13:57 +00:00
doxa-maps-88655c70
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.
2026-09-24 04:39:10 +00:00
doxa-maps-88655c70
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.
2026-09-24 04:30:11 +00:00
doxa-maps-88655c70
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.
2026-09-24 04:20:24 +00:00
doxa-maps-88655c70
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.
2026-09-24 04:08:30 +00:00
doxa-maps-88655c70
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.
2026-09-24 04:01:04 +00:00
doxa-maps-88655c70
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.
2026-09-24 03:35:35 +00:00
doxa-maps-88655c70
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.
2026-09-24 03:25:13 +00:00
doxa-maps-88655c70
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.
2026-09-24 03:18:27 +00:00
doxa-maps-88655c70
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.
2026-09-24 03:02:06 +00:00
doxa-maps
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.
2026-09-22 04:59:30 +00:00
doxa-maps-88655c70
2d64e21fbf Complete app-profiles->plugin-designs sweep across comments/docs; build green; layouts kept at root (load-bearing) 2026-09-17 18:18:19 +00:00
swift-river542
b7d91e9aca Public data ships without working notes; unused data files stay out of the drop-in
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- 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
2026-09-16 20:16:08 -05:00
swift-river542
12d1fee133 Public build folder is three entries: index.html, assets/, doxa-maps/
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- 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
2026-09-16 18:23:14 -05:00
doxa-maps
3fee91a193 Release governance: field-access data is private, never shipped
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
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.
2026-09-16 04:34:33 +00:00
doxa-maps
5ef3c01c8d Data out of public/ (phase 2): posters + tables live in data/
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
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.
2026-09-16 01:37:52 +00:00
doxa-maps
eca13ac2ff Data out of public/: sources live in data/, the web-root is generated
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
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.
2026-09-16 01:24:41 +00:00