Commit graph

24 commits

Author SHA1 Message Date
Doxa Maps
7cd40a6e8e Pin wording: shared points and key-city clusters claim only what is certain
A pin's position is generalised, so it does not tell a reader the locality or where a group is
centred.

- pins.sharedPoint in all 11 locales and ApproxPinNote.vue's fallback: the groups sharing the point
  are named and counted ("{n} groups share this dot"); the locality claim is gone.
- keyCities map.note, map.sheetNote and list.caveat.row in all 11 locales: a cluster is of
  people-group pins, not centroids, and each pin marks a people group, not a town.
- The key-cities and languages-per-city caveats in build-tabulator-tables.mjs and in every committed
  copy of those two tables (only that one field changes), derive-city-candidates.mjs's caveat, and
  the matching comments in key-cities, name-clusters and the poster generator.
- check-approx-pin-note's pass line no longer calls pins localities. Two UI-strings baseline entries
  whose strings no longer exist in their files are dropped.
2026-10-02 09:21:54 +00:00
Doxa Maps
b3daa34ab1 P2 (2/2): five more lines without "design note" as a person, in the UPG-100 data scripts
clusters.json (one decision_pending value; the field is not in the built tree), retag-adm1.mjs (a
comment and a console message), search-index.js and uncovered-proof.mjs (comments) state the reason
without a persona. derive-city-candidates.mjs's safety_note no longer repeats "separate".
2026-10-02 08:51:19 +00:00
Doxa Maps
8b83c34a79 P2: comments state the reason without quoting anyone or naming a role
Seventeen lines in 15 files. Six quoted short fragments from feedback; eleven used "design note" where a
person once stood ("design note selects 2+ rows", "a design note design call"). Each now states the
reason in neutral terms and keeps the technical content and dates.
All edits are comments, except one string: the safety_note text in
library/scripts/research/derive-city-candidates.mjs, a research script that is not bundled and whose
output is not committed. No logic changes.
2026-10-02 08:45:40 +00:00
Doxa Maps
63aad26e92 The private entry-policy table is built by its own script outside the public export; the public research script carries no trace of it 2026-10-01 16:32:22 +00:00
Doxa Maps
2c397d25ef Source comments state design history and measurements as facts, without quoting or naming anyone
Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person,
named fleet agents or carried private host names now state the same technical fact in neutral words:
poster-render (including the comments it writes into generated posters), packet-render, the poster and
research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note
states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an
agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in
its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/),
which nothing reads, are removed.
2026-10-01 00:37:17 +00:00
Doxa Maps
5203bf06c4 A landing cannot grow a bundle past the size ceiling: landing-preconditions runs the size check
verify-bundles-shipped (every shipped bundle under 1,500,000 bytes) ran only
inside `verify`, in gates:post, and nothing in the landing called it. So a
landing that grew a bundle past the ceiling landed and then went red:
694b00991 shipped country-posters at 1,662,962 bytes. It is now step 3c of
the landing precondition, on the rebuilt tree, before the push (~0.2 s).

Its message now reports bytes: it printed "1.6MB (ceiling 1.4MB)" for
1,662,962 against 1,500,000, mixing MiB with the decimal ceiling.
2026-09-29 07:31:17 +00:00
Doxa Maps
bfc99ff827 library/ keeps the reason and drops the speaker: no comment quotes or cites the Driver
Every reference to the Driver in library/ is reworded: the engineering reason
stays, the speaker and the quote go, and a decision still open reads
"provisional, pending a product decision". Comments only: each changed file
is identical after stripping comments (JavaScript, template and CSS, including
CSS comments inside the style strings shadow-DOM components inject).

Those style-string comments do ship, so several bundles change by exactly
those comment bytes.
2026-09-29 05:26:25 +00:00
Doxa Maps
6dd5278584 Svalbard carries its own data, not Norway's
iso3-to-iso2 mapped SJM to NO, so every research fetch keyed through it filed
Norway's figures under Svalbard. With SJM -> SJ on main, the four fetched tables now
hold what the pipeline produces for Svalbard itself, re-fetched for SJ through each
script's own code:
  network-operators      60 Norwegian networks -> none (RIPEstat: 0 ASNs for SJ)
  platform-reachability  15 rows of Norway's OONI -> 15 not-measured (no SJ probes)
  social-platforms        8 rows of Norway, 2026-07 -> 4 rows of Svalbard, 2026-09
  messaging-channels      3 rows from Norway's store -> none (no SJ storefront)
Norway is not added: it is not in the DX country spine these tables are built over,
so no build produces Norway rows here. Svalbard's own people group rows elsewhere
are untouched, and every other country's rows are unchanged and in order.
2026-09-27 05:05:04 +00:00
Doxa Maps
8f79a3af0f Norway gets into the country table at all
NOR was absent from iso3-to-iso2, so Norway appeared in no research table:
consumers filter on ISO3_2[iso3], which makes a missing code a silent
omission rather than an error. Its absence is also why SJM -> NO read as
injective -- "NO" had exactly one claimant, the wrong one.

NOR -> NO, from ISO 3166-1, which is what CLDR's codeMappings encodes. The
full CLDR vendor still comes with the 242-country table and will re-derive
this alongside the other 66 missing codes; this is the one line
exp-shepherd needs first to re-key the mislabelled Svalbard rows onto
Norway.

NOR joins the named spot-check, and the completeness floor rises 175 -> 176
with it. Left at 175 that check would have called a regression back to the
pre-NOR map clean, which is the opposite of what it is for. Removing NOR
now fires both guards; injectivity still reports "all 175 codes map to a
DISTINCT ISO2" while it does, for the third time in this defect.
2026-09-27 04:14:14 +00:00
Doxa Maps
1eae1d8c10 Svalbard gets its own country code
SJM mapped to NO, which is Norway. Every research fetch keyed to Svalbard
and Jan Mayen therefore asked the APIs about Norway. SJM -> SJ.

Injectivity could not see this, and that is the part worth keeping: the
check passes as long as no two ISO3 codes claim the same ISO2, and NOR is
absent from the map entirely, so "NO" had exactly one claimant and read as
clean. A uniqueness test cannot tell a right code from a wrong one nobody
else wanted. So SJM joins the named spot-check beside PSE and HKG, which
were wrong the same way -- mapped to their administrator's code. Reverting
the value now names it; before, the verifier still printed "all 175 codes
map to a DISTINCT ISO2".

The mapping is the half that stops a future fetch being wrong. The rows
already fetched are still Norway's, keyed to Svalbard: 60 network
operators (Telenor Norge AS, GlobalConnect AS, Marlink AS), 15 platform
reachability rows and 8 social-platform rows, in the shipped tables under
public/assets/tables/. Those belong to other plugins, so they are routed
rather than rewritten here.
2026-09-27 04:14:14 +00:00
Doxa Maps
1801bbc20f Messaging channels: a taxi and a food app are no longer a country's top messaging app
The classifier matched "line" as a bare word, so "LINE MAN: Food Delivery & more"
(Thailand) and "Malé Taxi Line" (Maldives) each ranked as the country's top messaging
app, ahead of WhatsApp. LINE now matches only as the whole name or before a colon.

The classifier moves to app-kind.mjs so the fetcher and the table builder share it,
and the builder re-derives each kind from the app name, so a cached fetch made under
an older rule cannot carry its answer forward. The headline count is computed from
the rows instead of typed: WhatsApp (Messenger or Business) is top in 73 countries.
2026-09-27 03:25:59 +00:00
doxa-maps-88655c70
79bd6d2ac3 public/ is one folder: the output tree plus a README, with the Plugin Studio as its front page
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Driver decisions (cards BUILD-OUTPUT-IS-ONE-FOLDER, BUILD-PLUGIN-STUDIO-IS-THE-OUTPUT-FRONT-PAGE).

- public/ holds only <tree>/ and a generated README.md. The build no longer writes public/index.html,
  public/assets/ or public/__gallery/; `rm -rf public && bun run build` recreates exactly these two.
- The Plugin Studio is <tree>/index.html (tree-base "."), replacing the staging-forge chooser; nothing
  is written above the tree. check:staging-lang, which tested that chooser, is retired with it.
- Assets are gathered in a temp directory and the fetched allowlist staged into <tree>/assets/ as
  before. fonts/, images/, brand/ and legacy/ existed ONLY in public/assets: they are sources and move
  to data/ (git mv). Poster previews keep their tracked sources in plugin-designs/*/assets/previews
  (all 378 verified present).
- __gallery/ is archived out of public/ (a copy kept in the umbrella's _qa-cycle/archive) and out of
  the build; generate-gallery renders only on request, into a directory you name, never public/.
- Native shells load the output folder: capacitor webDir public/<tree> (cap copy needs an index.html
  there), Tauri frontendDist + window url relative to it. internal/sync-app-shells.mjs keeps both in
  step with the tree's name on every build, so the coming rename stays one config line; the Capacitor
  start page resolves its leaf tree-relative.
- dropin.include now lists only extras (README.md): the tree is always shipped and never named twice;
  excludes are tree-relative ({tree}/...). clean.mjs removes the old root entries from existing
  checkouts; the build lock and public/.gitignore's rules move out of public/.
- check:wp-embed read the BUILT manifest from gates:pre, before the build, so a from-scratch build
  could never pass; it runs in gates:post and reads the tree name from config.
- Stale bundles refreshed by the clean build (key-cities, template-bundle, wall-of-people-groups);
  the CLI doc's quoted scripts re-synced; capability exemplars regenerated; the '#' note leaves
  check:stale-sweep-claim's command (it was already in //scripts).

Proof: rm -rf public && bun run build -> public/ = README.md + doxa-maps/; check:sri, check:drop,
check:parse, check:public-hygiene, check:quoted-scripts, check:capability-exemplars PASS; all 57 pages
(the front page + 56 leaves) mount under /doxa-maps/ on this tree. check:facet-cascade is red as on main.
2026-09-25 17:41:56 +00:00
doxa-maps-88655c70
412602cdef Five more gate scripts read the tree's name from the build config
The research verify-* scripts and research-map-plus-grid's two gates hardcoded 'doxa-maps' (11
literals); they now import TREE_NAME from internal/_tree-name.mjs like the rest of the tooling.
2026-09-25 16:25:22 +00:00
doxa-maps-88655c70
7f700935c5 Simple Research + Big Picture: the host's style stops at the box, and the last English goes
TWO FIXES THAT BOTH SHOW UP ONLY WHEN THESE PLUGINS ARE SOMEBODY ELSE'S GUEST.

1. ATTRIBUTE FALLTHROUGH. These SFCs mount AS custom elements, so a style the
   host puts on the tag lands on the component's own root element. MEASURED with
   a host style of "display:flex; position:relative; height:100%": that exact
   string appeared verbatim on the inner root div of people-groups-grid,
   stack-demo, research-map-plus-grid and dashboard-plus-grid. The gallery sets
   one on every tab, and the same fallthrough collapsed the Prayer Walk Planner
   to 76px. inheritAttrs:false on each root — a host styling the TAG is styling
   the box, not our internals.
   AFTER: the inner root carries no inline style on any of the four, and each
   still renders full height (2997-4010px in the harness), so the guard did not
   become the collapse it prevents.
   Worth knowing for the next probe: shadowRoot.firstElementChild is a <style>
   node — Vue injects styles first — so a probe that trusts it measures nothing
   and reports "no fallthrough, nothing to fix". Take the first non-style child.

2. THE RESIDUAL CHROME the in-gallery re-measure found: the dashboard title,
   Align Dashboard and its tooltip, World, Vertical Legend, groups in scope, the
   per-row aria templates (Filter by / Remove filter / groups), and the map
   drawer's aria label. Catalogued in all 11 locales and read through tStatic,
   reusing existing wording where the catalogue already had the word.

The aria TEMPLATES matter more than their size suggests: they repeat per row, so
one untranslated template is dozens of English strings in a screen-reader's ear.
2026-09-25 08:19:56 +00:00
doxa-maps-88655c70
3fd1fbcaea Move the private field-access dataset out of the repo
data-private/ was a TRACKED folder, so field-access-visas.json would ship
with the repo the moment it goes public. A tracked folder is not private.

The table generator no longer writes inside the checkout: private tables go
to DOXA_PRIVATE_DIR (a folder outside the repo) or are skipped. data-private/
is gitignored so it cannot be re-created by accident.

The file is removed from HEAD only; it is still in history (5 commits).
Publish from fresh history or scrub it before the repo goes public.
2026-09-24 18:34:01 +00:00
doxa-maps-88655c70
4cb04480cc Localize tab labels for it/ro/zh, and prove the catalog is actually read
Some checks are pending
build-gate / build (push) Waiting to run
120 keys across three locales: legend.json (12 each), search.json kinds (10
each) and tabs.json (18 each, previously absent entirely). Each locale follows
its own established vocabulary rather than a parallel one — Popolo/Popor/民族,
matching the terms already used in that catalog's detail and legend strings.
zh stays Simplified here, as the rest of this tree is, even though the prayer
packet's zh block is Traditional: a reader sees 民族 next to 祷告进度, never
the two files side by side.

Ceilings drop 94 -> 54 per locale. regions.json is the only i18n debt left in
the app: 54 keys of full sentences, three locales.

WHY THE NEW GATE. check:i18n-parity compares catalogs TO EACH OTHER, so it
certifies a file no t() call ever reaches — perfect symmetry, zero consumers,
green forever. Grepping for t('tabs. found ZERO call sites and tabs.json was
nearly recorded as dead. It is not: the only consumer builds the key
dynamically (SemanticTreeLegend.vue:40, 'tabs.' + (tab.i18nKey || tab.id)),
which no literal-prefix grep can see. The reverse error appeared in the same
pass — regions. and options. matched only poster-regions.mjs and a JS options
parameter. Static reachability is unavailable in both directions, so
check:tabs-localized renders the labels and reads them.

It asserts DIFFERENCE from English, not mere presence: te() falls back to the
hardcoded English label, so "shows some text" passes on the exact failure
being tested. Mutation-tested three ways — missing catalog FAILs, an all-English
catalog FAILs (first draft PASSed it, taking a per-pair exemption seven times),
one legitimate collision still passes with a note.

Both i18n gates now run in gates:post; neither was wired into the build before.

Also fixed, found by the build rather than sought:
- check-i18n-parity hardcoded an absolute path, so the studio copy silently
  graded wt-packet's locales and reported PASS on a tree it never read. Now
  repo-relative. That exposed the studio's own locale tree as a strict subset,
  103 keys behind including buttons.json:share — the very orphan this gate's
  header says it was written to catch, reappeared unseen in a second repo.
  Synced forward: zero studio-only keys, zero text conflicts, lossless.
- index.json still declared field-access-visas with rows:1146, a leftover from
  the commit that removed that table as private. The generator already filters
  private tables; the committed artifact predated the filter. Removing the
  declaration also removes the row-count leak. Copying the file in would have
  reopened the leak.
- check:sfc-parse needs an installed node_modules and is the first pre-gate to
  fail without one, so a dependency-less worktree reads as a parse error.
  Documented, with the reason switching it to bun only appears to fix it.
2026-09-24 06:23:30 +00:00
doxa-maps-88655c70
4475581c1b Close the visa-data leak at its factory + harden the gate
Some checks are pending
build-gate / build (push) Waiting to run
The field-access-visas table (580 KB -- how workers enter closed
countries) was found PUBLIC in three copies and LIVE-SERVED at 200:
the key-cities bundle data dir, the research out/ dir, and a stale
staging tree inside the served catalog. Root cause: emit() enforced
PRIVATE_TABLES but the ROUTE bundle-copy loop did not -- a privacy
rule enforced at one of two writers is not a rule. The loop now skips
PRIVATE_TABLES; the redundant source copies are deleted (data-private/
keeps the one canonical file); and the hygiene gate flags the
filename and visa_ fields as HARD findings in every mode -- this data
class never rides the strict-tier softening.

All three public copies and the live URL were purged first and
verified 404 by anonymous fetch before this commit.
2026-09-24 03:03:53 +00:00
doxa-maps-88655c70
3a7913df64 Record why the table generator keys by ISO code and never by name
Extending last tick's external-source check from legends to the baked tables
turned up a real discrepancy: gaps-and-opportunities.json has 2,106 rows against
a 2,107-row corpus, with five name mismatches. The table says "Jerba" and
"Tmagourt" where the live API now says "Amazigh of Djerba" and "Amazigh,
Matmata" — the same Tunisian peoples, relabelled upstream since the snapshot.

It is harmless, and the reason is the point. Nothing joins on the name: the
generator keys everything by iso639_3, and the consumer does the same at
resources-map.vue:194, whose comment already states the rule and its stake —
"Never a name match: language names collide and drift ... a fuzzy hit here would
put the wrong scripture in someone's hands."

The generator followed that rule in four places and never said why. So the
decision lived only in the consumer and in my head, and a future author tidying
this file toward a name join would find nothing to stop them. They would
silently drop three groups and orphan two rows: no error, no empty state, just
fewer people on a map about people nobody has reached. That is precisely what
check-label-joins.mjs was written to prevent.

Verified rather than asserted before writing it down: the join key is clean on
every row — heart_iso639_3 present and well-formed on all 2,106, zero blank,
zero malformed, 862 distinct codes; bridge_iso639_3 blank on 461, which is
expected because not every group has a bridge language. gospel-resources/
data-tables renders 50 rows by 21 cells with zero console errors and zero
responses >= 400. Both citations in the new comment were checked against the
files they name.

Comment only; no behaviour change. Five gates pass, including check:label-joins
itself and check:legend-source from last tick.
2026-09-23 11:06:11 +00:00
doxa-maps-88655c70
159acac940 Point the app-tables guard at pages that exist, and let it count rows it can see
verify:full runs two browser guards that `bun run verify` skips, and both were
failing. Neither was a product defect; both had rotted unnoticed precisely
because verify-all.mjs marks them `browser: true` and skips them unless --full is
passed, so the build chain has never run them.

verify-app-tables was broken four ways, each of which alone made it useless:

Its default port was 8917, which nothing in this repo has ever bound. Now 4173,
the port `bun run serve` binds, so it runs with the same server as every other
browser gate. APP_BASE still overrides.

It navigated to `index.html#key-city-posters` and two sibling fragments,
resolved against the PROFILES object in layouts/app-wrapper-material. That shell
is not what `/` serves — as of 2026-09-22 the root page mounts <plugin-studio>,
and the built root contains ZERO of those three keys. The list is now three real
profile paths taken from manifest.json. key-city-posters is dropped rather than
renamed to something that merely looks similar: no such profile ships.

Its "are we on our page" check matched the shell title "Doxa Maps". A leaf
titles itself "<profile> · <bundle>". My first replacement matched an absolute
script path and was equally wrong, because a leaf references its bundle
relatively (src="../gospel-resources.js") — measured, on a page that was fine.
It now matches the SRI integrity attribute, which this build guarantees by
construction and no other server emits.

Its row counter only counted rows ONE level inside a shadow host, with no seen
set. A row in the light DOM, or one shadow level deeper, counted zero. Now walks
every depth.

The last defect is the one worth reading twice. The app header printed near the
END of the loop body, after the row check, so every verdict appeared ABOVE the
app it described and the whole report was offset by one. That made a PASSING app
(gospel-resources/data-tables, 40 rows) look like the failure and the real
failure look like it had rendered rows. I chased the wrong app through three
rounds of instrumentation before reading the output in order. A report that is
correct but misordered costs a wrong diagnosis, not just time.

Read correctly, the guard now says: cities-internet/data-tables 40 rows and 1
table fetched; gospel-resources/data-tables 40 rows and 1 table fetched; and
gospel-resources/resources-list fetches NOTHING and renders nothing. That last
one is the already-carded gospel-resources data gap, waiting on the three Driver
decisions — the component is built and correctly showing an empty result for
data it was never given. So the guard is reporting one real, known, Driver-gated
absence, which is what it is for.

Everything else in it was already sound and is untouched: the recursive
shadow-root walk for row counting, fail-closed on zero fetches, and third-party
request detection, which the file rightly calls a persecution risk rather than a
performance note.
2026-09-23 09:19:20 +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
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
994b8d7dcf Prepare repo to the vision: BLUF vision-cast README, library warning, SRI injection, term scrub
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
- README rewritten as a BLUF-first vision cast for vibe coders (key-terms glossary,
  origin story, features, requirements, data caveat, developer's note).
- library/components/WARNING.md: prototype in your app-profile; only proven,
  parameterized components graduate into the shared library.
- generate-tree.mjs: inject Subresource Integrity (sha384) + crossorigin into the
  standalone page <script> tags and the copy-paste embed snippets (manifest = registry).
- Scrubbed identity-tied phrasing from comments/docs, keeping the technical rationale
  and the mission content.
2026-09-15 22:58:19 +00:00
doxa-maps
d5ba21fa76 doxa-maps — portable bundler for embeddable JAMstack maps
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.

History reset to a single clean commit; no PII, IPs, or internal terms.
2026-09-10 01:53:07 +00:00