From 7fe1901703590f59b68f6f394038c628297bd9c4 Mon Sep 17 00:00:00 2001 From: Timmy Date: Wed, 28 Oct 2026 12:47:04 +0000 Subject: [PATCH] fix(demo): make the on-camera caption bar opaque and prove its last line is not clipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release-delta caption is paragraph length. Rendered at 94% opacity over the running app, the app's own text bled through it, and the final line sat flush against the bar's bottom edge — the one caption a reviewer most needs to read was the hardest one to read. The bar is now opaque, long copy steps down to 10.5/9.5px, and caption() measures the last line box with a Range and fails the run when the bottom padding is under 3px, so a cramped caption can no longer be recorded as proof. --- scripts/record_release_demo.mjs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/record_release_demo.mjs b/scripts/record_release_demo.mjs index d4c35e2..f4087ff 100644 --- a/scripts/record_release_demo.mjs +++ b/scripts/record_release_demo.mjs @@ -136,9 +136,13 @@ async function caption(text, duration = 1400) { node.textContent = text; // Inline styles, not a stylesheet: a stylesheet belongs to one document and the demo reloads the // page, which silently stripped caption styling and left every later caption clipped body text. + // The bar is opaque, not 94%: a translucent bar let the app's own text show through a long + // release-delta caption and read as run-together garbage. Long copy also steps down a size so a + // paragraph-length caption keeps its bottom padding instead of pressing the last line to the edge. + const size = text.length > 380 ? 9.5 : text.length > 220 ? 10.5 : 12; node.style.cssText = 'position:fixed;left:16px;right:16px;top:14px;z-index:20000;' - + 'background:rgba(40,33,29,.94);color:#fff;padding:11px 14px;border-radius:14px;' - + 'font:800 12px/1.25 system-ui;letter-spacing:.02em;text-align:center;' + + 'background:#28211d;color:#fff;padding:12px 14px 14px;border-radius:14px;' + + `font:800 ${size}px/1.32 system-ui;letter-spacing:.02em;text-align:center;` + 'box-shadow:0 8px 24px rgba(0,0,0,.2)'; document.body.append(node); node.animate([{ opacity: 0, transform: 'translateY(-8px)' }, { opacity: 1, transform: 'translateY(0)' }], { duration: 260, fill: 'forwards' }); @@ -152,18 +156,27 @@ async function caption(text, duration = 1400) { const rect = node.getBoundingClientRect(); const style = getComputedStyle(node); const topmost = document.elementFromPoint(Math.round(rect.left + rect.width / 2), Math.round(rect.top + rect.height / 2)); + // Proving the string was pushed is not proving it was readable. Measure the last line's own box, + // so a paragraph caption pressed flat against the bar's bottom edge fails here instead of shipping. + const range = document.createRange(); + range.selectNodeContents(node); + const lines = Array.from(range.getClientRects()).filter(box => box.height > 1); + const lastBottom = lines.length ? Math.max(...lines.map(box => box.bottom)) : rect.bottom; + const padBottom = Math.round(rect.bottom - lastBottom); return { ok: style.position === 'fixed' && style.zIndex === '20000' && rect.top <= 40 && rect.height >= 24 - && rect.width > 200 && rect.bottom <= window.innerHeight && (topmost === node || (topmost && node.contains(topmost))), + && rect.width > 200 && rect.bottom <= window.innerHeight && (topmost === node || (topmost && node.contains(topmost))) + && lines.length > 0 && padBottom >= 3, top: Math.round(rect.top), height: Math.round(rect.height), position: style.position, zIndex: style.zIndex, background: style.backgroundColor, topmost: topmost === node, + padBottom, lines: lines.length, }; }); const shownBand = await captionBand().catch(() => null); const bandChanged = Boolean(blankBand && shownBand && !blankBand.equals(shownBand)); if (!painted.ok) throw new Error(`Caption is not an on-screen bar: ${text} ${JSON.stringify(painted)}`); if (!bandChanged) throw new Error(`Caption painted nothing into the caption band: ${text}`); - captionEvidence.push({ text, visible: true, position: painted.position, top: painted.top, height: painted.height, bandChanged }); + captionEvidence.push({ text, visible: true, position: painted.position, top: painted.top, height: painted.height, bandChanged, padBottom: painted.padBottom, lines: painted.lines }); await sleep(duration); // A caption must not outlive its stated duration. It used to stay on screen until the next one // replaced it, so a view change left the previous screen's claim hanging over a different screen —