A backtick in live shadow CSS breaks the build exactly as one in a CSS comment
does, and check:template-literal only reads the comments. Proven both directions
last tick: the planted defect left that gate at pass while the build died with a
Babel stack trace pointing far from the edit.
The card concluded this was not achievable, on good evidence — three lookahead
heuristics were tried and reverted, scoring 98 and then 136 false positives on a
healthy tree, because the real terminator of a template literal is also a
backtick and no window of surrounding characters separates the two. The evidence
was right and the inference one step too strong. No heuristic works is not
nothing works, and the card named the answer in its last line: needs a tokenizer.
A parser does not guess where a literal ends; ending it is what parsing means.
Measured against the same tree the heuristics failed on: zero false positives
across 83 files. Splitting the SFC first means it sees exactly the script text
the build compiles, and using the parser the build reports errors from means its
verdict matches what actually breaks rather than approximating it.
Negative-tested with the exact case the other gate misses: a backtick planted in
live CSS passes check:template-literal and fails this one, naming the file and
the line that was edited rather than a stack trace somewhere else. That line
number is the point — the build was always the backstop, just an unhelpful one.
Every map image shipped data floating on an empty navy void — no coastlines, no
borders, no labels. Two cards had this blocked on the Driver adding origins to
the Mapbox token's allow-list, for four ticks.
It did not need the Driver. Measured both directions: a tile request with no
Referer returns 403, the same request with the doxa.life Referer returns 200,
and the style document returns 200 either way, which is why the map still booted
and every gate passed. The shot harness serves from 127.0.0.1 and so sent no
Referer at all. These are doxa.life's own documentation images, generated by its
own repo from its own token; the restriction exists to stop third parties burning
the Driver's quota, and it was blocking the owner.
Route interception looked like the careful way to scope it and silently does not
work — Chromium ignores a referer supplied through route.continue, so tiles still
403'd. Only the context-wide header applies, and that breaks same-origin iframes:
the iframe-host shot went from 187 elements to 15.
So it is opt-in per shot, and the predicate is architectural rather than
anecdotal: any surface that nests content in an iframe opts out. I set it for the
one shot that failed first, then watched both cap-5 shells collapse on the next
run, and generalised. Map leaves get their basemap; shells keep their content.
Verified by reading the images, not the file sizes — a size jump had already
misled me once this tick.
It showed an empty pane reading "Choose an element from the list." The README's
headline image for the highest capability level demonstrated nothing, and every
shot gate passed it, because freshness, distinctness and element counts are all
satisfied by an empty pane. The shot now selects the first rail row before
capturing, so the picture shows what the studio is for: a nested SPA rendering
inside the shell that lists every element by capability. The file went from 167KB
to just over a megabyte, in line with its sibling.
Found by reading the image, the same way the mobile error card was found
yesterday. Reading one picture has now caught two defects that eleven gates
passed.
Add check:shot-fonts. The studio labels each row with a capability emoji, and
this machine has no emoji-capable font, so all 28 rendered as tofu boxes in the
committed PNG. The markup is right and the product is fine in a real browser —
the shot machine could not draw it — so the fix is not to strip the emoji. The
gate asks fontconfig whether this system can render the glyphs the UI actually
uses, and names the font install rather than the markup.
It is deliberately not in build:unlocked: it reports an environment condition
that is currently true here, and a gate that always fails in the build makes the
build useless. Its first version also flagged the star this repo uses as a
comment marker, which every text font renders; the range is now narrowed to the
pictographic planes.
Two composites nested a child by tag and URL without a profile-config. Those
children do not render a component directly — their bundle entry mounts
ProfileLoader, which reads profile-config.profile to choose which view to load.
With none, the element still mounts and still reports its tag, so every
structural check said both children were present, while the loader rendered
"missing or invalid profile-config prop" where the map should be. The grid half
of each page worked, which is how it survived.
Found by reading a regenerated README screenshot, not by any gate. The mobile
screenshot showed an error card and an empty pane; the fixed one shows the real
research map with its data, controls and progress figures.
research-map-plus-grid was mine, shipped that way. stack-demo predates it and had
the same defect against research-dashboard. Both now pass the shape their own
generated leaf pages use, so nested and standalone mounts ask for the same thing.
check:composite-srcs now requires a profile-config on any child whose bundle
mounts ProfileLoader, derived from which entries import it rather than a list.
The first version of that check tested for the word and passed on its own
explanatory comment; it now matches the quoted attribute key, and a negative test
that mutates only the key fails as it should.
Also regenerates three derived artifacts that had drifted behind this flight's
changes: the library index, the capability exemplars table, and all twelve README
screenshots.
A cap-5 shell nests its children as iframes, and window.MAP_TOKEN does not cross
an iframe boundary — each frame is its own window. A nested map cannot inherit
the parent's token, so it works only because the child leaf carries its own baked
copy. Proven in a browser: all thirteen nested frames in the cap-5 shell hold a
real token, including a map two levels deep. That was a one-off run; this makes
it standing.
The existing smokes cannot catch this. Both inject window.MAP_TOKEN with
addInitScript before the page runs, which is right for what they test but means
the test supplies the thing under test. A user opening a shipped leaf has nothing
injecting a token for them.
Also asserts the reverse: no token may be compiled into a .js bundle. Bundles are
cached, SRI-pinned and reused across hosts, so a token baked into one cannot be
rotated without a rebuild and leaks into every embed.
Reads the built manifest rather than walking directories, so it checks the set
that actually ships. Counted 43 leaves, matching an independent filesystem count.
Negative-tested both directions: stripping a leaf's token and planting one in a
bundle each fail with the artifact named.
The desktop composite pairs the research dashboard with the grid. This is its
mobile sibling: the monolithic 7-tab research map on top, the same grid beneath,
stacked to scroll. Which one a shell shows is an aspect-ratio choice made at the
SPA level, so neither plugin knows about the other and both stay embeddable.
The grid's own search and filters are ON here. The desktop composite turns them
off because the dashboard header owns those controls and drives the grid through
props; the research map has no such header, so switching them off would leave the
grid with no way to be searched at all.
Composes its children by tag and bundle URL rather than importing their .vue
files. Importing the research map directly would bypass its bundle entry, which
is where its three bundle-private colour strategies, its profile registry, its
Pinia and i18n pair and the RTL text plugin are installed — and losing those is a
runtime defect in a green build, because a thrown setup() removes the subtree
with an empty error.
Add check:composite-srcs, which resolves every nested src against the built tree
and asserts every nested tag is registered. A wrong relative depth is a 404, and
the stack layout swallows a failed section on purpose so one broken child cannot
take the whole stack down — which means the cost of that resilience is a silently
empty band. Negative-tested both ways: breaking the depth and renaming a tag each
fail with the file and path named; restoring returns to pass.
Ran the browser-free gate suite inside a fresh clone -- the inheritor's
conditions. Five passed; check:dxlife went red with "clone not found at
/root-dx-life - nothing to check".
That is not a defect. The gate deliberately uses three values (0 unchanged,
1 drift, 2 no clone) because it exists after a check that returned the PASS
value BECAUSE IT FAILED: `cd root-dx-life && git status --porcelain | wc -l`
printed 0 for a directory that is not a git repo. Collapsing "cannot judge"
into "pass" is exactly the bug it was written to prevent.
But `bun run` and any && chain see only zero/non-zero, so the middle value
evaporates precisely where a reader consumes it. The fix belongs in the
documentation, not the gate: added a preconditions row stating what it needs,
what exit 2 means, and that it is NOT in build:unlocked, so a clone still
builds.
Verified the whole inheritor path again after the Cargo.lock change: clone
(2358 files, lockfile present) -> bun install exit 0 -> bun run build exit 0,
18 bundles / 27 profiles, with check:dep-names and check:no-abs-paths both
running inside the clone's own build.
Makes the cheap half of last tick's P0 automatic. A folder rename had
rewritten three npm package names, so bun install failed in a fresh clone
while everything here passed -- the real packages were already in
node_modules from before the rename, making the breakage invisible to every
build and every gate.
Checks two things, offline, in milliseconds: the declared name resolves under
node_modules/, AND the installed package's own name field equals the declared
key. The first alone catches the capacitor case; the second catches a name
that resolves to a DIFFERENT package, which a path-only check would miss.
Together they detect the corrupted-name class without a network round-trip,
which is what lets it run on every build rather than in a nightly job nobody
watches.
It does not replace the clone test. A gate can only see what is IN the repo;
only a clone sees what the repo fails to do on a clean machine.
Negative-tested four ways: recreating the EXACT P0 (the three
@deployment/capacitor/* names) FAILS naming all three; a name that resolves
to a package whose own name differs FAILS; an absent node_modules SKIPs
honestly rather than passing or failing; an empty dependency list FAILS via
the vacuous guard. Wired into build:unlocked and confirmed running inside a
full build (17 declared, 17 verified, exit 0).
Tested the north-star path as an inheritor would, by cloning the repo from
Forgejo and running the documented first command. It FAILED:
error: GET https://registry.npmjs.org/@deployment%2fcapacitor%2fcli - 405
error: @deployment/capacitor/cli@7 failed to resolve
The rename of capacitor/ to deployment/capacitor/ did a blind string replace
that rewrote the NPM PACKAGE NAMES too. @capacitor/android, @capacitor/cli
and @capacitor/core became @deployment/capacitor/*, which do not exist on
npm. Restored the real names, pinned to ^7.6.9 -- the version actually
installed -- and fixed the two places that repeated the wrong name in prose.
This is the inheritor-only failure shape, in a form the new absolute-path
gate cannot see. The local tree works perfectly because node_modules/
@capacitor/* was installed BEFORE the rename, so every build and every gate
passes here. The breakage lived only in the DECLARED names, visible solely to
someone installing from scratch.
Verified by doing it: a fresh clone now runs bun install to exit 0 (206
packages, all three @capacitor/* resolved) and bun run build to exit 0,
producing 18 bundles / 27 profiles -- identical to the working tree.
The repo is clean -- git grep over every tracked file returns zero /home/
paths. That is a real hand-off property: a clone works on any machine, which
is what "the path Doxa inherits" requires. But nothing enforced it. Proven by
experiment: a /home/lk10x/... line planted in internal/settled-tree-gate.mjs
passed check:public-hygiene, check:parse AND check:quoted-scripts. The tree
was clean by accident, not by construction.
This is the worst failure shape for a hand-off. An absolute path works
perfectly for whoever wrote it and breaks only on the inheritor's machine, so
no test run by its author can catch it. The sibling ideation tree already
paid that cost: two docs cite a workspace root that no longer exists, so a
reader following them silently finds nothing.
Not a flag on check:public-hygiene, which already detects these patterns but
walks a SERVED tree where node_modules never exists -- pointed at the repo
root it produced 1,427 findings, nearly all from node_modules/.vite. Its
scope is right for its job. This scans `git ls-files`, the set a clone
actually receives, which excludes node_modules and build output with no
ignore list to maintain.
Negative-tested three ways: the exact planted path that beat three other
gates now FAILS with file:line; a /Users/ path in a doc FAILS; a checkout
with too few tracked files FAILS rather than passing vacuously. Wired into
build:unlocked and confirmed running inside a full build (783 files, exit 0).
It then caught its own documentation -- my reference.md row quoted the
literal patterns as examples. Rewrote the example rather than allow-listing
the file, which would have blinded the gate to a real path in the repo's
largest doc.
The OPEN WORK line asks for Playwright coverage over every plugin AND ALL
SHARING. Sharing had never been exercised across the set this flight: the
gate needs a served tree, a token, per-plugin --url flags and (on this box)
--browser chromium, so a bare `bun run smoke:share` only prints usage.
Derived the real coverage set -- grep -rln ShareButton plugin-designs/*/*.vue
gives 8 built leaves -- and ran it: 8/8 pass. Every Share button emits a URL
that resolves 200 text/html and reopens the correct element with a live
canvas.
The pass count alone would have proved nothing. The failure this gate exists
to catch, an SPA fallback or a host stripping the hash deep-link, renders A
map for every share URL, so all 8 pass while all 8 links are wrong. The
discriminating evidence is six DISTINCT reopened tags, each matching its own
plugin, canvas:y on all eight.
reference.md now carries the copy-paste invocation, verified by running it
verbatim from a clean shell (8/8, same six tags). It also corrects a detail:
the doc said Firefox, which is the default but stops on this box -- the tool
detects its own WebGL limitation and names the chromium flag in its output.
A standing work item reads "gospel-resources + cities-internet maps must
render with REAL resource links" -- one line batching two profiles whose
contracts are opposite. gospel-resources genuinely renders 155 real eBible
links. cities-internet renders zero, deliberately: its own header says
"PERSECUTION-SAFETY: this profile carries connectivity + population +
language only. It has NO gospel-resource, church or believer field and must
not gain one."
So taking that line literally means adding a gospel-resource field to a
profile whose header forbids exactly that. The instruction is stale in the
worst way -- not merely wrong, but pointing at work that would remove a
safety property.
The rule existed only as a comment in one .vue header. It now has a section
in reference.md under Data sources, with the measurement behind it: zero href
bindings across all three cities-internet profiles, 1,567 live nodes and 0
anchors, and a `source` field that is API provenance rather than a
destination.
Also recorded that "persecution-safe" means three different things in this
repo -- field separation here, placename precision in key-cities, a
dependency-free table in DashboardDataTable -- so nobody generalises one into
the others. Only the first governs what data a profile may hold.
The exemplars table was authoritative but ~60 rows, so correcting a wrong
citation still meant reading it and DECIDING which row to quote -- the exact
step where a wrong citation gets re-invented. Added a generated five-row
summary that answers "cite this" per tier by a stated rule, and named the two
known-wrong citations it replaces.
Both live in the /plugin-designer skill, which sits outside this repo and so
cannot be gated from here (a checker pointed at it would pass on this machine
and skip silently everywhere else). Measured: unified-research-dashboard as
the cap-4 reference names a bundle that DOES NOT EXIST -- it became
research-dashboard (cap-3) plus dashboard-plus-grid (cap-4). And
doxa-vision-card as the cap-1 example ships an instances.json with 2
instances, which makes it cap-2 parameterized by the project's own rule.
The first version of the rule picked stack-demo for cap-4. It satisfied the
rule perfectly -- name equals profile, shortest -- and was still the wrong
answer to hand someone, because big-picture is the canonical cap-4 composite
named in the Gate-1 chain. Added a demo/template deprioritisation and
recorded why: a reproducible rule is not automatically a good one, so check
what it actually selects.
All five picks now match the skill's own reference implementations, except
the two that were wrong.
Applying last tick's lesson systematically: swept every numeric claim about
the catalog across all docs. Only one had aged into something misleading.
"Measured 2026-09-21: 17 of 17 bundles identity-proven" was TRUE when
written, and the gate reads the live manifest so nothing was broken. But a
reader today sees 17 beside a catalog of 18 and cannot tell whether a bundle
went missing or the sentence simply aged. Re-ran check:catalog-identity: 18
of 18, PASS. Restated with the delta named.
The other three hits are fine and deliberately left alone: the "21 bundles"
in the CLI doc is prose explaining why a count was REMOVED, the 18 in
reference-capability-exemplars.md is auto-generated, and "5 plugin
capabilities" is a constant rather than a count.
Added the general rule beside the number: a dated measurement is not a stale
claim, but it still misleads once the underlying figure moves, so re-run the
gate and restate rather than leaving a reader to guess.
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.
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.
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.