From 2e8f303a434e8f772bee17f49e27b8926edc23bd Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:44:35 +0000 Subject: [PATCH] poster cards: measure the density the Driver asked about MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/check-poster-card-fits.mjs | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/internal/check-poster-card-fits.mjs b/internal/check-poster-card-fits.mjs index 914e6f398..426710210 100644 --- a/internal/check-poster-card-fits.mjs +++ b/internal/check-poster-card-fits.mjs @@ -222,10 +222,40 @@ async function measure(width, lang) { // what its buttons SAY has not been fixed. scrollWidth > clientWidth is the browser telling us // the text did not fit its box. const clipped = ctrls.filter((e) => e.scrollWidth > e.clientWidth + 1) + // ★ DENSITY: how many posters share a row, and how wide each is. + // 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 it: "perRow is + // currently printed nowhere and asserted nowhere. A number printed but not asserted on is + // decoration." + // ⚠ REPORTED, NOT YET ASSERTED, and that is deliberate. The desktop target is settled (~4) but + // the MOBILE one is not: 3 per row at 390px is a ~120px card, which cannot hold the wrapped + // print footer this same gate asserts is readable. Asserting a number the Driver has not + // ruled on would encode my taste as a requirement. Measuring it first is what makes the + // ruling answerable — and it is the precondition for tuning, since you cannot tune toward a + // target you are not measuring. + // Row-mates are found by TOP EDGE, not by index: a grid row is a set of boxes sharing a y, + // and counting "every Nth card" assumes the very layout under question. + // ⚠ IT IS THE MAX, AND THE WALL IS SECTIONED. Grouping every card by top edge at 1440px gives + // row sizes [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 would be right here by luck and wrong on + // a wall whose first section holds one poster. The max across all rows is the density the + // Driver's "around 4 per row" is about; cardW is reported beside it because the two move + // together and the width is what explains the count. + let perRow = 0, cardW = 0 + if (cards.length) { + const byTop = new Map() + for (const c of cards) { + const t = Math.round(c.getBoundingClientRect().top / 8) * 8 + byTop.set(t, (byTop.get(t) || 0) + 1) + } + perRow = Math.max(...byTop.values()) + cardW = Math.round(cards[0].getBoundingClientRect().width) + } return { cards: cards.length, rows: cards.filter((c) => c.querySelector('.pfs')).length, overflowing, worst, intrusions, coveringPreview, worstY, previewsPainted, previewsBroken, noPreview, clipped: clipped.length, - controls: ctrls.length, tiny: tiny.length } + controls: ctrls.length, tiny: tiny.length, perRow, cardW } })()`) await page.close() return { width, lang, hover, reach, ...out } @@ -247,6 +277,7 @@ for (const r of results) { + ` · over-preview ${r.coveringPreview}${r.worstY ? ` (${r.worstY}px)` : ''}` + ` · previews ${r.previewsPainted} painted` + `${r.previewsBroken ? `/${r.previewsBroken} BROKEN` : ''}${r.noPreview ? `/${r.noPreview} none` : ''}` + + ` · max ${r.perRow}/row @ ${r.cardW}px` + ` · ${r.controls} control(s), ${r.tiny} too small, ${r.clipped} clipped` + (r.reach ? ` · last card ${r.reach.lastInView ? 'reachable' : 'UNREACHABLE'}` : '') + (r.hover