Commit graph

5 commits

Author SHA1 Message Date
doxa-maps-88655c70
0f0bebd9c2 Serve locally the way production serves, so a bad URL fails here instead of live
bun run serve was python3 -m http.server, which resolves a directory request to
its index.html. The live static layer returns 404 for the same request. So a gate
could pass locally on a URL that does not work in production, and nothing local
could ever catch it.

That is not a harness quirk, it is a blind spot with a green light on it: every
defect inside the permissive gap passes locally by construction and can only be
found in production, which is the worst place to find it. It cost a false
"the publish failed, zero filters live" reading on a publish that was
byte-for-byte correct.

serve-strict.mjs returns 404 for a directory, exactly like production, with no
index fallback and no directory listing. It also refuses to read outside its
root. Loopback only, inherited from the script it replaces, whose own comment
records that the bind was once 0.0.0.0 and answered on the box's public IP while
a gate was running. The permissive server is kept as serve:permissive rather
than deleted, so the difference can be demonstrated.

It earned itself immediately: verify-shell-scroll held two bare-directory URLs
and failed the first time it ran against the strict server. Both now ask for
index.html, which is the form the manifest already ships for all 28 profiles,
and the gate passes again -- both wheel gestures moving the grid 1500px onto
screen.

Fixing the server rather than the URLs is the point. The repo was already
production-correct: 28 of 28 manifest URLs and 82 gate references use
index.html. What was missing was anything that could fail when one did not. A
rule you have to remember fails under time pressure; a server that returns 404
does not.
2026-09-23 03:09:25 +00:00
doxa-maps-88655c70
285ba39c31 Stop the data table from swallowing the wheel, and grade the gesture honestly
The Driver could not scroll down to the people-groups grid. Three separate
diagnoses blamed the map, including one of mine that shipped. Measured in the
cap-5 shell: a ten-tick wheel with the cursor over the dashboard moved the outer
pane 300px while .rd-grid's own scrollTop went 0 to 2606. The wheel was being
consumed by the table -- a virtualized grid whose scrollHeight is 54834px inside
a ~207px window. That is ordinary scroll-chaining: a nested scrollport eats wheel
events until it reaches its own end, and with 54 thousand pixels of table it
never reaches the end. The Mapbox canvas sits above the cursor and was never
involved.

overscroll-behavior:contain does not stop the table scrolling; it stops it
chaining, so the remainder now reaches the pane.

The gate that should have caught this had two defects of its own. It measured the
gesture the Driver makes and then asserted on a different number, so it passed at
2611px while his gesture moved 300. When that was fixed the threshold was written
as "moved less than 200px" -- a figure picked without comparing it to the value
already measured as broken, which was 300, so it still passed. The threshold is
now derived from a measured baseline: the same gesture performed lower down the
page, where it is known to work. Two identical gestures on one surface should
move it comparably, and there is no constant left to rot.
2026-09-23 00:31:35 +00:00
doxa-maps-88655c70
2ec6c00e68 Stop the map from swallowing the wheel, and grade the gesture the user makes
syncScrollZoomToHost decided whether Mapbox should keep scrollZoom by sampling
the layout at 0, 1200 and 4000ms and then never asking again. That is a race,
not a sync: the cap-5 dashboard tab lazy-mounts on activation, so at every one
of those instants the scrollport ancestor had not reached its final height. The
map kept scrollZoom enabled and ate the wheel. Wheeling over the map moved the
grid 300px; wheeling below it moved 2611px. The cursor is over the map, so the
people-groups grid under the dashboard was unreachable.

The condition is layout-dependent, so observe layout instead of guessing when it
settles: a ResizeObserver on the map container, the scrollport that owns the page
scroll, and body, plus a resize listener. Every event that can flip the answer now
re-asks it.

verify-shell-scroll already measured this. It computed movedByMap for the gesture
over the map, then asserted only on movedTotal, which sums in a second gesture
below the map -- so it reported PASS at 2611px while the real gesture moved 300px.
It now asserts on the number it was built to measure. Worth remembering as its own
failure family: a gate that measures the right thing and grades a different
variable, which fails honestly but never on the condition it exists for.
2026-09-22 22:06:59 +00:00
doxa-maps-88655c70
762953a054 Land the swarm's dashboard work: colour, rails, cascade, scroll
Some checks are pending
build-gate / build (push) Waiting to run
Sixteen source files from five agents, all verified against source gates
before commit: template-literal, sfc-parse, kpi-unit, composite-reg,
facet-cascade, one-palette, script-flags, no-public-binds, no-orphan-gates and
quoted-scripts all pass. public-hygiene reports 0 findings.

Colour. The choropleth ramp is now derived by interpolating the product's own
tokens (surface #f3f3f1 to accent #92b195) rather than adding a sixth grey, and
that fixes a pincer the earlier research had not named: on the old grey-to-black
ramp the navy dot and the white ring failed at OPPOSITE ends, so no fixed pin
colour could win. Worst-case dot contrast moves 1.13 to 7.29. Second-order
breaks fixed with it -- the country outline would have measured 1.08-1.82 as
white on the pale fill, so it is #6d766e at 2.59-4.34, and the legend swatch
border.

Religion colours no longer key by array index. colorFor takes no index at all;
colour comes from the registry by facet plus value. All 31 distinct live
religion labels now match the map, 0 mismatches. Tree-rail grey rows 142 to 0
against the live 2,107-row API.

Cascade. Clearing a parent facet now clears its descendants, in the selection
bus rather than in any component, because every clear path already funnels
through setFilter/clearFilters -- a component fix would have fixed one button
and left four. The hierarchy is derived from config.facetRail[].drill, so it
cannot drift from the tiers the rail renders. Place turned out to be three deep
(region, block, country), not two.

Rails. Tree rows wrapping 18/66 to 3/66; the tree rail is now the wider of the
two at 296 vs 264, with the map cell unchanged because both pairs sum to 560.

Also lands the shell scroll fix and the facet-cascade gate with its committed
fixture -- 73 sampled rows, no network, so the assertion that reproduces the
empty map runs on a fresh clone instead of silently skipping.
2026-09-22 18:57:39 +00:00
doxa-maps-88655c70
b2fc0e4dda Make the shell-scroll gate runnable, and catch orphan gates
Some checks are pending
build-gate / build (push) Waiting to run
verify-shell-scroll.mjs was written and negative-tested, then left with no
package.json entry and no importer, so nothing could ever run it again. It
also defaulted to localhost:8899 -- an ad-hoc port from an earlier session --
so it failed with ERR_CONNECTION_REFUSED for anyone else and read as a broken
gate rather than a missing server. Now check:shell-scroll, defaulting to
BASE_URL or 127.0.0.1:4173 like every other browser gate, and listed in
verify-browser-all's GATES array.

check:no-orphan-gates fails any gate-shaped file in internal/ that nothing can
invoke. The repo carded this once already; it recurs because writing a gate
and registering one are separate acts and only the first feels like work.

Not every unreferenced file is a lost gate, so the rule is not "everything
must be wired": theme-flash-probe is a dated diagnostic for two fixed bugs,
worth keeping as a reproduction recipe. It now carries an @one-shot tag saying
so, which makes the author state which kind of thing they wrote.

Two self-referential defects in the first version of that gate, both from
reading evidence out of documentation:

- it cited theme-flash-probe.mjs in its own header as an example, then grepped
  for importers, found itself, and declared the orphan reachable. The gate
  written to catch orphans made an orphan pass by mentioning it. A gate must
  never be its own witness, so it now excludes its own file.
- it matched @one-shot anywhere in prose, including its own paragraph
  explaining the tag, and reported "1 tagged" when nothing had been tagged.
  The tag must now be a marker at line start.

Negative-tested: removing check:shell-scroll from package.json fails by name.

The scroll fix itself is NOT done. contain:layout and .vtl-host overflow-y are
confirmed in the built bundle, and a real wheel gesture still moves the grid
0px with 2586px mounted and unreachable. The gate is what caught that; the
diff looked correct.
2026-09-22 17:55:43 +00:00