Commit graph

255 commits

Author SHA1 Message Date
doxa-maps-88655c70
ff2c12fcff rebuild the leaf pages with the baked public token
Some checks are pending
build-gate / build (push) Waiting to run
The generated leaf pages carry BAKED_TOKEN, so they change whenever the
build-config token changes. public/doxa-maps is committed as part of the
product, so shipping the config without the regenerated leaves would leave the
tree inconsistent with its own source.

26 of 26 map leaves now carry the pk; the rest are the staging and SPA pages
the generator writes the same block into.

Staged with 'git add -u' so only TRACKED modifications are included: a broad
'git add public/doxa-maps' swept in 378 untracked poster webp files and 376k
insertions, which is the separate untracked-build-outputs question and not this
change. That commit was reset and redone.
2026-09-21 22:48:48 +00:00
doxa-maps-88655c70
3542a5bfc7 bake the public Mapbox pk so every map works out of the box
Driver 2026-09-21: 'all the maps need to have their mapbox token wired
properly according to the references - all the maps are not working yet until
that fix', then 'all maps can use the pk from the api, no problem - if they
want to set a mapbox sk they can'.

Every leaf already implemented a 6-rung token ladder; rungs 1-3 are used
directly with NO network call. On nuxtbase.app rungs 4-6 all failed: the
same-origin /api/maps/token 404s and the absolute doxa.life fallback sends ZERO
CORS headers. Rungs 1-3 were unset, hence 'No Mapbox access token provided' at
dashboard-plus-grid.js:605.

Setting build-config.token (rung 3) inlines the key into all 26 leaves, so a
map works on ANY host with no endpoint and no CORS.

This required splitting the hygiene rule, which lumped pk with sk/tk. A pk. is
a PUBLIC key: designed to ship in client code and protected by URL
restrictions, not secrecy (measured - the same pk. serves tiles 200 with a
doxa.life Referer and 403s with nuxtbase.app). Flagging it made rung 3
unbuildable, since that gate runs inside build:unlocked. sk. and tk. remain
flagged and were negative-tested: sk CAUGHT, tk CAUGHT, GitHub PAT CAUGHT, pk
allowed.

Verified: full build ALL GUARDS PASS, hygiene clean, SRI consistent across 58
leaf pages; 26 of 26 leaves carry the baked pk; and three maps mount with NO
token supplied by the harness - 0 'no token' errors.
2026-09-21 22:47:43 +00:00
doxa-maps-88655c70
1e30c678a9 widen the publish secret gate from 1 credential format to 7
Last tick I named this narrow and deliberately left it, reasoning that public/
is a tree we GENERATE so the only realistic leak is the Mapbox token the build
writes. That reasoning was wrong: 855 files under public/ are TRACKED, 95 of
them text - boundaries, people-group data, brand assets, leaf pages - curated
and committed by hand. A hand-committed file is exactly where a pasted
credential lands, and this tree is rsynced to a PUBLIC catalog.

Measured before fixing: a planted ghp_ PAT and an AKIA AWS key both passed as
'clean - 0 findings'.

Now covers GitHub PATs, Slack tokens, AWS access keys, private-key blocks,
literal Bearer tokens and Coolify token assignments alongside the original
Mapbox JWT. Each entry matches a KNOWN credential FORMAT rather than guessing
from entropy - an entropy rule over this tree would fire on every SRI hash and
base64 image and be ignored.

Negative-tested all 7: each plants its credential in public/ and is CAUGHT by
name; the clean tree still passes with 0 findings across 855 files; the file is
byte-identical after testing. The gate runs inside build:unlocked, so every
build and CI run enforces it.

Also caught before shipping: (?i:...) is Python/PCRE, not JavaScript - written
inline it throws 'Invalid regular expression' and takes the whole gate down.
Uses a trailing /i flag instead.
2026-09-21 21:57:39 +00:00
doxa-maps-88655c70
ea53a2e19a emit a real desktop binary and .deb for deploy method 5
tauri build produced target/release/doxa-maps-desktop (65 MB ELF) and a 58 MB
.deb (Package: doxa-maps, Depends: libwebkit2gtk-4.1-0 libgtk-3-0). tauri info
reports the environment fully green and correctly detects frontendDist
../public, Vue.js, Vite.

The web tree is compiled INTO the binary rather than shipped as files, which is
why the .deb holds only 6 entries - confirmed by 19 distinct embedded
doxa-maps/... asset paths in the ELF and by size (a logic-free shell is 65 MB
only because the 132 MB public/ tree is compressed inside it).

bundle.targets narrowed from all to deb: rpmbuild and linuxdeploy are absent
here, so all would fail two of three formats at the END of a ~6-minute release
build.

The CLI is a devDependency (@tauri-apps/cli, prebuilt binary) rather than
cargo install tauri-cli, which compiles from source.

Status stays BUILT, NOT SHOT. The GUI launch is unverified because this box is
headless - the binary panics in tao with 'Failed to initialize GTK', the
absence of a display rather than a defect, and no Xvfb is installed.
2026-09-21 21:36:18 +00:00
doxa-maps-88655c70
381d3087c7 point the docs at src-tauri/tauri.conf.json and record the clean compile
Three docs said the config lives at the repo root - true when written, false
since the move, and exactly the real-but-wrong-target shape: a reader follows
the path, finds nothing, and cannot tell whether the feature exists.

Status upgraded from 'rustc parses main.rs' to what was actually measured:
cargo check finishes with 0 errors and 0 warnings across 400+ crates. Still
'built, not shot' - no packaged binary has been launched.
2026-09-21 21:23:21 +00:00
doxa-maps-88655c70
0545a00cca make the Tauri shell actually compile: canonical config path, strict schema, real icon
Yesterday's scaffold was never compiled - only parsed. Running the real
toolchain found three defects the config-only gate could not see.

1. tauri.conf.json was at the REPO ROOT. tauri-build reads it from src-tauri/
   only; cargo compiled 228 crates before dying with 'unable to read Tauri
   config file ... entity not found'. Moved, and check:tauri now asserts the
   canonical location AND fails on a stray root copy (two configs where edits
   to one silently do nothing).

2. The '//'-prefixed comment keys were FATAL. capacitor.config.json documents
   itself that way because Capacitor ignores unknown keys; Tauri's parser is
   strict and rejected '//THE_TOKEN_CAVEAT' outright. The config is now
   schema-pure and the rationale moved to src-tauri/README.md, which also
   records why it cannot go back.

3. generate_context! requires icons/icon.png even when bundle.icon is omitted -
   my comment claimed omitting it AVOIDED an error; it caused one. Reused the
   Android launcher icon (192x192 RGBA) so both native shells carry one
   identity, matching the shared appId.

Result: cargo check finishes clean, 0 errors 0 warnings, 400+ crates including
wry and tauri 2.11.6. Status stays BUILT, NOT SHOT - no packaged binary has
been launched. gen/ and target/ are gitignored as generated output.
2026-09-21 21:22:14 +00:00
doxa-maps-88655c70
679dfc47d6 update the deploy-method ledger: Tauri is built-not-shot, not aspirational
Five places across three docs still said method 5 was aspirational / not yet
scaffolded / a deferred POC. All were true this morning and all are now false:
tauri.conf.json, src-tauri/ and check:tauri exist.

The honest-ledger table moves from 2 proven / 2 built-not-shot / 1 aspirational
to 2 / 3 / 0, and its closing sentence is corrected - with nothing aspirational
left, 'the two lower states' no longer described the table.

Status is deliberately BUILT, NOT SHOT and never 'proven': proven is reserved
for methods 1 and 3, which have live artifacts. No desktop binary has been
emitted here. Section 3 now carries the runnable contract command, the
one-build-four-consumers framing, and the token/CORS caveat up front.
2026-09-21 21:07:35 +00:00
doxa-maps-88655c70
cad913bc6f scaffold deploy-method 5: Tauri desktop shell for the cap-5 SPA
Method 5 was the last aspirational entry in the 5-deploy-method model: 0
tauri.conf.json, 0 Cargo.toml, 0 .rs. It is now scaffolded against the v2
config schema, mirroring the Capacitor pattern that already works.

frontendDist points at the SAME public/ tree Capacitor and the CDN serve, so
one build still feeds every consumer - now four instead of three. The crate
carries no application logic on purpose: the product is the web tree, this is
a window around it.

check:tauri is the analogue of cap:verify and exists for the same reason
capacitor.config.json warns about in capitals: a wrong frontendDist or a window
url that does not exist launches a BLANK WHITE WINDOW, not an error. It runs
with no Rust toolchain and no Tauri CLI, so CI can run it. Negative-tested five
ways - wrong frontendDist, missing window url, identifier drift from the
Capacitor appId, empty windows array, missing crate file - each exits 1, and
the config was restored byte-identical after every test.

Status stays BUILT, NOT SHOT. rustc parses main.rs (it reaches line 14 and
fails only on the uninstalled tauri crate, a dependency error not a syntax
one), but producing a binary needs the Tauri CLI plus system webview libs.
Also recorded the token/CORS caveat up front: a Tauri webview serves from
tauri://localhost, so the token endpoint must send CORS for that origin -
the same surprise Capacitor documents for https://localhost.
2026-09-21 21:06:04 +00:00
doxa-maps-88655c70
c8242064e6 correct Step 2: three files are required, not exactly four
Step 2 told an author to create 'exactly four files'. Measured across the 18
shipped bundles, the folder holds 3 to 8 files and NOT ONE has exactly four.
all-people-groups is the true minimum at 3 (root .vue + index.js + index.html)
and is a fully working, manifest-listed plugin. 9 of 18 carry no .meta.json at
all, because the single and prop-table tiers are INFERRED rather than declared.

The table now marks each file required or opt-in, and adds the two files real
bundles actually use that the doc never mentioned: <name>.instances.json (which
is what makes a bundle prop-table / cap-2, per discover.mjs) and src/.

Verified the one claim that matters by testing it: moving index.html out of
demo-map made the build error and dropped the bundle from the buildable list
(18 -> 17); restoring it returned a clean build, byte-identical to HEAD.

Capability inference confirmed against the built manifest: all-people-groups
type=single, demo-map and doxa-vision-card type=parameterized.
2026-09-21 20:54:30 +00:00
doxa-maps-88655c70
ba55689f2f correct Step 6: the SPA auto-discovers a new plugin, no tab edit required
Step 6 told an author that surfacing a plugin means adding a TAB_DEFS entry and
a NAV_MODEL row. That overstates the work. The cap-5 SPA reads the built
manifest at runtime and auto-discovers every bundle that is neither curated nor
on the NOT_EXPERIMENTAL exclusion list, placing it in the Experimental folder
with its label and description from the manifest.

Verified in a browser rather than from the comment that claims it: the rail
rendered 11 rows - 6 curated, then 5 auto-discovered (all-people-groups,
country-posters, demo-map, doxa-vision-card, stack-demo), each carrying the
flask marker.

The manual edit is still documented, reframed as what it actually is: optional
promotion OUT of Experimental into the curated teaching order. Also recorded
the one-row-per-bundle rule and the fail-soft behaviour, and corrected the
closing summary, which omitted the new mount step and implied surfacing was
manual work.
2026-09-21 20:48:50 +00:00
doxa-maps-88655c70
688f16c828 add Step 3b to the golden path: prove the plugin mounts before publishing
The golden path took an author from author -> build -> PUBLISH TO THE LIVE
CATALOG without ever opening the element in a browser. Every guard in Step 3 is
static: it checks that files exist, parse, carry a correct SRI hash and resolve.
None of them renders anything.

Proved the gap rather than asserting it: injecting a throw into a component's
setup() produced a SUCCESSFUL build (206 kB emitted, exit 0) with check:parse
PASS and check:drop PASS, while smoke:embeds reported 0/2 pages mounting. After
restoring, the same command reports 2/2.

The step documents the token requirement because its absence is loud and
misleading: without it every map profile fails to mount (27/40) and reads like
13 broken plugins. It also records why this is not a CI step - CI is the single
authoritative builder but installs no browser binary.

All three quoted commands were run as written before being documented.
2026-09-21 20:45:36 +00:00
doxa-maps-88655c70
744ee8b00d wire a Mapbox token through embed-smoke so map profiles can mount
Without a token every map profile failed the mount assertion and the gate
reported 13 'mount timeout' failures that read as broken plugins. They were
not broken: the same profiles mount immediately once a token is supplied
(doxa-simple-map went 0/4 -> 4/4 with canvas; the full suite 27/40 -> 40/40).

Both channels are set because they are not interchangeable: ?tk= reaches only
the top-level element's profile-config, while nested children read
window.MAP_TOKEN. A composite mounts with ?tk= alone but its children stay
blank.

No token is not a failure - the run prints an explicit warning and degrades to
DOM-only coverage rather than reporting map profiles as broken. Negative-tested
in both directions.
2026-09-21 18:15:38 +00:00
doxa-maps-88655c70
f1772fda6f correct the dot-visibility comment to match the two-part fix actually shipped
The comment claimed a three-part fix including 'a thin white inner ring for
contrast'. No such property was ever added, and a mapbox circle layer has a
single stroke, so no inner ring is possible on this layer. A reader trusting
the comment would look for a second layer that does not exist.

Verified live against the running map rather than the source: the upg-pins
layer reports stroke-color rgba(15,20,25,0.9), opacity 1, and stroke-width
interpolating 1.2 -> 2 across zoom. The fix is real; only its description
was wrong.
2026-09-21 17:34:37 +00:00
doxa-maps-88655c70
a3ec9e5e9e point the README at the output path the build actually writes
Three places told a plugin author their work compiles to public/<name>/. That directory
does not exist. The build emits public/doxa-maps/<bundle>/<bundle>.js, where the
doxa-maps segment is the output tree name from vite.doxa-maps-build-config.json — not the
plugin's name and not this repo's folder name. The doc conflated two different <name>s
and sent a receiver looking somewhere empty.

One of the three lines carried its own contradiction: it wrote public/<name>/ and
public/doxa-maps/manifest.json in the same sentence.

Left the reference.md occurrences alone. Both say "output tree" explicitly, so <name>
correctly means the configurable tree name there rather than a plugin.

The layout section now also states the contract plainly, since that is what a hand-off
reader needs: write inside plugin-designs/<your-map>/, run the build, and it appears in
the output tree with no registration step. Every concrete path in the README was then
resolved, and both template forms checked against a real bundle.
2026-09-21 16:55:15 +00:00
doxa-maps-88655c70
424d9e0345 say what cap sync actually stages instead of a bundle count that matches nothing
The Capacitor record claimed "21 bundles staged". That number matches nothing measurable
today: the tree has 18 built bundles, 17 of them in the manifest since key-cities is
withheld by gatedProfiles, and 19 subdirectories under public/doxa-maps, one of which is
assets and not a bundle at all.

More to the point, bundles was never the unit cap sync operates on — it copies webDir
wholesale. A count that names the wrong noun goes stale the moment the tree changes and
cannot be re-checked against anything, which is how the original figure survived several
sweeps unnoticed. The line now states what was actually verified.
2026-09-21 16:45:39 +00:00
doxa-maps-88655c70
5ddabe26e2 light the remaining unconditional loading overlays and the copy-me starter
A previous sweep found six dark matches, correctly ruled five of them unreachable, and
fixed the one real violation in doxa-simple-map. Re-running it with a luminance test
rather than a hex prefix found more, because the earlier pattern also matched the green
accent #10b981 and so buried the genuine surfaces in noise.

Three further unconditional violations, all the same shape as the one already fixed: the
loading overlays in all-people-groups, country-posters and doxa-research-map each render
on every load via v-if="!appReady", so a near-black flash was the first thing a visitor
saw. All four now use the palette surface with muted text.

The template-bundle starter was also dark in both profiles. That one matters more than a
leaf: the documented recipe is to copy template-bundle and rename it, so every future
plugin would have inherited a surface the house style forbids. The two profiles stay
visibly distinct — a tinted surface with an accent border, b white with a neutral one.

Verified by reading the shipped rule out of each built bundle: 4 of 4 found, all
#f3f3f1 at luminance 243. The found-count is reported so a run that matches nothing
cannot read as a pass — an earlier version of that check found zero rules and printed
ALL LIGHT.
2026-09-21 16:40:53 +00:00
doxa-maps-88655c70
af73dfb3f9 adopt Material nav metrics in VerticalTabsLayout
Driver: "the sidebar is supposed to look like Material UI, like what people are used to...
people are used to the YouTube layout, Google layout is just way better. We do not need
to create custom styles people are not used to."

Measured before: app bar 51px, nav rows 44px with an 8px radius, collapsed rail 60px.
Now app bar 64px desktop and 56px mobile, rows 48px, a full pill active row, and a 72px
mini-variant rail. Nav width was already 256px and is unchanged.

Nothing vendored. The card's law is to imitate the metrics in our own CSS rather than add
Quasar or MUI, which is the same clone-don't-edit rule the library follows elsewhere.

Two traps worth recording. box-sizing:border-box is load-bearing on the app bar: without
it the element is content-box, so min-height:64px measured 81px live once padding and
border were added. And the rail collapse is a translate rather than a width change, so
four numbers are coupled — the nav translate, the content pane's left, and two padding
compensations that keep icons inside the visible strip.

Verified live: app bar 64 desktop and 56 mobile, rows 48 with a 999px radius, rail 256
expanded to 72 collapsed with the content pane starting at exactly 72.
2026-09-21 16:25:43 +00:00
doxa-maps-88655c70
4d92e69377 count only zones that are meant to paint, so the badge reads full health
Deriving the denominator from perZone.size fixed the impossible 27/26 but left a
permanent 27/28, which reads as "one zone is broken". I carded it as a bug on exactly
that reading. It is not a bug.

resolveAllZones deliberately excludes the deaf zone from the region-fill tapestry and
records zero for it, because its A017 affinity selector double-stamps land already owned
by the 13 geographic zones — 16 of oceania-indonesia's 18 Pacific ISO3 codes alone. That
double-stamp showed deaf twice and underlapped every other region. Deaf stays complete in
the list, the legend and the pins; only the polygon fill is withheld.

So a by-design exclusion was being displayed as a shortfall. Counting only zones that are
meant to have geometry makes the badge report 27/27 when everything is healthy, and fall
below its total only when a zone genuinely fails to resolve.

Verified: 28 leaves minus 1 deaf is 27 expected, 27 resolve, and the leaf now shows
195 polys · 27/27 zones · 2107 pins. The composite does not display this badge at all,
which a first probe misread as a failure.
2026-09-21 16:03:54 +00:00
doxa-maps-88655c70
2b72fb6a55 round the top-header search bar to a full pill
The Driver asked for the top-header search bar to be more rounded. It is now 16px, which
is exactly half its measured 32px height, so the ends are true semicircles. Any larger
value renders identically because the browser clamps to height/2, making this the actual
maximum rather than a number picked by eye.

Scoped to the top header only. The card's scope field named the shared SearchBar
component, but that component's own 10px is not what renders here: measuring the computed
style showed 6px, because DashboardTopBar overrides it. Editing the library would also
have changed people-groups-grid and big-picture, which was not asked for.

Verified 6px to 16px at two viewports, confirmed as a full pill, with the magnifier icon
still clear of the curve at a 10px inset.
2026-09-21 15:51:10 +00:00
doxa-maps-88655c70
d58643f1df make the share suite refuse to report a false failure, and give it the token
The card recorded a hypothesis and said not to assume it: that Firefox's deepFind resolved
only the outermost shadow root. Measured and disproved — both engines find exactly one
shadow host at depth 0, so shadow traversal is identical. The real difference is WebGL.
Chromium reports webglSupported true with 157 elements in the shadow root and the toolbar
present; Firefox reports false with 31 elements, no toolbar, and "[doxa-simple-map] Map
failed to initialize".

Confirmed as an environment property, not a page one: Firefox returns null from getContext
on a blank page here, and firefoxUserPrefs for software WebGL do not change it. Chromium
only works because launch() passes SwiftShader args. So the Share button is never rendered
because the map never initialises, and the suite was blaming the product for that.

Rather than quietly defaulting away from the Driver's house rule, the suite now aborts with
the measurement and the exact working command, and exits 2 so a caller can tell "could not
test" from "test failed". Restating the rule stays the Driver's call.

It also had no token wiring, the third gate found missing it. My first attempt set only
window.MAP_TOKEN, assuming a page global would survive into the reopened page. Measured:
?tk= only mounts, global only does NOT mount, both mount. The global is read by nested
children of a composite, not by a top-level element. So the host URL carries ?tk= and the
share URL is navigated verbatim, because appending a token there would fake the very thing
under test.

The remaining failure is now honest and in the right place: the host page renders, and the
share URL cannot, because a pasteable link deliberately strips query params. Only the token
endpoint fixes that.
2026-09-21 14:19:09 +00:00
doxa-maps-88655c70
873a3ef976 classify console errors in the two map gates instead of counting them
Both gates barred on zero console errors, which fails a perfectly rendering map whenever
the environment is imperfect. Measured on all-people-groups with a valid token: the
element mounts at 900px with a live GL canvas at 1440x900 and 309 characters of real
text, and the gate said FAIL over 20 console errors that were all one external cause.

Three classes, each observed rather than imagined. Ladder fallthrough is the token ladder
doing exactly what it is designed to do, trying rungs in order until one answers. Mapbox
403s come from the shared token being URL-restricted to doxa.life, and Mapbox then throws
its own internal errors because of them, which is why one restriction produces twenty
lines. Terminal "No Mapbox access token provided" means the ladder is exhausted and the
map genuinely cannot paint, so that still fails.

This is not "ignore errors": anything unrecognised still fails by name, and the exempted
count is printed per tier so the exemption's blast radius stays visible. A silent
exemption is how a gate quietly stops testing anything.

verify:map-paint also gained the token wiring verify:5-caps already had. Classifying was
necessary but not sufficient there — without a token the element never mounts at all, and
a gate that can never pass is still a gate that gets ignored.

verify:5-caps now reports 5/5 with a token and still fails correctly without one, naming
the terminal cause. verify:map-paint reports 4/4 painting with live GL and attribution.
2026-09-21 14:04:18 +00:00
doxa-maps-88655c70
b08ed8b484 centre the grid explainer in the existing table header
The Driver rejected v1, which added the hint as its own paragraph above the grid: "you
dont have to add a SECOND HEADER to make this show up... it could be CENTRED on the same
header where it says total 2107." So it now rides the bar that is already there, beside
the live count.

Centred by absolute positioning against the bar, not by flex spacers. The first attempt
here used a spacer on each side and measured 55px off centre at both 1440px and 1280px,
because spacers split the leftover space rather than the bar, and the left cluster
(dock, title, count, selection controls) and right cluster (toast, Export CSV) differ in
width. Absolute at left:50% with translateX(-50%) is relative to the bar itself, so the
clusters cannot drag it, and being out of flow it adds no height by construction.

The bar gains position:relative to be that containing block. pointer-events:none keeps
the hint from intercepting a click meant for a control beneath it, and it hides below
1100px where the clusters leave no free middle.

Measured against a pre-change baseline of 32px: 32px at all three viewports, one header
bar, and centre offset 0 at 1440px and 1280px.
2026-09-21 13:42:55 +00:00
doxa-maps-88655c70
c401a758bb name the legend collapse control and gate every button's accessible name
Last tick's fix covered the shared map-control base. Sweeping the rendered DOM of all 17
bundles found three more unnamed buttons — the same "Collapse legend" control in three
map bundles, again from one shared component. Its only content is an svg caret, so with
title alone its accessible name was empty.

It is a disclosure control, so it now reports aria-expanded as well. Verified by toggling
it: the label flips Collapse/Expand and aria-expanded flips true/false.

The gate measures the rendered DOM through shadow roots, because a source grep cannot
answer this question. There are 132 <button> hits here and most have visible text, which
IS a valid accessible name; aria-label is often on a following line. Only the DOM knows
which buttons ended up nameless. It counts aria-label, a resolved aria-labelledby, or
visible text, and deliberately does not count title, which is the defect it was written
for. Hidden buttons are skipped since assistive tech cannot reach them either.

The sweep went 319/322 before the legend fix to 322/322 after, which is the evidence it
detects real defects rather than passing vacuously.
2026-09-21 13:23:35 +00:00
doxa-maps-88655c70
a5c5cf1910 give every map control an accessible name
The zoom buttons render an icon through a slot and carried only `title`, so their
accessible name was empty: a screen reader announced "button" with no indication of what
it does. title is a tooltip, not an accessible name — unreliably announced, and never
surfaced to touch users.

Found while proving the research-dashboard zoom controls actually work. They do: a click
triggered six tile requests at zoom level 2, so the open-work item is function-verified,
not just present. The probe that matched aria-label found nothing while the probe that
fell back to title found both buttons, and that discrepancy was the defect.

One binding in the shared MapControlButton reaches all eight control buttons — zoom in
and out, fullscreen, location, help, share, hamburger, theme — across seven map bundles,
because every one composes this base. No caller changes: the component does not set
inheritAttrs:false, so a caller passing its own aria-label still wins through normal
fallthrough, and undefined omits the attribute when there is no title to mirror.

Verified across all seven bundles: 34 control buttons, 0 without an accessible name.
2026-09-21 13:02:56 +00:00
doxa-maps-88655c70
d0568ebd35 prove the live server serves the catalog's exact bytes
Card 1 asks that one public Forgejo repo be the live catalog source and that the merge
lose nothing. Both halves were marked done-verified on 2026-09-19, two publishes ago, so
this re-measures rather than trusting the field: the catalog is still a zero-diff match
for the freshly-built tree, all 17 live bundles hash to their own manifest integrity, and
the live public manifest is byte-identical to the catalog's.

An HTTP status cannot make that claim. /app/doxa-maps/manifest.json returns 200 and is a
2,872-byte HTML sign-in page, so a status check reads the login wall as healthy. Every
profile already carries an integrity hash the build computed for an unrelated reason, so
comparing it against the live bytes is a cryptographic identity proof with no new source
of truth to keep in sync.

Network-dependent, so not in build:unlocked and not in CI. It distinguishes "could not
reach" from "reached and wrong" and only the latter fails, because a gate that fails on a
network blip gets switched off.

Negative-tested four ways: the login wall fails with the reason, an unreachable host
skips at exit 0, the catalog-comparison mode confirms the sha256 match, and the baseline
proves 17 of 17.
2026-09-21 12:46:48 +00:00
doxa-maps-88655c70
466493ed80 generate the capability exemplar table so the answer stops being remembered
"Which bundle is the capability-N example?" has been answered wrongly in three separate
artifacts, each fixed in isolation while the others kept the bug: verify-5-capabilities
cited doxa-vision-card as cap-1, the README made the identical claim, and the
/plugin-designer skill still cites both that and unified-research-dashboard, a bundle
that does not exist.

The skill cannot be gated from this repo. It lives in the session config directory, in
no git repo, so a checker pointed at it would pass here and skip silently everywhere
else — the gate-that-cannot-fail shape. So instead of policing the skill, give the repo
an authoritative generated answer that prose can cite and a gate can verify.

The table derives from manifest.json, which the build computed from each folder's real
shape, and reference.md now points at it with the reason: a folder's apparent simplicity
does not decide its tier. doxa-vision-card looks like the lightest thing in the repo and
is capability 2, because it ships an instances file.

check:capability-exemplars regenerates and content-compares rather than checking mtimes,
because a full build rewrites manifest.json every time and a gate that cries wolf after
every build gets switched off.
2026-09-21 12:38:50 +00:00
doxa-maps-88655c70
88f01b9a95 lift the pin hint into view when its host clips it
The cap-4 composite shipped the coach-mark sliced through the middle of its sentence.
Measured, same component and viewport: the leaf renders the pill at bottom 864 of 900,
the composite at 919 — clipped by 17px, because the composite adds a ~53px header above
its nested child's stage and findMapBox() finds no positioned container to re-parent into.

The component's header promises that case degrades to "stay put — a degraded anchor, never
a crash". A graceful degradation that degrades to invisible is a failure, not a fallback,
so the clamp measures the rendered rect and only ever lifts a pill that is actually clipped.

Two wrong attempts are recorded in the code because the reasons matter. Setting
style.transform would have destroyed the wrapper's translateX(-50%) centring, and the
pill's computed transform also carries the bob animation, so a computed reading is never
just my offset. Adjusting the bottom offset composes with the attribution clearance
instead of fighting it. Clamping only at nextTick also missed: the composite's layout has
not settled then, so the clamp now runs from syncAttribClearance, which already re-fires
from a ResizeObserver, plus a second observer on the pill's own layout parent.

Verified on six surfaces: the composite lifts 30px to bottom 888, every other surface
reports lift 0 and is byte-identical in behaviour, and all six keep centreDelta 0.
2026-09-21 12:30:11 +00:00
doxa-maps-88655c70
c7a228e941 shoot a real cap-1 element and gate README capability claims against the manifest
The CAP 1 slot shot doxa-vision-card, which is not a single element: it ships
doxa-vision-card.instances.json with two instances, so discover.mjs types it
parameterized and manifest.json records "parameterized" — capability 2.

The same error had already been found and fixed in verify-5-capabilities.mjs, whose own
comment records that it had been sampling cap-2 twice and cap-1 never. That fix landed in
the gate and never reached the README: two artifacts encoding one fact, with nothing
forcing agreement. Fixing one copy was not fixing the fact.

all-people-groups is a genuine cap-1 — one .vue, no instances file, manifest type single.
The README now also says what decides the tier, since the folder's apparent simplicity is
the trap: doxa-vision-card looks like the lightest thing in the repo.

check:capability-citations compares every README capability claim against the manifest,
blocking in CI. Its first matcher used \Z, which is not JavaScript, so it matched zero
sections while still reporting a citation count — a gate reporting a number it had not
verified. It now splits on headings and the count reflects real matches.
2026-09-21 12:02:18 +00:00
doxa-maps-88655c70
dc878584bd re-shoot README images after the rebuild
No bundle .js changed — the rebuild was deterministic and the catalog needs no publish.
The images move only because check:shots compares each PNG against its bundle mtime, and
a full build was required to clear the composite staleness my negative test introduced.
2026-09-21 11:53:18 +00:00
doxa-maps-88655c70
029cffc01f gate the blank-basemap class: 7 of 10 map bundles draw geography from Mapbox tiles only
Reading the cap-2 screenshots found all three doxa-simple-map shots rendering data dots
on an empty dark field: no coastlines, no country fills, no labels. Every shot reported
OK, because the page loads, the element mounts, and the legend, controls and data layer
are all correct.

The reason no signal can see it: the Mapbox STYLE DOCUMENT returns 200 from every origin,
so the map genuinely boots. Only the TILESETS are restricted — mapbox-streets-v8, which
is the basemap itself, plus bathymetry-v2 and country-boundaries-v1, all 403 off
doxa.life and 200 with its referer. A booted map showing its own UI and its own data
looks like a working map; only the geography is absent.

This also explains a near-misdiagnosis. cap4-upg100-list paints perfectly beside four
blank maps, which looked like a per-bundle bug. It is not: my-upg-100-list ships its own
world-adm0.geojson and draws geography from self-hosted data. Three bundles do that and
survive; seven do not.

The dark basemap in those shots is deliberate, not a theme violation — useMapTheme's
lookForStrategy keeps prayer, adoption and engagement on the night preset by design.

Negative-tested: marking every affected bundle as self-hosted removes the finding, and
removing the markers brings it back.
2026-09-21 11:46:36 +00:00
doxa-maps-88655c70
dfe6c3d1fa correct the "research-dashboard owns no map" premise and gate the blank-tileset class
A previous reader measured ZERO <canvas> on research-dashboard and concluded the surface
owns no map, recording it as an architectural fact and relaxing the shot's readiness
check to match. The measurement was real; the inference inverted it. The page imports
useMapInstance, injects mapboxToken and adds a vector source, and re-measured live it
builds a mapboxgl-canvas at 878x445. Zero canvas meant the map failed to initialise.

The cost was not the blank image. A weakened assertion that makes a failing check pass
promotes a defect to a design note, and the README's deploy-3 proof has shown a blank
grey map pane since.

Why it is blank, measured: the shared pk. token is URL-restricted to doxa.life. Against
country-boundaries-v1, Referer doxa.life returns 200 on three attempts, 127.0.0.1 and no
referer return 403. The style endpoint returns 200 from every origin, which is why most
maps paint here and only the tileset-backed choropleth blanks. A first reading of that
asymmetry looked like a missing tileset scope; re-testing with the doxa.life referer
disproved it.

check:shot-fidelity now reports this class alongside the missing emoji font, and no
longer exits early after the first finding.
2026-09-21 11:39:06 +00:00
doxa-maps-88655c70
15ce17f907 fix the impossible 27/26 zone badge and replace a faked page-2 shot with the real third profile
The badge read "27/26 zones" — a count exceeding its own total — in two shipped README
screenshots. 26 was the leaf count before slice 09b-6 added the Frontier-Asia uncovered
rows; the literal was never updated, so it drifted the moment the data grew. The
denominator is now derived from perZone.size, which tracks the zone list automatically
and cannot disagree again. Measured from clusters.json: 13 regional + 12 asia + 2
uncovered + 1 deaf = 28. The corrected badge also surfaces 1 genuinely unresolved zone
that the stale denominator was masking.

legend-tree.js asserted "33 leaves" and "7 Frontier-Asia uncovered" in a comment; the
shipped data carries 28 and 2. Corrected to point at the arrays instead of a count.

README showed "List — page 1" and "List — page 2" as the same picture. The shot loaded
one URL twice and tried to reach a page 2 by scrolling and clicking Next; neither exists,
the list has no pagination, and every attempt sat in try/catch so the miss was silent.
The PNGs differed by 0.16% render noise, so no checksum would have caught them. Mean-
while poster-wall — a real manifest-listed profile — had no screenshot at all. Shoot the
real third thing rather than fake a fourth.

check:shot-distinctness gates this, blocking in CI. It keys on url+viewport, so the
deploy2 mobile shot of the same SPA stays legitimate rather than a false positive.
2026-09-21 11:29:25 +00:00
doxa-maps-88655c70
5567134978 shoot the plugin-studio element and stop captioning the product SPA as the studio
README drew the two cap-5 products apart in prose and even warned that captioning a
studio shot as the product SPA is a documented mistake — then filled its plugin-studio
slot with cap5-doxa-maps-spa.png. Both cap-5 figures showed the same app and no
plugin-studio screenshot existed anywhere in the repo.

A slot filled with the wrong real image looks exactly like a correct one; nothing 404s.
So the fix is to shoot the missing artifact, not re-caption around the gap. Both cap-5
slots now use generated, freshness-gated images.

Also adds check:shot-fidelity. The shot run reported 13x OK while every emoji rendered
as a tofu box: this machine has no emoji font. Measured rather than assumed — the DOM
carries 27 correct codepoints, so the markup is right and the defect belongs to the shot
machine. Reports by default because installing a font is a Driver call; --strict blocks
once one is present.
2026-09-21 10:59:28 +00:00
doxa-maps-88655c70
a853081e3f gate the docs' quoted scripts block against package.json
README-SECTION-3 reproduces the scripts block verbatim and carries its own plea to
re-transcribe after every package.json edit. It drifted anyway, twice: cap:sync still
showed the bunx form that does not exist here, and build:unlocked was missing
check:public-hygiene and check:label-joins.

The second is the dangerous shape. An omission in a quoted list has no wrong character
to notice, so a reader auditing what the build verifies concludes the secret scan is not
in the chain. The warning was already there and did not work, because re-transcribing by
hand is what gets skipped when the edit felt unrelated.

Blocking in CI: it diffs two files already in the checkout, so a failure is always real.
Negative-tested both ways, including a matcher that matches too little failing rather
than passing vacuously.
2026-09-21 10:47:51 +00:00
doxa-maps-88655c70
2fa4bf992a point docs at library/layouts and fix the stale bunx and provider-ladder claims
The top-level layouts/ directory does not exist; the wrappers live in library/layouts/.
Six references across four docs named the phantom path, including a copy-pasteable
cd command and a README-longform link that resolved to docs/layouts/README.md (a 404).

how-to.md taught bunx for the iOS scaffold. bunx is not on PATH here, which is the
exact failure package.json already documents in cap:sync; point both at the local bin.

useMapProvider.js's own header listed three resolution rungs, omitting ?provider= and
misnumbering window.MAP_PROVIDER. The function implements four. how-to.md was correct.
2026-09-21 10:43:11 +00:00
doxa-maps-88655c70
70d0e36ad3 point the tutorial at the studio folder and separate build from publish 2026-09-21 07:38:50 +00:00
doxa-maps-88655c70
51b6fa3f6d document the standalone gates and why they sit outside the build 2026-09-21 07:35:11 +00:00
doxa-maps-88655c70
f8c7e77748 carry the push buffer flag in the golden path, not only the runbook 2026-09-21 07:34:05 +00:00
doxa-maps-88655c70
f4c265e72a report a stale library menu in ci by regenerating and diffing 2026-09-21 07:26:18 +00:00
doxa-maps-88655c70
e3136a4ec1 generate the library menu and gate it against library drift 2026-09-21 07:25:18 +00:00
doxa-maps-88655c70
b6debf2bfb verify the wordpress embed contract in ci when php is available 2026-09-21 07:18:13 +00:00
doxa-maps-88655c70
c26b3bb597 report stale readme screenshots in ci 2026-09-21 07:14:54 +00:00
doxa-maps-88655c70
a4d6062e96 assert the token path the client requests, not only the typed one 2026-09-21 07:13:55 +00:00
doxa-maps-88655c70
32e6c1231e gate readme screenshots against the bundle they show; wire the generator 2026-09-21 07:05:00 +00:00
doxa-maps-88655c70
7276ade0e6 wait for svg not canvas on the chart dashboard; refresh all readme shots 2026-09-21 06:58:48 +00:00
doxa-maps-88655c70
f160494567 let the capability verifier carry a token so it measures capabilities 2026-09-21 06:46:15 +00:00
doxa-maps-88655c70
22d4c9bedb require evidence on a done card; baseline the ten pre-existing 2026-09-21 06:34:30 +00:00
doxa-maps-88655c70
bd4dec7db2 build lock 2026-09-21 06:23:39 +00:00
doxa-maps-88655c70
9e3443333f track html comments so prose backticks stop desyncing the scanner 2026-09-21 06:19:37 +00:00
doxa-maps-88655c70
08cbccb7ba build lock 2026-09-21 06:15:34 +00:00