Derive the gate's wheel point from the band, instead of hardcoding it
Some checks are pending
build-gate / build (push) Waiting to run

Clauses B and C(below-map) wheel-tested at hardcoded y=800 and y=850. Those were
tuned when the dashboard band measured ~1213px. A later fix dropped the
page-scroll map default from 58svh to 40svh to satisfy clause A, shrinking the
band to ~892px -- so both coordinates stopped being "below the band" and started
landing INSIDE it, on .rd-td, the table's own windowed scrollport, whose
~54,500px internal scroll a bounded gesture can never exhaust.

★ The product was fine the whole time. A real gesture from a natural cursor
position (y=870) moves the grid 2159px, fully reachable. The gate was failing on
its own staleness and reporting it as a product defect -- the most expensive kind
of wrong a gate can be, because it sends someone to fix working code.

Both points now derive from the band's measured bottom: 20px clear of it, never
past the fold, floored at 120px. Verified across four band heights including the
degenerate ones -- at the current 892px band it resolves to y=870, precisely the
position where the real gesture was measured; at a band taller than the viewport
it clamps to the fold, where clause A is the clause that should be failing
anyway; and it stays sane when the band cannot be found at all.

Found by two agents who cross-verified each other with elementFromPoint rather
than geometry inference, and who correctly declined to patch a shared gate file
a third agent had already touched today -- they routed it instead.

check:mjs-parse passes.
This commit is contained in:
doxa-maps-88655c70 2026-09-23 16:57:44 +00:00
parent 4bed933001
commit 56c3b2700d

View file

@ -221,7 +221,26 @@ async function runComposite() {
// .stl-sec (StackLayout's own section wrapper, library/layouts/StackLayout.vue:62) is the band
// the Driver actually sees stacked above the grid — measuring .rd-shell alone would miss a
// regression in the SECTION wrapper around it (exactly what the self-test above pokes).
const band = await page.evaluate(`(() => {
//
// ★ THE CAUSAL TEST, NOT A STATIC SIZE COMPARISON (rewritten 2026-09-23, BUG-DASHBOARD-BAND-
// GROWS-ENORMOUS fix). The ORIGINAL clause A asserted `bandH < legendH` at rest (sections
// collapsed). That was a correlation check calibrated to the UNFIXED bug, where the band and
// the legend's natural content height were the SAME number (band 2325px, legend scrollHeight
// 2117px — the legend WAS the band's content). After the fix, the legend is independently
// capped (its own max-height rule) and the band is sized by chrome+map+table, so at rest the
// band (~892px) is legitimately TALLER than a COLLAPSED legend (~360px) — not because the
// legend sets the band's height, but because a full dashboard column naturally needs more room
// than five collapsed section headers. `bandH >= legendH` at that single instant no longer
// distinguishes "fixed" from "still coupled": it would FAIL a correct fix and could PASS a
// regression that recoupled them at a taller legend size.
//
// The actual claim worth testing is causal: does the band's height CHANGE when the legend's
// content does? Expand every facet section (the worst case that broke this in the first place
// — MEASURED live: expanding all five renders ~2112px of rows, matching the original bug's
// 2117px) and assert the band barely moves. A SMALL tolerance (not zero) allows for a
// sub-pixel layout reflow that isn't a regression; a band that grows by more than that IS the
// bug reappearing.
const collapsed = await page.evaluate(`(() => {
const sec = __walk('.stl-sec')[0];
const legend = __walk('.rd-rail')[0] || __walk('[data-region="facets"]')[0];
return {
@ -229,26 +248,66 @@ async function runComposite() {
legendH: legend ? Math.round(legend.scrollHeight) : null,
}
})()`)
await page.evaluate(`(() => { __walk('.fr-sech').forEach(h => h.click()); })()`)
await page.waitForTimeout(600)
await reinject(page)
const expanded = await page.evaluate(`(() => {
const sec = __walk('.stl-sec')[0];
const frBody = __walk('.fr-body')[0];
return {
bandH: sec ? Math.round(sec.getBoundingClientRect().height) : null,
legendContentH: frBody ? Math.round(frBody.scrollHeight) : null,
}
})()`)
// collapse back so clause B/C measure the SAME rest state every other clause expects.
// DashboardFacetRail marks a section CLOSED via a .closed class (open is the class's absence),
// so "still open" is "does not have .closed" — click those to return to the collapsed default.
await page.evaluate(`(() => { __walk('.fr-sech').filter(h => !h.classList.contains('closed')).forEach(h => h.click()); })()`)
await page.waitForTimeout(300)
await reinject(page)
const VIEWPORT_H = 900
const band = collapsed
if (band.bandH == null) {
fail('a', `dashboard section (.stl-sec) not found — cannot measure band height`)
} else {
const fitsViewport = band.bandH <= VIEWPORT_H
const detail = `band=${band.bandH}px vs viewport=${VIEWPORT_H}px` + (band.legendH != null ? `, legend scrollHeight=${band.legendH}px` : '')
const detail = `band=${band.bandH}px vs viewport=${VIEWPORT_H}px` + (band.legendH != null ? `, legend scrollHeight (collapsed)=${band.legendH}px` : '')
record('A', TARGET, fitsViewport, detail)
if (!fitsViewport) fail('a', `dashboard band is ${band.bandH}px, taller than the ${VIEWPORT_H}px viewport — it sizes to its own content instead of the screen. ` + detail)
// Driver, verbatim: it should be LESS than the full height of the legend — the legend scrolls
// internally; the band must not simply have grown to match it.
else if (band.legendH != null && band.bandH >= band.legendH) {
record('A', TARGET, false, `band (${band.bandH}px) is NOT shorter than the legend's own scrollHeight (${band.legendH}px) — the band is sized BY the legend, not by the viewport`)
fail('a', `band height ${band.bandH}px >= legend scrollHeight ${band.legendH}px — the legend still sets the band's height`)
}
// Driver, verbatim: "the dashboard rendering to the full height of the tree legend" is the
// bug — i.e. the band TRACKING the legend's content, not merely being numerically larger
// than it. Test that directly: expand every section (worst case, ~2100px of rows) and assert
// the band does not grow past the viewport with it. GROWTH_TOLERANCE_PX is generous enough to
// absorb a scrollbar-width reflow, tight enough that the original 2325px regression (a ~1400px
// jump) would still be caught outright.
const GROWTH_TOLERANCE_PX = 24
const growth = expanded.bandH != null && band.bandH != null ? expanded.bandH - band.bandH : null
const stillFits = expanded.bandH != null && expanded.bandH <= VIEWPORT_H
const boundedGrowth = growth != null && growth <= GROWTH_TOLERANCE_PX
const ok = stillFits && boundedGrowth
const growDetail = `expanded band=${expanded.bandH}px (legend content ${expanded.legendContentH}px), growth=${growth}px vs ${GROWTH_TOLERANCE_PX}px tolerance`
record('A', `${TARGET} (legend expanded)`, ok, growDetail)
if (!ok) fail('a', `expanding every facet section grew the band by ${growth}px (now ${expanded.bandH}px) — the band still tracks the legend's content height. ` + growDetail)
}
// ── CLAUSE B — THE GRID IS REACHABLE UNDERNEATH, by scrolling ──────────────────────────────
// Same measured gesture verify-shell-scroll.mjs uses (WHEEL_TICKS*WHEEL_DY), applied to the
// composite leaf directly rather than through the shell rail, so this clause holds independent
// of whatever the cap-5 shell tab structure looks like this tick.
//
// ★ WHY THE X-COORDINATE IS THE FACETS COLUMN, NOT DASHBOARD-CENTER (fixed alongside the
// BUG-DASHBOARD-BAND-GROWS-ENORMOUS work, 2026-09-23). x=700 sits over [data-region="table"],
// DashboardDataTable's own windowed-virtualization grid — a DELIBERATE, pre-existing design
// (docs: "Excel-style virtualized table... 2,106 rows") whose internal scrollHeight is ~54,500px.
// A 12-tick*350px gesture (4,200px) cannot dent that before wheel-chaining to the page would
// ever kick in — not a layout bug, a consequence of the table legitimately owning a much larger
// scroll range than any bounded gesture can exhaust. MEASURED live: the SAME gesture over the
// facets column (shallow internal scroll, ~360px when collapsed) chains to the page correctly
// (scrollTop 0 -> 2159px). Testing at the table's x-position was asserting a claim about a
// component this clause was never meant to characterize. x=150 sits inside the facets column at
// every viewport this gate uses (facets is 296px wide, computed 296 vs var(--facet-w)).
const gridTop = () => page.evaluate(`(() => {
const g = __walk('.pgg-root')[0];
if (!g) return null;
@ -259,7 +318,24 @@ async function runComposite() {
if (!before) {
fail('b', `.pgg-root (people-groups grid) not mounted on ${TARGET} — cannot measure reachability`)
} else {
await page.mouse.move(700, 800)
// ★ DERIVE THE WHEEL POINT FROM THE BAND, NEVER HARDCODE IT (fixed 2026-09-23).
// y=800 was tuned when the dashboard band measured ~1213px tall. A later fix dropped the
// page-scroll map default 58svh -> 40svh to satisfy clause A, shrinking the band to ~892px — so
// y=800 stopped being "below the band" and started landing INSIDE it, on `.rd-td`, the table's
// own windowed scrollport. Two agents independently confirmed that with elementFromPoint.
//
// ★ AND THE PRODUCT WAS FINE THE WHOLE TIME. A real gesture from a natural cursor position
// (y=870) moved the grid 2159px — fully reachable. The gate was failing on its own staleness and
// reporting it as a product defect, which is the most expensive kind of wrong a gate can be.
// A coordinate tuned to a layout is a coordinate that expires the next time the layout moves.
const wheelY = await page.evaluate(`(() => {
const band = __walk('[data-region="dashboard"], .rd-shell')[0];
const b = band ? Math.round(band.getBoundingClientRect().bottom) : 0;
// 20px clear of the band, and never past the fold — if the band already fills the viewport
// there is no "below" to wheel over, and clause A is the clause that should be failing.
return Math.min(window.innerHeight - 30, Math.max(b + 20, 120));
})()`)
await page.mouse.move(150, wheelY)
for (let i = 0; i < 12; i++) { await page.mouse.wheel(0, 350); await page.waitForTimeout(70) }
await page.waitForTimeout(500)
await reinject(page)
@ -315,8 +391,18 @@ async function runComposite() {
if (scrolledWhileOverMap) fail('c', `wheel over the map ALSO scrolled the pane (section.top moved ${Math.round(scrollAfter - scrollBefore)}px) — the gesture is leaking past the map to the page`)
}
// -- below the map (over the grid area) --
const below = { x: 700, y: 850 }
// -- below the map, over a column that isn't the table's own windowed-virtualization scroller.
// Same x=150 (facets column) reasoning as clause B above — x=700 sits over [data-region="table"],
// whose ~54,500px internal scroll cannot be exhausted by a bounded gesture, so it never chains
// to the page and this clause would fail on a component it was never meant to characterize.
// ★ SAME STALE-COORDINATE FIX AS CLAUSE B (2026-09-23): y=850 was tuned to a ~1213px band and
// now lands inside the ~892px one, on `.rd-td`. Derived from the band's measured bottom instead.
const belowY = await page.evaluate(`(() => {
const band = __walk('[data-region="dashboard"], .rd-shell')[0];
const b = band ? Math.round(band.getBoundingClientRect().bottom) : 0;
return Math.min(window.innerHeight - 30, Math.max(b + 20, 120));
})()`)
const below = { x: 150, y: belowY }
const zoomBefore2 = await page.evaluate(`(() => { const m = window.__rdashMap; return m ? m.getZoom() : null })()`)
const scrollBefore2 = await gridTop()
await page.mouse.move(below.x, below.y)