diff --git a/plugin-designs/gospel-internet/README.md b/plugin-designs/gospel-internet/README.md new file mode 100644 index 000000000..a6f7387df --- /dev/null +++ b/plugin-designs/gospel-internet/README.md @@ -0,0 +1,120 @@ +# Gospel × Internet + +Which people groups already have gospel resources in their own language, and where the internet can +carry those resources to the ones that do not. + +This file is the durable record of how the board is built. It replaces a planning document that lived +outside the repository, so the comments in this bundle can cite something a reader of the public +source can actually open. + +## The views + +One tab strip over one shared state. Every view is a URL state +(`?view=&access=&band=&country=&province=&lang=&list=&group=`), so any view can be shared or embedded +exactly as the reader left it. + +| View | What it answers | +|---|---| +| **Board** (default) | map + facets + grid + drawer + charts, over every group in the chosen list | +| **Nothing in own language** | the groups with no resource found, as a front door, by population against their country's access band | +| **India & China by province** | province outlines and per-province counts, where national figures are too coarse to act on | +| **Language tree** | every language family of these people groups, coloured by what exists in each language | +| **Bounded set** | one boundary: reached inside, unreached outside | +| **Centered set** | rings by how close a group is to a reached neighbour, arrows pointing inward | + +## The people-group list + +The board draws a **list**, not the whole table. The default list (`doxa`) fetches nothing and filters +nothing. A different list keeps only the rows whose slug belongs to it and **fails closed**: if its +membership cannot be loaded the map gets no rows rather than silently falling back to everything. + +## The two set charts + +Both are drawn on a **whiteboard projection** — an abstract plane mapped through the map engine, so the +charts pan, zoom and click like the rest of the board instead of being pictures beside it. + +**Rings.** 1–10 is placement by distance from a reached neighbour; ring 0 is the centre (the group *is* +reached, not missing); ring −1 is "no relation found", which is a real outcome and never rounded onto +ring 10. A group absent from the join is reported as unmatched, separately again. + +**Bands, not circles.** `library/components/charts/ringBands.js` spreads the groups on a ring *through* +its band rather than along one circle. Positions are a pure function of the group's own id, so two +renders — and two printings of one packet — are identical, and a dot that moves means the data moved. +There is deliberately no seed: a seed would rescramble every dot while the data was unchanged, which on +a printed sheet is unrecoverable. + +**Labels first.** Each ring label reserves an arc, and the band fill refuses to place a pin inside it. +Both come from one source, so "pins never overlap the labels" holds by construction. A label longer +than its ring can carry truncates rather than eating the circle: uncapped, a long name on ring 1 once +claimed 96% of that small circle and the innermost ring drew empty. + +### `fit` and `fill-all` + +The binding constraint is the band's **depth**, not the arc's length. Separation cannot fall below twice +the pin floor, so a band of the current depth holds at most three rows — and no separation setting +reaches the busiest ring's population. + +- **`fit`** (the default today) keeps every pin separable at the chart's own scale and *reports* what + will not fit. This is the honest mode for a printed sheet, which cannot zoom: a dot a reader cannot + resolve is a dot that is not there. +- **`fill-all`** places every group and lets zoom do the separating, since separation is in map units. + Zoomed out a busy band reads as an arc of colour; zoomed in the pins separate and become clickable. + +Whichever mode is used, the caption states the number of groups that **have a ring** — a fact about the +data — and never the number of pins currently drawn, which would change as the reader zooms. + +### Why the charts fit their box + +The engine renders one world without copies, so it will not zoom out past the point where that world +fills the canvas *width*. On a wide, short figure that floor is too close for the drawing's own height, +and the outermost groups fall outside the frame. The fix is the chart's own footprint +(`extentFraction`), not the engine's behaviour. `check:gospel-set-fit` asserts every layout still fits +at that floor, with the previous footprint as its negative control. + +The fit follows the container with a `ResizeObserver`: these panels are `v-show`, so a map can be built +while its container is `display:none` and zero-sized, and a one-shot fit against that box lands at the +wrong zoom. + +## Data joins + +People groups join the near-neighbour table by **slug** first — the only key that is one field on both +sides and unique by construction — then by `rop3 + iso3`, then by `name + iso3`. Rows that match none +of these are counted and reported, never guessed at. + +Administrative names are matched **diacritic-folded**, at the lookup rather than by spelling each +variant, so "Jämtland" and "Jamtland" reach the same zone. + +## Honesty, and what this board will not say + +These are constraints on the product, not preferences: + +- **Pins are approximate.** A dot marks where a people is found in a country, not a town. The notice is + shown on the map and in every drawer, and provinces are marked approximate everywhere. +- **Never "reached".** The words are "resource found in the sources checked" and "nothing found in own + language". No game words — nothing is "claimed" or "done". +- **Never who works where.** No engagement status, no agencies, no workers, no teams, no field access. + The column exists in the data and is not read. +- **Internet figures are the country's**, or the state's, with source and year on every surface. There + is no group-level internet claim. +- **Public data only**, and every outbound link goes through the doctrine screen and allowlist. + +## Columns and export + +One row per group: people group · country · province (IND/CHN, approximate) · language · population · +access level · best resource · bridge language and strength · country internet % with year · internet +band · top platform · pray link. Hidden by default but available in the picker and in CSV: +`translated_by`, `training_url`, `bridge_training_url`, `deaf_bridge_publisher`, `mobile_per_100`, +`people_praying`. + +Export writes the filtered grid in its visible columns, with a header line carrying the sources and the +date. + +## Its gates + +| Gate | What it refuses to let through | +|---|---| +| `check:gospel-set-charts` | the set layouts' pinned properties, including that no chart grew a `seed` prop | +| `check:gospel-set-fit` | a layout whose drawing cannot fit its canvas at the engine's zoom floor | +| `check:ring-bands` | a pin outside its band, two pins closer than their own diameter, a pin under a label, or output that is not identical for identical input | +| `check:gospel-zone-tree` | the admin→zone join, including the diacritic folding | +| `check:gospel-whiteboard` | the whiteboard projection's contract | diff --git a/plugin-designs/gospel-internet/board.meta.json b/plugin-designs/gospel-internet/board.meta.json index 7b81fde72..f92967a0f 100644 --- a/plugin-designs/gospel-internet/board.meta.json +++ b/plugin-designs/gospel-internet/board.meta.json @@ -5,7 +5,7 @@ "pinHint": false, "urlState": "query" }, - "note": "pinHint:false because the board has its own legend and drawer; the shared coach-mark would float over the facet rail. urlState:'query' makes the board's own page keep ?view=&access=&band=&country=&province=&language=&group= in the address bar (shareable views, SPEC-GOSPEL-INTERNET-DASHBOARD section 3). A partner embedding the element without it keeps their URL untouched: the board READS the query always and WRITES it only when asked.", + "note": "pinHint:false because the board has its own legend and drawer; the shared coach-mark would float over the facet rail. urlState:'query' makes the board's own page keep ?view=&access=&band=&country=&province=&language=&group= in the address bar (shareable views, README.md, \"The views\"). A partner embedding the element without it keeps their URL untouched: the board READS the query always and WRITES it only when asked.", "stage": "experimental", "heavy": true } diff --git a/plugin-designs/gospel-internet/board.vue b/plugin-designs/gospel-internet/board.vue index 8a4d6ddad..a59fff266 100644 --- a/plugin-designs/gospel-internet/board.vue +++ b/plugin-designs/gospel-internet/board.vue @@ -6,7 +6,7 @@ * * One board for sending gospel resources through the internet, embeddable by partners. * - * SPEC: _qa-cycle/03-deployment-cycle/SPEC-GOSPEL-INTERNET-DASHBOARD.md (MVP = section 15, phase 1). + * How this board is built, and what it refuses to say: ./README.md * * THE SHELL, built on the dashboard kit: the selection bus (library useSelectionBus, filter / selection / * hover), the layout engine (library useDashboardLayout: three drag handles, keyboard operable, diff --git a/plugin-designs/gospel-internet/gospel-internet.instances.json b/plugin-designs/gospel-internet/gospel-internet.instances.json index 31f784c56..50b48c7d8 100644 --- a/plugin-designs/gospel-internet/gospel-internet.instances.json +++ b/plugin-designs/gospel-internet/gospel-internet.instances.json @@ -1,5 +1,5 @@ { - "_comment": "Preset partner embeds of (SPEC-GOSPEL-INTERNET-DASHBOARD section 14). Keyless: provider 'auto' falls to MapLibre with no token. Each preset is a URL a partner can QR.", + "_comment": "Preset partner embeds of (see README.md: every view is a URL state, so a preset is just that URL). Keyless: provider 'auto' falls to MapLibre with no token. Each preset is a URL a partner can QR.", "instances": [ { "id": "board", diff --git a/plugin-designs/gospel-internet/scripts/build-gospel-internet.mjs b/plugin-designs/gospel-internet/scripts/build-gospel-internet.mjs index fb6b916eb..274fe10e3 100644 --- a/plugin-designs/gospel-internet/scripts/build-gospel-internet.mjs +++ b/plugin-designs/gospel-internet/scripts/build-gospel-internet.mjs @@ -27,7 +27,7 @@ * (It replaces this builder's own point-in-polygon, which agreed with theirs group for group.) * * ★ WHAT IS NEVER READ: doxa-people-groups.rows.engagement, or anything that says who works where - * (SPEC-GOSPEL-INTERNET-DASHBOARD section 13). Nothing from plugin-designs/key-cities/. + * (see ../README.md, "Honesty"). Nothing from plugin-designs/key-cities/. * * Codes, not labels, cross into the plugin: access_level own|bridge|zero, access_detail * read_watch|hear|read|stories|none, internet_band yes|partial|no|nodata. The plugin translates. diff --git a/plugin-designs/gospel-internet/src/columns.js b/plugin-designs/gospel-internet/src/columns.js index 07c13a501..b56023ea1 100644 --- a/plugin-designs/gospel-internet/src/columns.js +++ b/plugin-designs/gospel-internet/src/columns.js @@ -1,6 +1,6 @@ /** * columns.js — the grid's columns and the CSV's columns: ONE list, so what a reader sees is what the file - * holds (SPEC-GOSPEL-INTERNET-DASHBOARD section 5 and 9). `hidden` columns are off in the grid until the + * holds (see ../README.md, "Columns and export"). `hidden` columns are off in the grid until the * picker turns them on, and the CSV exports exactly the columns that are ON. * * `csv` is the raw value (codes, numbers, URLs) — a spreadsheet joins on it, so it is never translated. diff --git a/plugin-designs/gospel-internet/src/components/GiDrawer.vue b/plugin-designs/gospel-internet/src/components/GiDrawer.vue index 05e5aabfe..7bdabb092 100644 --- a/plugin-designs/gospel-internet/src/components/GiDrawer.vue +++ b/plugin-designs/gospel-internet/src/components/GiDrawer.vue @@ -1,6 +1,6 @@