14 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fbc517c94a |
A browser gate refuses to launch outside the browser slot; check:browser-gates-guarded keeps it that way
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. |
||
|
|
d753998e0f |
Three gates read the tree's name instead of assuming it
The output tree was renamed doxa-maps → dx-maps. The name has always been a config knob, and _tree-name.mjs exists precisely so a rename is one line — but three gates still built their URLs from the literal, so they fetched a folder that no longer exists. THE FAILURE MODE IS WHY THIS MATTERS MORE THAN A PATH FIX. A gate pointed at a missing tree does not error; it loads a 404, finds an empty document, and reports whatever "nothing" looks like in its own terms. Measured in one sitting, all three of these were artifacts of the rename rather than findings about the product: a frame probe returned 0 images and no wall, twice, on two different leaves — which I first read as "the composite mounts the wall in a second pane"; check-basemap-labels-localized returned NOT MEASURED, "map never finished its style"; and every leaf under my plugin answered 404 while the card-fits gate had been passing, because IT was already migrated and my probes were not. Only the refuse-to-judge guards kept those from becoming filed defects. WHAT WAS ACTUALLY LIVE, after separating comments from code: exactly ONE executable literal across the 27 files that still mention the old name — the --leaf default in check-gospel-rtl-shaping, which I added last tick when parameterising that gate for the other raw-map owners. The rest are prose, plus one in public-hygiene-check that is the private REPO name (doxa-life/doxa-maps) and correctly unrelated. So the fleet-wide migration was essentially complete and this closes it. check-leaf-english-leaks now imports TREE_NAME for both its reachability probe and its leaf URLs; check-poster-card-fits was already migrated in code and only its comment still described the old path. |
||
|
|
44ef31b2c6 |
Tooling reads the tree's name from the build config, in one place
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. |
||
|
|
89bd9c5d79 |
Poster wall: the banner gets stickers, top and bottom
The card is a BANNER that displays a map, the way a label displays a design on a bottle. A banner taped up gets a sticker at each end, and each sticker straddles the edge it tapes: part on the banner's own white space, part overhanging. Neither one touches the map. MEASURED at 1440px before this change: banner 240-426, map 241-425 -> 1px of white space at each end header sticker 35px above the banner's top edge, -1px below footer sticker 1px above the bottom edge, 67px below So the map filled the banner edge to edge and there was no banner surface for a sticker to sit on. The footer row could only hang off into space, which is what shipped, and every assertion in the gate was green through all of it. The banner now reserves white space at both ends (--banner-pad-top / --banner-pad-bottom on .main), and each sticker centres on its edge: header sticker 17px on the banner / 17px above it footer sticker 35px on the banner / 33px overhanging map covered by either: 0px The print row also moved INSIDE .main. It was a sibling, so top:100% resolved against the CARD, 27px past the banner - the re-parenting and 07d3341c's centring transform are one fix, and neither works alone. That commit's per-row localStorage preference is preserved. Card Deck is no longer a peer button with a 162px <select> beside it. The stock picker is a disclosure: press Card Deck, choose a size, and that choice prints. A native dropdown in the row collides with the host page's own dropdowns, which is why it is buttons rather than a <select>. Print-button labels WRAP instead of clipping. With the per-row default now 2, a 390px viewport halves the card to ~170px, where Romanian's "Pachet de cartonașe" cannot share a line: measured 54 of 81 controls clipped at ro/390px, en and zh clean. The ruling on this wall is that a long label wraps; nowrap was forcing a clip. Also fixes the transparent strip above a pinned section header. Sticky pins to the scroll container's PADDING box, so with .wall's 22px padding-top the header rested 22px down and posters scrolled visibly through the gap. A ::before extends the header's opaque fill upward by exactly that padding, read from the same var so the two cannot drift. Measured by sampling the strip's pixels: 1 colour, 100% header background, where before it was 1338-2831 colours of poster artwork. The MFE shell's own chrome is translated, in all 11 locales. Measured with ?lang=ar: "Select View", "Views", "Prayer Resources" and "Map" rendered English while the poster wall's buttons inside the panes translated correctly, because each pane installs i18n and the wrapper around them never did - the chrome a reader meets FIRST was the untranslated part. The wall's own heading, tag and intro go through t() for the same reason, with the <strong> inside the translated string so each language emphasises its own words. The gate grew four assertions, each mutation-tested to fail on the defect it was written for: off-frame (row floated off the banner), head-off (header sticker not straddling), map-covered (a sticker on the poster), and selects (a native dropdown back in the row). Includes a rebuild of the four PosterWall consumers, which also picks up studio's shared useMapInstance unmount guard and exp-shepherd's translated LegendMobile labels. |
||
|
|
e32034039d |
poster cards: assert the preview's alt, which is the card's only name
Some checks are pending
build-gate / build (push) Waiting to run
MEASURED while sweeping my own gates for the count-only pattern: a poster card's
TEXT reads "116 UPG · Show on map" — a count and an action, no zone. The
identity lives entirely in the image's alt ("Northwest India — prayer poster…").
So the wall is CORRECT AS BUILT — all 27 previews carry a descriptive alt, and
sighted and screen-reader readers both learn which poster is which. No defect.
But that alt was doing load-bearing work nothing asserted: remove it and the
wall becomes 27 anonymous pictures for a screen-reader user while every
geometry, clipping, hover and reachability assertion here stays green.
★ I ALMOST ASSERTED THE WRONG THING. The obvious move on seeing "no zone name in
the card text" is to require one. That would have been me inventing a
requirement nobody stated — the same line I drew on the card-stock labels and
the UUPGs header. Measuring first showed the identity was already there, in the
attribute rather than the text, so the right assertion protects what exists
instead of mandating what I would have preferred.
MUTATION-TESTED: emptying the alt binding fails all 15 width x locale
combinations with "27 poster preview(s) have no alt text", exit 1. Restored,
gate green, SRI consistent.
|
||
|
|
a4c9c0a6c7 |
gates: refuse to measure a tree that is not this checkout
Some checks are pending
build-gate / build (push) Waiting to run
"Answers 200" and "is serving this build" are different questions, and my two browser gates only ever asked the first. Point either at a stale server and it measures the OLD build and reports PASS, with nothing in the output naming which tree it saw. ★ THAT IS NOT HYPOTHETICAL — I DID IT FOR SEVENTEEN TICKS. The published site was many merges behind while I ran these gates against it and reported the results as live verification. They were confident measurements of a tree that was not mine. The country-posters defect they eventually caught was only visible once the publish made live and local agree; before that, a stale copy would have passed either way and I would not have known the difference. bundler-2 found this class first (BUG-BROWSER-GATES-CAN-MEASURE-ANOTHER-WORKTREES-TREE: port 4173 on this box was held by a different worktree, and a gate reported three confident runs about code nobody had changed) and wrote _serves-this-tree.mjs, which hashes the served manifest.json against the one on disk. Eight gates use it; mine did not. This adds it to the two of mine that drive a browser. ⚠ NOT MEASURED, NOT SKIP, and the distinction carries the meaning. A SKIP reads as "nothing to check here". This is "I could not attribute what I saw", which is a different and louder fact. Exit 2, so a caller can tell it from a pass (0) and from a real failure (1). A useful side effect: these gates now refuse to measure live whenever the publish is stale, turning the silent failure mode I lived with into a loud one at the point of use. VERIFIED BOTH DIRECTIONS on both gates: against this checkout they measure and pass as before (15 width x locale combinations; 195 polys · 27/27 zones · 2107 pins on both leaves); against a host serving a different build they print NOT MEASURED and exit 2 instead of passing. The other 26 browser gates that lack this belong to bundler-2's chain, which owns them — reported, not converted. |
||
|
|
2e8f303a43 |
poster cards: measure the density the Driver asked about
Some checks are pending
build-gate / build (push) Waiting to run
The Driver asked for "around 4 per row on desktop, like Instagram". The grid is
ONE rule for every width -- repeat(auto-fill, minmax(210px,1fr)) -- so the count
falls out of the viewport rather than being chosen, and nothing measured it. The
card's own verify says so: "perRow is currently printed nowhere and asserted
nowhere. A number printed but not asserted on is decoration."
MEASURED, all five widths:
390px max 1/row @ 354px
768px max 3/row @ 224px
1024px max 4/row @ 225px <- already on target
1440px max 5/row @ 212px
1920px max 5/row @ 212px
The miss is only at 1440 and 1920, and the card width explains it: the grid
packs a fifth card by SHRINKING to 212px rather than growing toward 225px,
because auto-fill adds a column the moment 5x210 fits.
⚠ REPORTED, NOT ASSERTED, deliberately. The desktop target is settled (~4); the
MOBILE one is not -- 3 per row at 390px is a ~110px card, less than half the
224px that currently holds a readable print footer at 768px. Asserting a number
the Driver has not ruled on would encode my taste as a requirement. Measuring it
is what makes the ruling answerable, and it is the precondition for tuning:
you cannot tune toward a target you are not measuring.
★ IT IS THE MAX ACROSS ROWS, NOT THE FIRST ROW. Grouping every card by top edge
at 1440px gives [5,5,5,3,3,2,2,1,1] -- the short rows are the REMAINDERS of
collapsible sections, not a density failure. Reading the first row alone is
right here by luck and wrong on a wall whose first section holds one poster.
MUTATION-TESTED so the number cannot be decoration itself: widening the track to
minmax(260px,1fr) moves it to 1/2/3/4/4 per row at 354/351/309/273/273px. That
also prices wp1 -- 260px lands 1440 and 1920 on exactly 4 with BIGGER cards
(273px vs 212px), which is what "a bit bigger, more room for the footer" asked
for, at the cost of dropping 768px to 2/row. Which is why that card says "add a
desktop breakpoint" rather than "change the number". Not shipped: it changes
what readers see, and the card is gated.
|
||
|
|
b8d57be8d7 |
poster cards: assert a reader can actually REACH the last poster
Some checks are pending
build-gate / build (push) Waiting to run
Every assertion in this gate measured cards that are ALREADY laid out -- which is satisfied perfectly by a wall whose bottom half is unreachable. The Kingdom-agent ruled UPG-100 the one mobile-app showcase, promising an "Instagram-style scroll", and nothing asserted that the scroll works. MUTATION-TESTED, and the result is the argument. Capping the wall's height with overflow:hidden leaves every existing number perfect -- overflow 0, neighbour 0, over-preview 0, 108 controls, 0 too small, 0 clipped, at all five widths -- while 20 of 27 posters become unreachable. Only the new assertion fires: "scrolling to the bottom does NOT bring the last poster into view (scrolled 0 of 0)". A reader on the showcase would see the top of the wall and never learn the rest existed, with the whole suite green. ⚠ DO NOT ASK document.documentElement.scrollHeight. MEASURED at 390x844 on upg-100-mfe: it reports 844 -- exactly one viewport -- while the wall holds 13,032px of cards across two nested scrollers. The composite scrolls an INNER element, so the document is not the thing that moves, and a document-level check would call a healthy wall empty. It finds the element that genuinely overflows, picking the largest so a small inner scroller (the legend's own list) cannot be mistaken for the wall. It SCROLLS rather than computing. "The last card is at 13001px" is not the same claim as "scrolling brings it into view with working controls", and only the second one is what a reader gets. Having arrived, it also checks that card's controls are present, unclipped and pressable -- the far end of a long list is where a layout gives out, and every previous sample was taken near the top. Side effect worth noting: scrolling to the bottom triggers the lazy previews, so the 390px run now reports 17 painted rather than 8. The images were always fine; nothing had ever scrolled far enough to load them. |
||
|
|
a3729129b1 |
poster cards: the fits gate sweeps LOCALES, not just widths
Some checks are pending
build-gate / build (push) Waiting to run
The Driver's requirement was "full words visible AND support localization".
This gate only ever ran in English, which is the shortest of the eleven -- so
the half of the requirement about translation had never been measured.
MEASURED at 1440px, the three print labels:
en Prayer List 76px · Card Deck 72px · Poster Map 79px
ro Listă de rugăciune 122px · Pachet de cartonașe 133px · Harta afișului 92px
zh 禱告名單 56px · 禱告卡組 56px · 海报地图 56px
★ THE WORST CASE IS RENDERED WIDTH, NOT STRING LENGTH, and picking by the wrong
one would have picked the wrong locale. Chinese has the FEWEST characters of the
three and is nowhere near the widest, because each glyph is near double-width;
Romanian is +75% on English. The gate asserts scrollWidth > clientWidth, which
is pixels, so the sweep is pixels too. en is the control, ro and zh the measured
extremes; CARD_FIT_LANG overrides.
MUTATION-TESTED, and the result is the whole argument for this change.
Restoring the ellipsis the Driver rejected, at 1440px:
en 0 clipped <- passes
ro 54 clipped <- fails
zh 0 clipped <- passes
A regression that clips half the controls for Romanian readers would have
shipped completely green under the English-only gate. Restore the wrap and all
15 combinations pass.
Failures now name the locale ("ro 768px: 54 of 108 clipped"), because "768px"
alone is a report nobody can reproduce.
The PASS line reports widths x locales rather than their product -- it used to
say "at 15 width(s)" for 5 widths in 3 languages, and a summary that overstates
its own coverage is how a green line stops meaning anything.
Also verified, separately from the sweep: a PARTIAL translation degrades per key.
German renders translated (Gebetsliste, Kartenset, Posterkarte); an unknown
locale falls back to English; both with 0 blank labels, 0 raw i18n keys visible
and 0 clipped.
|
||
|
|
a7f57c117d |
poster cards: a blue outline on hover, and a gate that can see it
Some checks are pending
build-gate / build (push) Waiting to run
The card asks for the Pinterest/Instagram behaviour: hovering a poster outlines it in blue so a reader knows which one their click is about to act on. ★ THIS IS NOT A RESTORED BORDER. Several reports asked to bring back "the blue outline around the poster", and MEASURED 2026-09-24 there has never been one: all 136 elements painting a frame on this wall compute outline:none, and every border is grey (.card #e3e6ea, .main #dfe3e7). Nothing regressed -- the affordance was never built. The reports kept recurring because a hover state is invisible to a screenshot, so each check confirmed "no outline" and concluded the sighting was mistaken. outline, NOT border, and that is load-bearing. An outline paints OUTSIDE the box and takes no space, so it cannot move anything. The rest of this wall's gate asserts where the print row sits at five widths; a hover that grew a border would re-lay-out the card and push that row, so a cosmetic state could break a gate about print controls. :focus-within carries it to the keyboard, and @media (hover:none) drops the whole state on touch -- with no pointer it would either never appear or stick after a tap, leaving a card looking permanently selected. THE GATE NOW MOVES THE POINTER. Everything it measured before was of a loaded, untouched page, which is precisely why this affordance could go unbuilt while being repeatedly requested. ⚠ AND ONE OF MY OWN ASSERTIONS WAS DECORATION UNTIL I MUTATION-TESTED IT. I first asserted the card's OUTER width is unchanged by hover. It can't change: the card is a grid item, so the track fixes its width and a border grown on hover eats inward. MEASURED with border-width:4px on hover -- outer 224->224px, unfalsifiable, while the CONTENT box went 222->216px. It now asserts clientWidth, which is what actually repositions the print row. MUTATION-TESTED: remove the outline -> FAIL at all four pointer widths (exit 1); grow a border alongside it -> FAIL naming the content-box shift (exit 1); restore -> PASS (exit 0), reporting "hover 2px rgb(47, 111, 219)" at 768-1920 and "no outline (touch, correct)" at 390. The print/open controls the card also asks for are already there and were verified clickable in the same pass: Prayer List, Card Deck, the stock select and Poster Map, all four with full labels and real handlers, 0 clipped. |
||
|
|
608ef3dd56 |
poster cards: print options wrap to full words instead of an ellipsis
The Driver rejected the ellipsis by name: the print options must LINE WRAP so
every label stays readable, because a button that no longer says what it does
has not been fixed.
Three measurements drove the CSS:
- a <fieldset> takes min-width:min-content from the UA stylesheet, which
OVERRIDES width and max-width. .pfs read width:82% while computing 431px
inside a 212px card. min-width:0 goes on the fieldset ITSELF, not only on
its children.
- the row is anchored at its TOP (top:100%;margin-top:-14px), not its bottom.
At bottom:-14px every extra line grew UPWARD over the preview image, which
is how the first overflow fix broke the card it was meant to repair.
- wrapping took the row from 35px to 78px, so the grid's row gap moves 64px
to 88px. The old gap was tuned when the row was a single line.
No text-overflow anywhere in .pfs. min-width:0 alone had let three 76-79px
labels share 172px, showing 40px each.
The gate gains the assertion that would have caught the ellipsis: a control is
clipped when scrollWidth exceeds clientWidth. Mutation-tested -- restoring
text-overflow:ellipsis reproduces 81 of 108 clipped and exits 1. Without that
assertion the gate passed a wall whose buttons all read "Pra...".
Measured at 390/768/1024/1440/1920px: 0 clipped, 0 overflow, 0 over-preview,
0 neighbour intrusions, 108 controls.
|
||
|
|
25e3edec8f |
upg-100: no mapbox stylesheet under maplibre, and assert the preview exists
Some checks are pending
build-gate / build (push) Waiting to run
Two things, both found by other agents' reports rather than by my own gates. rega found upg-100-list.vue:1464 injecting mapbox-gl.css v3.3.0 into the shadow root unconditionally, so a keyless maplibre embed still pulled a stylesheet from api.mapbox.com. Token-free and harmless to render, but wrong-engine leakage on a page whose point is having no Mapbox dependency. Gated on isMaplibre, which already existed at line 119. This commit is ATOMIC by design: the fix, the rebuilt bundle, and the deletion of rega's MAPBOX_KNOWN exemption land together. Either half alone leaves a window — delete first and main red-gates on its own known bug; fix first and a stale exemption sits there hiding the next one. Proven before deleting, because the exemption is keyed on a URL STRING while the defect is a network REQUEST: the built bundle still contains the v3.3.0 literal (the constant remains; the guard stops the fetch), so a source-grep gate would have been misled. rega's gate measures requests, and it reported "provider=maplibre made 0 request(s) to api.mapbox.com" in BOTH js and iframe modes with no KNOWN line printed at all — i.e. the exemption was already dead code. Re-proven after deletion: still 0 requests, still PASS. The leaf-head preloads are explicitly NOT covered and the reason is recorded in the gate: mapbox-gl.css v3.24.0 and mapbox-gl-geocoder v5.0.0 are a different version and a different owner (generate-tree, bundler-1's lane), already tracked separately. Second: the Driver's report had two halves — "it is supposed to show the previews" and the print options overlapping. I fixed and gated the overlap and left the first unasserted. The over-preview check was written as `if (img)`, which means a card with NO preview satisfies it perfectly: nothing can overlap an image that is not there. The gate now counts previews. Mutation-tested by aborting every previews/** request: the product degrades well — all 27 cards swap to a "poster not generated yet" placeholder with no broken-image icons and no <img> at all, which is precisely why an image-health check cannot see it. So the fallback itself is asserted, and all-cards-placeholder now fails. A first version of that assertion was wrong and the reason is recorded in the code: it called 18 lazy-loaded previews broken. Every img carries loading=lazy, the first unpainted one sat 3926px down an 844px viewport, and no HTTP response failed. Deferred is not broken; it now judges only in-viewport images. |
||
|
|
b026c3ef83 |
poster cards: the print row belongs below the preview, and a fieldset ignores width
Some checks are pending
build-gate / build (push) Waiting to run
The Driver caught my own fix breaking the poster section, in the same session I landed it. flex-wrap wrapped the print row onto three lines; the row is anchored at the card BOTTOM, so growing it 35px to 78px pushed its TOP up from 216px to 173px in a 238px card — straight over the preview image and the outline. I traded a horizontal overflow for a vertical one. My gate measured only left and right edges, so it reported PASS on a visibly broken card. Same instruments-lie family the flock banked today: a green that measures the wrong thing. The real cause of the ORIGINAL overflow, which both the old CSS and my first fix missed: a <fieldset> gets min-width:min-content from the UA stylesheet, and that OVERRIDES width and max-width. The rule said width:82% while the element computed 431px inside a 212px card. That is why "it has a width, looks fine" survived two rounds of this bug — the declaration was there and inert. Fixed with min-width:0 on the fieldset itself, flex-wrap:nowrap, and flex:1 1 0 on the stock <select>, which sizes to its widest option and was taking 188px of a 198px row. Measured through all three states: 431x35 at top 216 overflowing, then 78px tall at top 173 covering the preview, now 172x35 at top 216 — one line, below the 184x184 preview, inside the 212px card. Clean at 390/768/1024/1440/1920px with all 108 controls full size. The gate now measures the vertical axis and sweeps five widths. Mutation-tested against my own regression: restoring flex-wrap fails with "27 print rows COVER the preview image (37px)" at four of the five widths. 390px stays clean, which is exactly why single-width testing would have missed it a second time. |
||
|
|
7ae1572c94 |
poster cards: the print-options row stays inside its own card
Some checks are pending
build-gate / build (push) Waiting to run
The repo-hygiene agent found this in a fresh 1440px screenshot, which is the whole lesson: .pfs already had width:82%, so reading the CSS said it was fine. Flex items default to min-width:auto and refuse to shrink below their content width, so four controls — Prayer List, Card Deck, the stock select, Poster Map — pushed the row open regardless of what the container declared. A constrained box does not constrain flex children, and only geometry shows that. Measured at 1440px on cap4-upg100-mfe: 27 of 27 cards overflowing, worst 110px past a 212px card, 36 intrusions into a neighbouring card's box. After flex-wrap:wrap plus min-width:0 on the children: 0 and 0, with all 108 print controls still a usable size. That second number is asserted deliberately. "Nothing overflows" is trivially satisfied by a row whose buttons collapsed to zero width, which would be a worse product than the overflow it replaced. check:poster-card-fits holds both, and is mutation-tested: reverting the fix reproduces the reporter's exact figures. Also adds check:backlog-refs — a card may not depend on a card that does not exist, because a dangling depends_on can never be done, so the card can never become ready and nothing says so. Its first run disproved the finding that motivated it. I had censused three dangling references, including the PRINCIPLE card the loop prompt lists as open work. The gate came back PASS, and chasing the discrepancy rather than assuming the gate was broken showed my census was wrong: it read only the builder backlog, and all three cards exist in 01-ideation-research. Cross-phase references are legitimate, and a single-backlog census structurally cannot see them. Reporting three dangling refs would have sent someone hunting for cards that were never lost. It fails only for depends_on on an OPEN card; a dangling parent is reported and passes, because failing on filing debt teaches people to delete the reference rather than restore the card. Both severities mutation-tested. |