poster cards: assert a reader can actually REACH the last poster
Some checks are pending
build-gate / build (push) Waiting to run
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.
This commit is contained in:
parent
1c7384ee39
commit
b8d57be8d7
1 changed files with 64 additions and 1 deletions
|
|
@ -118,6 +118,50 @@ async function measure(width, lang) {
|
|||
}
|
||||
} catch (_) { /* hover is an enhancement; never let it break the geometry run */ }
|
||||
|
||||
// ★ CAN A READER ACTUALLY REACH THE LAST POSTER? Everything else here measures 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 it.
|
||||
//
|
||||
// ⚠ 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. 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. Find the element that actually overflows.
|
||||
//
|
||||
// It SCROLLS rather than computing: geometry saying "the last card is at 13001px" is not the
|
||||
// same claim as "scrolling brings it into view with working controls".
|
||||
let reach = null
|
||||
try {
|
||||
reach = await page.evaluate(`(async () => {
|
||||
const all = (${WALK})(document)
|
||||
const vis = (e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 }
|
||||
const cards = all.filter((e) => (e.className||'').toString().split(' ').includes('card')).filter(vis)
|
||||
if (!cards.length) return null
|
||||
const last = cards[cards.length - 1]
|
||||
// The scroller is whichever ancestor-ish element genuinely overflows. Picking the LARGEST
|
||||
// overflow avoids matching a small inner scroller (the legend's own list, say).
|
||||
const scrollers = all.filter((e) => {
|
||||
const cs = getComputedStyle(e)
|
||||
return /(auto|scroll)/.test(cs.overflowY) && e.scrollHeight > e.clientHeight + 8
|
||||
}).sort((a, b) => (b.scrollHeight - b.clientHeight) - (a.scrollHeight - a.clientHeight))
|
||||
const sc = scrollers[0]
|
||||
if (sc) { sc.scrollTop = sc.scrollHeight } else { window.scrollTo(0, document.body.scrollHeight) }
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
const lb = last.getBoundingClientRect()
|
||||
const ctrls = [...last.querySelectorAll('.pbtn,.pstock')]
|
||||
return {
|
||||
scrolled: sc ? Math.round(sc.scrollTop) : Math.round(window.scrollY),
|
||||
maxScroll: sc ? sc.scrollHeight - sc.clientHeight : 0,
|
||||
lastInView: lb.top < window.innerHeight && lb.bottom > 0,
|
||||
lastControls: ctrls.length,
|
||||
lastClipped: ctrls.filter((e) => e.scrollWidth > e.clientWidth + 1).length,
|
||||
lastUnusable: ctrls.filter((e) => {
|
||||
const r = e.getBoundingClientRect(); return r.width < 18 || r.height < 10
|
||||
}).length,
|
||||
}
|
||||
})()`)
|
||||
} catch (_) { /* reachability is additive; never let it break the geometry run */ }
|
||||
|
||||
const out = await page.evaluate(`(() => {
|
||||
const all = (${WALK})(document)
|
||||
const vis = (e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 }
|
||||
|
|
@ -184,7 +228,7 @@ async function measure(width, lang) {
|
|||
controls: ctrls.length, tiny: tiny.length }
|
||||
})()`)
|
||||
await page.close()
|
||||
return { width, lang, hover, ...out }
|
||||
return { width, lang, hover, reach, ...out }
|
||||
}
|
||||
|
||||
const results = []
|
||||
|
|
@ -204,6 +248,7 @@ for (const r of results) {
|
|||
+ ` · previews ${r.previewsPainted} painted`
|
||||
+ `${r.previewsBroken ? `/${r.previewsBroken} BROKEN` : ''}${r.noPreview ? `/${r.noPreview} none` : ''}`
|
||||
+ ` · ${r.controls} control(s), ${r.tiny} too small, ${r.clipped} clipped`
|
||||
+ (r.reach ? ` · last card ${r.reach.lastInView ? 'reachable' : 'UNREACHABLE'}` : '')
|
||||
+ (r.hover
|
||||
? ` · hover ${r.hover.after.outlineStyle === 'none'
|
||||
? 'no outline' + (r.width < 500 ? ' (touch, correct)' : '')
|
||||
|
|
@ -262,6 +307,24 @@ for (const r of results) {
|
|||
// HOVER: the card must answer the pointer, and must not move when it does.
|
||||
// Only asserted where a pointer exists — a touch viewport has `@media (hover:none)` suppressing
|
||||
// the state deliberately, so demanding an outline there would fail the correct behaviour.
|
||||
// REACHABILITY: the LAST card must be reachable, and still work when you get there.
|
||||
if (r.reach) {
|
||||
if (!r.reach.lastInView) {
|
||||
fails.push(`${at}: scrolling to the bottom does NOT bring the last poster into view `
|
||||
+ `(scrolled ${r.reach.scrolled} of ${r.reach.maxScroll}) — every geometry assertion above `
|
||||
+ 'is satisfied by a wall whose bottom half a reader can never get to')
|
||||
}
|
||||
if (r.reach.lastControls === 0) {
|
||||
fails.push(`${at}: the last poster has NO print controls — the wall degrades toward the end`)
|
||||
}
|
||||
if (r.reach.lastClipped) {
|
||||
fails.push(`${at}: ${r.reach.lastClipped} control(s) on the LAST poster are clipped — the `
|
||||
+ 'sampled cards near the top fit while the far end does not')
|
||||
}
|
||||
if (r.reach.lastUnusable) {
|
||||
fails.push(`${at}: ${r.reach.lastUnusable} control(s) on the last poster are too small to press`)
|
||||
}
|
||||
}
|
||||
if (r.hover) {
|
||||
const { before, after } = r.hover
|
||||
const pointer = r.width >= 500
|
||||
|
|
|
|||
Loading…
Reference in a new issue