Commit graph

275 commits

Author SHA1 Message Date
doxa-maps
c80f8fb073 Document the preconditions three gates need, so a bare run is not read as failure
Some checks are pending
build-gate / build (push) Waiting to run
Running the gate list I completed last tick, two entries reported FAIL:
smoke:gallery and smoke:share. Neither is a defect. smoke:gallery needs a
dev server on :5174 and says so; smoke:share needs --url arguments and prints
usage. Both exit non-zero, so in a batch run they read exactly like the real
failures the list exists to surface.

A checklist that turns a precondition into a red line trains its reader to
ignore red lines, which is how a real failure gets skipped. Added a
preconditions table stating what each needs and what a bare run actually
does, measured by running them.

Also recorded that the token-dependent gates DEGRADE rather than fail:
without MAPBOX_TOKEN, smoke:embeds reports map profiles as timeouts -- it
once read 27/40 for that reason and 40/40 with a token. A timeout there means
"no token" far more often than "broken plugin".

Two claims corrected before shipping. I wrote that the token endpoint needs a
browser User-Agent; measured, a plain curl gets 200 identically -- that
requirement belongs to pray.doxa.life, a different host. And I tested rather
than asserted the limitation now in the doc: verify:map-paint PASSES on a
leaf whose tiles 403, because a live GL context, a sized canvas and
attribution are all true while the basemap stays empty. The gate is correctly
scoped; the name invites over-reading, so the scope is now written down.
2026-09-22 04:43:00 +00:00
doxa-maps
5353ed5b51 Document every standalone gate, and fix the two that were already failing
Some checks are pending
build-gate / build (push) Waiting to run
26 gates live outside build:unlocked; only nine were listed in reference.md.
An inheritor reads that table as the list of what to run before publishing,
so seventeen checks were effectively invisible -- including the three that
caught the most defects this flight: catalog-currency, smoke:embeds and
verify:map-paint.

The cost was already being paid. Two unlisted gates were RED, and had been
for two sessions:

  check:quoted-scripts       the doc quoted a build chain missing the
                             check:descriptions step added two ticks ago
  check:capability-exemplars the generated tier table predated golden-path-check

Neither is a product defect -- both gates were working exactly as designed,
comparing documentation against machine truth. Nobody ran them. That is the
failure mode a standalone gate has by construction, and the table is the only
mechanism that prevents it.

Fixed both (doc updated to match package.json; exemplars regenerated to 27
rows), then filled the table to 25 of 26 -- the one omission is an alias for
a script already listed. Each new row states what the gate catches and, for
the ones that cannot live in the build, why.

Corrected a number mid-edit: I first wrote "nineteen gates" from an earlier
tally that had excluded the regenerator helpers. Measured again: 26.
2026-09-22 04:25:25 +00:00
doxa-maps
71b9aea7b4 verify:map-paint now covers 11 of 12 map profiles, up from 4
Some checks are pending
build-gate / build (push) Waiting to run
The paint gate listed four leaves -- the ones under investigation when it was
written. The manifest says twelve profiles carry isMap, so eight painted
leaves were ungated, including BOTH cap-4 composites; dashboard-plus-grid is
tab-1 of the cap-5 SPA, so the flagship view was outside the gate that exists
to prove maps actually paint.

Every added leaf was run through the script first and PASSED before being
listed. A gate must start green or it gets disabled and protects nothing.
Full run: all 11 paint, exit 0.

my-upg-100-list/poster-wall is deliberately excluded and the reason is in the
file. It is correctly flagged isMap -- poster-wall.vue:191 makeHiddenMap()
builds a real gl.Map -- but on demand during poster export, off-screen at
left:-9999px. At load there is no canvas, so the gate's "laid out, did not
paint" verdict is accurate while the product is fine. Listing it would mean a
permanently red gate, which is worse than an honest exclusion.

Negative-tested: poster-wall FAILS exit 1 (a leaf that really does not paint
at load), and a 404 URL FAILS rather than passing vacuously.
2026-09-22 04:21:33 +00:00
doxa-maps
2a1e55a0e8 catalog-currency: a bundle missing from the catalog now fails instead of passing
Some checks are pending
build-gate / build (push) Waiting to run
Card 1 Step 1 asks that the merge lose nothing. No gate owned that. A local
bundle that 404s live was counted "unreachable" and skipped, and unreachable
never gated the exit, so a catalog with an entire plugin deleted passed:

  CATALOG-CURRENCY: 17 current - 0 STALE - 1 unreachable (of 18 local bundles)
  CATALOG-CURRENCY: PASS                                        <- exit 0

Proven by deleting one bundle from a copy of the catalog and pointing the
gate at it. The hole fell between two checks that each answered their own
question correctly: identity proves the live bytes are self-consistent, and
currency proves they are the same build; neither asked whether anything was
absent. The tell was in the PASS text itself -- "every REACHABLE live
bundle". A qualifier inside a success message describes what a check does
not cover.

MISSING is now distinguished from UNREACHABLE. A 4xx is the server stating
the file is not there, which is a publish defect and fails. A connection
error or timeout is an environment condition and still counts as
unreachable, so an offline box still SKIPs and never fails a build.

Negative-tested four ways: the real catalog still PASSES (18/18, 0 MISSING);
a deleted bundle now FAILS exit 1 naming the exact URL probed; a tampered
bundle still FAILS as STALE; offline still SKIPs. Also fixed a reporting bug
introduced en route -- the failure printer rebuilt a .js path from the entry
name, mangling leaf URLs, and a wrong URL in an error message sends the
reader hunting for a file that was never requested.
2026-09-22 04:06:55 +00:00
doxa-maps
190d89846b Commit the built tree that shipped with golden-path-check
The previous commit staged only plugin-designs/golden-path-check/, so the
regenerated public/ tree was published to the catalog but never committed
here. Live and the working tree both read 18 bundles / 27 profiles while the
commit still said 17 / 26 — no live inconsistency, but the repo stopped
describing what it ships.

This repo tracks its build output, so source and output move together: every
prior change commits public/doxa-maps/manifest.json alongside the .vue files
that produced it. A narrowly scoped `git add` is what broke that here.
2026-09-22 03:59:22 +00:00
doxa-maps
e7eeb4d02a golden-path-check: walk the develop-a-plugin-and-see-it-live path end to end
Some checks are pending
build-gate / build (push) Waiting to run
The north star is that an admin agent can author a plugin, build it, publish
it and see it live. Every step was documented and individually verified, but
never walked in one pass by following the doc as written. This plugin is that
walk, authored from the documented three-file minimum.

It stays useful afterwards: it reads the manifest.json it was published
beside and reports what the catalog actually contains -- bundle and profile
counts, how many profiles describe themselves, and whether it is listed. If
you can see it answering, the path worked. Deliberately dependency-free: no
map engine, no token, no data API, so it can mount when everything else is
broken, which is when someone reaches for it.

The walk found a real defect in my own plugin, which is the point of doing
it. The first version wrapped the element in ProfileLoader (copied from a MAP
bundle) without createPinia. ProfileLoader builds three stores in setup(), so
setup threw -- and a thrown setup removes the subtree silently. Build green,
manifest listed it, nothing complained. Only smoke:embeds caught it: "shadow
DOM nearly empty (1 nodes)". That is exactly what Step 3b exists for and why
the doc puts it before publish.

The fix is the cap-1 pattern doxa-vision-card already uses: mount the root
SFC directly. ProfileLoader earns its cost for a map plugin; it is the wrong
wrapper for an element that draws no tiles and takes no token.

Verified: 17 -> 18 bundles, 26 -> 27 profiles, auto-discovered with no config
edit; renders 19 shadow nodes reporting live catalog state; embed suite 41/41.
2026-09-22 03:55:18 +00:00
doxa-maps
e2c5c2ec49 Gate: every shipped profile must say what it is
Some checks are pending
build-gate / build (push) Waiting to run
Last tick fixed 15 of 26 profiles that shipped with an empty description.
Without a gate that fix has a half-life: the north star is "develop a plugin
and see it live", so new plugins arrive often and each one is a fresh chance
to regress this silently. The repo already validates @element, but only
against a MALFORMED value — an ABSENT @description fails quietly, the
generator writes an empty string, every gate passes, and the defect only
surfaces to a human who opens the studio and reads.

Reads the BUILT manifest, not the .vue sources, on purpose. Re-deriving the
description here would mean reimplementing generate-tree's TAG_RE — which is
exactly what hid two of the fifteen last tick, when a hand-written copy of
that regex reported success while the real rule extracted nothing. Check the
artifact the consumer reads, never a reimplementation of how it was produced.

MIN_CHARS is calibrated against the real corpus, not guessed: shortest
genuine description 62 chars, median 81, longest 192, so the 25-char floor
sits clear of every legitimate entry while still catching a stub.

Negative-tested four ways, including end to end: deleting a @description tag
from a .vue source FAILS THE WHOLE BUILD (exit 1) naming the profile and the
file to edit; restoring it returns the build to green. Also fails on a short
stub and on a near-empty manifest (the vacuous-pass guard), rather than
passing for lack of anything to check.
2026-09-22 03:23:55 +00:00
doxa-maps
344c4dc831 Describe every plugin: 15 of 26 profiles shipped with no description
Some checks are pending
build-gate / build (push) Waiting to run
More than half the catalog had an empty description in manifest.json, so the
plugin-studio — the surface a Doxa inheritor browses to learn what exists —
showed flagship plugins as a bare "bundle - <tag>" with no explanation, while
a demo bundle explained itself fully.

Cause: the generator reads a JSDoc @description TAG, and these files carried
only prose. Every description added here is a summary of what that file's own
header already documented, not new invention. Claims were checked against the
artifacts: doxa-simple-map's "three presets" was verified against
simple-map.instances.json (prayer, engagement, adoption).

The two cap-5 shells needed a second pass. Their headers are HTML comments
with no leading asterisk, and the real TAG_RE requires `* @tag value`, so the
first attempt extracted nothing.

Result: 26/26 profiles described, 0 empty; the built gallery went from 15
empty / 11 populated to 0 / 26. Verified in a browser, not just in the
manifest. Embed suite 40/40.
2026-09-22 03:11:07 +00:00
doxa-maps
fc1159151f Give country-posters a real description so the SPA stops calling it "experimental bundle"
Some checks are pending
build-gate / build (push) Waiting to run
Both country-posters profiles had a prose header but no @description tag, and
the generator reads the TAG, not the prose. It emitted an empty description
into manifest.json, so the cap-5 SPA fell back to its generic label and the
Experimental rail showed a nameless tile — the only bundle in the app that
did. A Doxa inheritor browsing the SPA met "experimental bundle".

Added @description (and the @element tag both files were also missing, which
validates against the build's choke-point rule) to countries-map.vue and
country-poster-wall.vue, with text grounded in what each profile does.

Verified at the symptom, not the intermediate: the rendered rail now shows
the real description and the placeholder count is 0. Embed suite 40/40.
2026-09-22 02:45:07 +00:00
doxa-maps
40c4cc67d4 Move the eight CARTO basemap styles to OpenFreeMap
Some checks are pending
build-gate / build (push) Waiting to run
Eight of the twelve named styles resolved their MapLibre URL to
basemaps.cartocdn.com. Those are keyless today -- verified live, style JSON
and a real vector tile and a font all 200 with no token -- but CARTO's own
docs reserve the right to extend the key requirement to vector tiles. That
is a future silent outage already shipped, and the same single-point-of-
failure shape we are trying to escape by leaving the URL-restricted Mapbox
token. A dependency that is free today and gated tomorrow is a deferred one.

  dark-matter -> openfreemap dark      (midnight-glow, dusk, ink, wayfinder-night)
  positron    -> openfreemap positron  (paper)
  voyager     -> openfreemap liberty   (satellite, hybrid, wayfinder-day)

Each replacement verified before the swap: 200 keyless, zero keyed URLs in
sources/glyphs/sprite, real tiles serve. Built output now has zero CARTO
references. Embed suite 40/40 pages mount cleanly.

The header records the bar for adding a style here: keyless, commercial use
permitted, self-hostable. MapTiler and Stadia free tiers fail the second.
2026-09-22 02:27:48 +00:00
doxa-maps
8846be5b47 Let the map classifier see maps nested below a profile root or in another bundle
Some checks are pending
build-gate / build (push) Waiting to run
Two defects in generate-tree.mjs left the only two map-owning composite
leaves marked TOKEN_FREE, so they mounted with no token in profile-config
and survived only on a window.MAP_TOKEN fallback that yields to any
pre-existing global.

- isMapLeaf scans one flat file. research-dashboard.vue has zero engine
  references and is the only .vue in its folder, while the real map lives in
  src/components/dashboards/DashboardMapPanel.vue. New ownsAMapDeeply walks
  src/ for .vue/.js/.mjs and is wired into both the composite path and the
  non-composite branch.
- composes entries dropped the bundle segment, so a child from a sibling
  bundle was looked up in the wrong directory and silently missed.

Both fixes were needed; resolving cross-bundle alone still reports map=false
because the map is a directory deeper.

Negative-tested before wiring in: across every bundle, research-dashboard is
the only profile whose answer changes. Post-build check confirms the seven
genuinely map-free profiles stay token-free. embed suite 40/40, exit 0.
2026-09-22 02:09:59 +00:00
doxa-maps
7f08d09edc Remove the tile-403 on-screen banner; connect dashboard selections to the grid
Some checks are pending
build-gate / build (push) Waiting to run
Two Driver-reported defects.

1. No more "Basemap tiles blocked" popup on the map. The banner reported an
   operator problem (one Mapbox account setting on one deployment origin) to
   the end user, who can do nothing about it, on every map and every load. The
   console diagnosis stays, because it reaches the person who can act.

2. Selections in the dashboard now filter the grid beneath it. The two halves
   were never connected: research-dashboard had no defineEmits and its bus is a
   per-instance factory that is never provided, while dashboard-plus-grid passed
   the grid no filter props. The grid's receiving end already worked. Added the
   emit, plus a translation layer between the dashboard's snake_case facet keys
   and the grid's camelCase props, mirroring big-picture.

   Proven live: typing "Turk" takes the grid from 24 cards to 22 with wholly
   different rows. Only 4 of 12 facets map; the rest are dropped rather than
   guessed, because religion_family carries a family name while the grid's
   religion prop matches a code, and mapping them would filter to zero rows.
2026-09-22 01:43:56 +00:00
doxa-maps-88655c70
45888903c9 make check:done-evidence SKIP honestly instead of passing vacuously
Some checks are pending
build-gate / build (push) Waiting to run
Found by the folder-scan agent and verified by simulation. This gate resolves
ROOT at depth 2 while all 40 sibling gates use depth 1, because the backlogs it
reads live in _qa-cycle/ - agentic scratch OUTSIDE the repo, tracked by no git
repository and never cloned with the studio.

On this machine _qa-cycle happens to sit beside the repo, so it passes. An
inheritor who clones only the studio got something worse than a failure: it
printed "0 card(s) across 3 backlog(s), 0 done ... PASS" and exited 0. A
vacuously green gate certifies nothing while looking healthy.

Now it detects the absent directory and SKIPs with an explanation, still
exiting 0 so it cannot break an inheritor's build. Negative-tested both ways:
274 cards still checked here, honest SKIP in a bare clone.
2026-09-22 00:35:07 +00:00
doxa-maps-88655c70
3f103aaae7 pass the real token down from ProfileLoader when tk is absent
Some checks are pending
build-gate / build (push) Waiting to run
ROOT CAUSE, proven by interception rather than inference. A COMPOSITE leaf whose
own root paints no tiles is generated TOKEN_FREE, so its page calls mount('') and
the "tk: token" assignment never runs. Intercepting setAttribute BEFORE Vue could
consume it showed profile-config arriving with keys profile/dataSource/
instanceId/assetsBase and NO tk. ProfileLoader then computed '' and provided it
to every child, so a map nested INSIDE the composite got an empty accessToken and
logged "No Mapbox access token provided" - the error reported at
dashboard-plus-grid.js:605.

The generator assumed a composite's nested maps each render in their own iframe
leaf running its own token ladder. That is false for dashboard-plus-grid: zero
iframes, and ResearchDashboard / PeopleGroupsGrid are imported as COMPONENTS, so
the nested map runs inside the shell page and needs the token the shell withholds.

ProfileLoader now falls back to window.MAP_TOKEN when config.tk is absent.
STRICTLY ADDITIVE: 10 components inject mapboxToken and today all receive ''
whenever tk is missing; now they receive the real token. An empty string becoming
a valid token cannot break a map that already worked.

Verified: the nested map inside dashboard-plus-grid now carries a real
accessToken instead of ''. Negative-tested both ways - an explicit ?tk= still
WINS over the global (per-instance precedence intact), and with no token anywhere
the code still logs "No Mapbox access token provided" rather than silently
pretending. Full build ALL GUARDS PASS, SRI consistent.

This does not make the generator's classifier correct; see
BUG-COMPOSITE-LEAVES-ARE-TOKEN-FREE-BUT-NEST-REAL-MAPS for the deeper fix.
2026-09-22 00:09:43 +00:00
doxa-maps-88655c70
7ccdc37e8f show the tile-403 on screen, top-left, not only in the console
A console message only helps someone who already suspects a problem and opens
devtools. The reported experience was "I clicked the map and saw nothing", so
the page itself must say why.

The leaf's own #status node is not usable here: done() removes it the moment the
element mounts SUCCESSFULLY, which is exactly what happens in this failure -
mount succeeds, only the tiles 403 afterwards. So the composable creates its own
small banner instead.

A CORNER banner, never a full-cover overlay: the data layer, legend and labels
all render correctly and stay useful. Hiding a working map to report a missing
basemap would trade one blank screen for another.

TOP-LEFT was chosen by LOOKING at a screenshot, not by geometry. A first attempt
at bottom-left rendered underneath the legend panel and was unreadable, while
every numeric check still passed - 442x70px, correct palette, 3% coverage.
The other corners are taken: controls top-right, attribution bottom-right, hint
pill bottom-centre.

Material light only: #fff surface, #3b463d text, #d1d4cd border, #6d766e muted.
No red - this is a configuration notice, not a crash. pointer-events:none so it
can never block map interaction, and the container is only set position:relative
when it is still static, so a profile that positions its own container keeps it.
2026-09-22 00:02:50 +00:00
doxa-maps-88655c70
47e0e85808 rebuild leaves and bundles with the tile-403 diagnostic
Some checks are pending
build-gate / build (push) Waiting to run
2026-09-21 23:45:33 +00:00
doxa-maps-88655c70
4c6d8571cf name the tile-403 failure instead of rendering a silent blank void
A URL-restricted Mapbox token returns 200 for the style document AND the fonts,
and 403 only for tilesets. So the element mounts at full height, the legend and
labels paint, the data layer paints - and the basemap silently never arrives.
It reads as a rendering bug in our code. It is not: it is one account setting.

Nothing listened for it. GL-JS fires an "error" event carrying e.status on every
failed tile request, so the information was always there - no one asked. That gap
cost real debugging hours this flight and is the first thing an inheritor
deploying to a NEW domain will hit.

The listener attaches after both engine branches converge, so it covers Mapbox
and MapLibre with one hook, and is wrapped in try/catch so an engine without an
.on() method can never break map creation.

ONE MESSAGE PER MAP, not per tile: a blank basemap 403s every tile in the
viewport, dozens per second while panning, and an unguarded console.error would
bury the page and train the reader to ignore it.

Verified both directions: a restricted map produced 9 tile 403s and exactly 1
diagnostic; the token-free maplibre profile mounted 800px and emitted 0.
Full build ALL GUARDS PASS, SRI consistent.
2026-09-21 23:44:11 +00:00
doxa-maps-88655c70
6005bc101b correct a false claim: the map engines load from CDN, not from vendor/
Some checks are pending
build-gate / build (push) Waiting to run
The Tauri README said the bundles 'self-host mapbox-gl from public/assets/vendor/
(the no-CDN rule)'. Measured: vendor/ contains only tabulator, and every leaf
carries src=https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js. MapLibre
likewise loads from cdn.jsdelivr.net. The ENGINES registry in
library/composables/useMapInstance.js is the authority.

This mattered because the sentence sat next to CSP guidance: a script-src
written from the old claim would have blocked the engine and produced a blank
map - the exact symptom being debugged elsewhere.
2026-09-21 23:36:55 +00:00
doxa-maps-88655c70
9dc4dda8cf consolidate docs into documentation/ and deployment shells into deployment/
Some checks are pending
build-gate / build (push) Waiting to run
Driver 2026-09-21: 'a folder called deployment which will store the capacitor
folder, wordpress folder and tauri folder', and 'a folder called documentation
to store the contributing folder so docs are in one place'.

docs/ + contributing/ + CONTRIBUTING.md -> documentation/ (three doc homes into
one). capacitor/ + wordpress/ + src-tauri/ -> deployment/.

The public/ -> dist/ rename the Driver asked about is NOT here: after seeing the
measured blast radius (855 tracked files, 54 source refs, and public/ is the
served web-root four consumers point at) the Driver withdrew it - 'keep the
public folder the same'.

Path references were rewritten mechanically (258 across 87 files), but three
classes needed hand fixes because a prefix regex cannot see them:
  - join(ROOT,'docs',...) in 10 internal scripts - a quoted SEGMENT, not a prefix
  - tauri frontendDist '../public' -> '../../public', since src-tauri is now one
    level deeper; check:tauri caught this exactly as the card predicted
  - the WordPress contract test's dirname(__DIR__,2) -> 3, same reason

Also reverted one over-reach: the rewrite renamed a FINDING-CATEGORY label
'docs' in public-hygiene-check.mjs, which is a rule name in its own header, not
a path.

Verified: full build ALL GUARDS PASS, SRI consistent; check:tauri, check:mobile
(cap sync + webDir OK), check:wp-embed, quoted-scripts, capability-citations,
capability-exemplars, library-index, public-hygiene, parse and drop all pass.
2026-09-21 23:23:23 +00:00
doxa-maps-88655c70
0c7bfb9b9c add a catalog CURRENCY gate: is the live tree the build we have?
Some checks are pending
build-gate / build (push) Waiting to run
check:catalog-identity hashes each live bundle against the CATALOG'S OWN
manifest - deliberately, per its header. That proves the served tree is
internally consistent; it can never prove it is current. A catalog three
publishes behind is perfectly self-consistent and passes clean.

Measured today: after baking the Mapbox token, catalog-identity reported '17 of
17 identity-proven - PASS' while every live leaf still served BAKED_TOKEN = ''
and every map on the platform was broken.

The two gates answer different questions and neither subsumes the other:
  identity  is the live catalog consistent WITH ITSELF?  (tamper/corruption)
  currency  is the live catalog the SAME BUILD as ours?  (staleness)

It checks BOTH bundles and leaf pages, because a bundle-only version of this
gate also passed: baking the token changed 37 leaf HTML pages and ZERO .js
bundles. generate-tree owns the leaves, vite owns the bundles, and a publish can
change either independently.

Current reading: bundles 17 current, leaves 17 STALE.

Negative-tested: unreachable catalog SKIPs (offline must not fail a build), a
manifest yielding 0 integrity values FAILs rather than passing vacuously, and a
missing local build SKIPs.
2026-09-21 23:05:53 +00:00
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