Derive the gate's wheel point from the band, instead of hardcoding it
Some checks are pending
build-gate / build (push) Waiting to run
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:
parent
4bed933001
commit
56c3b2700d
1 changed files with 97 additions and 11 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue