library/packet/poster-render.js and posterSheets.js arrive in this landing
carrying the generator's comments as they stood before the sweep: two lines
named the Driver and one said "he changed the SHAPE". They are reworded as
the sweep reworded them in generate.mjs, and two pronouns the sweep left
behind (SearchFilterHeader, check-filter-width-stable) now say what was
asked instead of who asked. The baseline drops the two generate.mjs lines
that moved out.
Every reference to the Driver in internal/ is reworded the same way as in
library/: the reason stays, the speaker and the quote go, an open decision
reads "provisional, pending a product decision", and agent names and decision-
sheet rows are dropped. Where a gate printed the word, its message now names
the reviewer or the ruling instead.
Three spots were code, not comments, and changed with their callers:
- the element census's allowlist field `driver` is now `ruling`, in the gate
and both presets (same validation: match, ruling, date and card required);
- check:i18n-never-translated's DRIVER-GATED marker is DECISION-PENDING, in
the entries and in the regex that counts them (its output is otherwise
byte-identical);
- the package.json description of the census says "ruling".
public-hygiene-check keeps its two patterns for the word: they are what keep
it out of the served folder.
Only the browser seat runs browsers, and only inside internal/browser-slot.sh.
That rule was remembered, not enforced: an agent ran a gate it took for a
headless source check and it launched Chromium outside the slot.
internal/lib/playwright-guarded.mjs is now the one way a repo file reaches
playwright: its launch(), launchPersistentContext(), launchServer(), connect()
and connectOverCDP() exit 75 (NOT MEASURED) with a message naming
browser-slot.sh unless DOXA_BROWSER_SLOT is set, and browser-slot.sh exports it
for the command it runs, so a bash -c grandchild inherits it. All 72 files that
imported playwright switch to it (internal/lib/browser.mjs and 64 internal gates;
plugin-designs scripts: doxa-research-map 3, my-upg-100-list 2,
research-map-plus-grid 2, research-dashboard 1): a mechanical import swap.
check:browser-gates-guarded (gates:pre) fails a direct import;
bun run browser-gates lists the 97 scripts that launch a browser. isBrowserGate
moves to internal/lib/browser-gates.mjs so the runner and the check share it.
The output tree's name has always been configurable (vite.doxa-maps-build-config.json "name"),
but 61 tooling files ignored it and hardcoded 'doxa-maps' (181 literals), so a rename would have
moved the build while the gates kept reading the old folder.
- internal/_tree-name.mjs reads it once (validated as one URL-safe path segment; falls back to the
historical name) and exports TREE_NAME / TREE_NAME_RE; `node internal/_tree-name.mjs` prints it
for shell scripts.
- A codemod rewrote 171 literals in internal/ to TREE_NAME; the regex literals, messages with
backticks, atomic-public-swap.sh (fails loudly if the name cannot be read), build-lock.sh and
vite.config.js (its duplicate readMfeName is gone) were done by hand.
- The name is unchanged: old and new tooling produce all 1408 files under public/ byte for byte.
Code lines still naming the tree in internal/ + vite.config.js: 2, both intended (the helper's
fallback, the private-repo-name pattern in public-hygiene-check).
This renames nothing visible; the eventual rename becomes a one-line config change.
The studio's rail was hand-rolled vertical tabs stretched into a tree.
It is now an Explorer tree modelled on the VS Code Explorer, keyboarded
per the WAI-ARIA tree pattern:
- CAP 5 -> CAP 1 sections with sticky headers; one-line rows with the
name and a muted element tag; twisty on the left; indent guides.
- Composites fold; their parts hang beneath, one level at a time, each
part opening the plugin's one canonical entry.
- A filter box on top shows matches together with the path to them.
- One view action, Collapse all.
- Auto-reveal: opening a plugin by click, URL hash or remembered tab
scrolls its row into view and opens its section.
- Arrow keys walk the tree, Right/Left open, close and move between
parent and child, Enter opens, a letter jumps to the next name.
- The slim rail becomes an activity bar with one icon per section.
- Fold state is remembered per page.
The tree lives in plugin-designs/plugin-studio/src/StudioTree.vue and
fills VerticalTabsLayout's new #rail slot, so the layout still owns the
collapse toggle, mobile drawer and slim strip. The alias, depth and
branch row fields added to the layout earlier today are removed again:
nothing else used them.
The studio host now has a default height (85vh, min 480px), as
doxa-maps-spa does. Pasting the published embed snippet into a plain
page mounted a complete studio 0px tall. A page's own height or
--plugin-studio-height still wins.
The studio gates that click tabs by text now also match tree rows.
Rebuilt the plugin-studio bundle and re-baked its SRI.
Five gates took their measurement from whatever answered a default localhost
port. On this machine that port belongs to whichever worktree started first:
:4173 is wt-packet's server, :4898 is its repo root. Measured today — an hour of
probing reported on a build that was not on disk here, and the mistake was only
caught because a marker string was in the file and absent from the response.
"Answers 200" and "is serving this checkout" are different questions, and only
the second one licenses a verdict. _serves-this-tree.mjs already asked it (it
exists because the gospel-resources gate hit this and spent three runs reporting
on the wrong tree) but only three callers used it. Wired into the five that
measure a served page and had no such check:
check-provider-switch request tally per host — the wrong tree's tally
check-maplibre-layer-drops silent layer drops — invisible by nature
check-filter-width-stable width divergence — would accuse untouched CSS
check-facet-changes-map data flow on facet change
check-packet-browser imports the renderer FROM the server
Only a genuine MISMATCH skips. "No manifest to compare" is a different
precondition (an unbuilt checkout) that each gate already speaks for, and
turning that into a skip would silence these on the builds they should run on.
A mismatch exits 0, like the unreachable case, because it is a precondition and
not a product finding — but it prints which, so nobody reads it as a pass.
CONTROLS, each fired, per gate: correct server -> PASS with a real number
(provider-switch 19 -> 5 mapbox requests, drops 17/17, filter-width identical,
facet 2107 -> 1209 dots, packet-browser headings match); a served tree whose
manifest differs by one key -> SKIP naming "serves a DIFFERENT build"; no server
-> the pre-existing unreachable SKIP, unchanged.
One correction found by running it rather than by reading it: the first wiring
passed the LEAF PAGE URL where the helper wants the ORIGIN, so it looked for the
manifest under the page's own path and skipped every run for the wrong reason.
node --check cannot see that; only running both controls can.
KNOWN COST, not hidden: in a wave with no server of this tree running,
check:provider-switch and check:packet-browser now SKIP where they used to
report a green about another worktree. That is the honest reading, not a
regression, and the runner prints "evaluated nothing" for them. Next increment
is to let these gates serve this checkout themselves so they measure again.
The gate printed "FAIL — 2 host(s)" when it had measured NOTHING —
ERR_CONNECTION_REFUSED on :4173, "UNMEASURED" twice, then a failure. That
accuses the product of a regression on the strength of a missing process,
and it already cost one wrong report in this flight: the gate was reported
as failing when it had simply never run.
★ THIS DOES NOT REVERSE THE AUTHOR'S RULE. "An unmeasured host is a failure.
A gate that quietly drops a host reports success about something it never
looked at" is correct for a STRUCTURAL miss — the page loads and the trigger
or tab cannot be found, meaning the gate has been defeated — and that path
is kept, comment and all. Only connection-level errors are separated out.
It is also not a judgement call, because the house already decided: four
sibling browser gates skip on an unreachable host and say so in the same
words — check-fits-the-screen, interop-harness, check-catalog-identity and
check-legend-matches-source all print "SKIP — ... unreachable right now.
Not a product finding." This gate was the outlier.
THREE CONTROLS, ALL FIRED:
no server -> SKIP, exit 0
server + healthy code -> PASS, exit 0
real regression, the original
flex RANGE put back on
.filter-dropdown -> FAIL, exit 1 ("the row grew 369px")
The third is the one that matters: it proves the gate can still catch the
bug it exists for, rather than having been quietened.
Before believing any of those browser measurements I ran `bun run tree` and
confirmed check:sri 0 — a single-bundle build leaves stale SRI, the browser
refuses the script, and the page then measures clean for the wrong reason.
That trap made a negative control vacuous earlier in this flight.
check:filter-width was failing on both hosts with "UNMEASURED: no
.filter-dropdown__trigger found in the composed tree" and "no tab matched
/dashboard-plus-grid/i". Neither was true of the product. Both paths ended in a
trailing slash — directory URLs — and `bun run serve` is serve-strict.mjs, which
404s a directory with no index fallback because the live static layer does. A
404 page has no trigger and no tab rail, so a missing PAGE was reported as two
missing FEATURES. Measured: both directory URLs return 404, both index.html URLs
return 200.
Now passes in both hosts, and what it reports is the Driver's fixed-width
decision holding: all eight triggers at 168px before and after selecting the
longest option, identical in the standalone dashboard and inside the cap-5
studio.
Two things were checked rather than assumed. The tab regex looked like a second
bug — the rail labels tabs "Big Picture", not by bundle id — but the studio also
renders a developer row reading "dashboard-plus-grid<dashboard-plus-grid>", so
/dashboard-plus-grid/i correctly matches tab 3. Single cause, not two. And the
trigger class the gate looks for does exist (FilterDropdown.vue:278), which is
what made "the page is wrong" the remaining explanation.
Also adds a response check before any content assertion, so this failure mode
cannot recur silently. Negative-tested by pointing it at a nonexistent page: it
now fails with "UNMEASURED: HTTP 404 for /research-dashboard/NO-SUCH-PAGE/
index.html — nothing below this means anything", which names the cause instead
of describing a symptom.
Worth recording why this survived: one tick earlier I swept for directory URLs
and reported the class closed. That sweep grepped for goto(`${BASE}...`) as a
template literal, and this file builds its URL as BASE + path by concatenation,
so the pattern could not match it. A sweep is only as wide as its pattern. The
re-sweep looked instead for any goto whose URL lacks ".html" — a property of the
RESULT rather than of the syntax — and found this one plus nothing else among
the static-tree gates. The BASE + '/' users in smoke-studio, smoke and
check-scrim target the vite dev server on 5173, which does resolve directories,
so they are correct as written.
.filter-dropdown was flex:1 1 160px with min-width:160px and max-width:240px
-- a range, not a width -- so flex-grow renegotiated the moment the selected
label appeared. The row grew 960 to 1294px and triggers reached 240px.
Not cosmetic. The selections row with Clear All sits underneath the filters,
so a filter that grows on selection re-flows the row below it and the band
stops being a fixed block. The stable width is what lets the centred stack
hold still.
Independently reproduced before gating rather than taken from the agent
report: 168px across all six triggers, identical after selecting the longest
option in each. The negative test then reproduced the broken widths
[240,240,240,184,171,219] -- the same array the original report cited, which
confirms that measurement was real.
Asserts the observable outcome, not the CSS. A grep for --dx-filter-w would
pass while a missing min-width:0 on the label still pushed the box open: as a
flex item without it the label is floored at min-content and refuses to
ellipsise. Both halves are needed, so the gate measures the width.
Selecting the LONGEST option is load-bearing. A short option fits inside the
old range too, so a probe that clicks the first option passes on a build that
still has the bug.
Two hosts, and an unmeasurable host fails rather than skips -- three defects
this week were invisible standalone and appeared only in the shell.
Negative-tested: restoring the flex range fails both hosts by name, naming the
before and after arrays and the total growth.