From 6717402331dcf2006c7c682ad584297e775af7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 1 Sep 2026 07:29:08 +0100 Subject: [PATCH] the preview is as tall as the page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frame took all the room the panel had, so a short page left a band of white under the footer with the measure guides running down through it. An iframe has no height: fit-content, so the page is measured inside it and the frame follows. The wrapper, not the body — body is min-h-screen, so measuring it would ask the frame how tall the frame is. Co-Authored-By: Claude Opus 5 (1M context) --- web/SitePreview.tsx | 52 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/web/SitePreview.tsx b/web/SitePreview.tsx index a9cffef..cd24161 100644 --- a/web/SitePreview.tsx +++ b/web/SitePreview.tsx @@ -232,17 +232,53 @@ export const SitePreview = ({ /* - All the room there is. WIDTH is the device; height is just how much of the page you can see. + The page's own height, measured inside the frame. - It was capped at the device's own height, on the reasoning that a 390x844 phone drawn 1200 pixels - tall is not a phone. That was wrong, and the empty band under the preview was the tell: media - queries answer WIDTH, so the layout at 390 is the phone's layout whatever the height. A taller frame - shows more page, which is a scroll position rather than a different device. + ── Why it is measured and not just "the room" ── - One real consequence, and it is the right one: the footer is `min-h-dvh`, so it sits at the bottom - of the FRAME. A taller frame therefore puts it lower — exactly as a taller browser window does. + It was `available.height` — all the room the panel had — which is right for a page longer than the + panel and wrong for every other one: a short page left a band of white below the footer with the + measure guides running down through it. Nothing was broken; the frame was simply taller than the + document, and an iframe has no `height: fit-content`. + + ── The WRAPPER, not the body ── + + The site's `` is `min-h-screen`, so its height is the FRAME's height and measuring it would + ask the frame how tall the frame is. Its one child is the page — header, main, footer — and that is + a real content height. It stopped being `min-h-dvh` on the same day this changed, for the same + reason: the stretch belonged to nothing anybody could see or set. */ - const shownHeight = Math.max(0, available.height); + const [contentHeight, setContentHeight] = useState(0); + + useEffect(() => { + if (!loaded) return; + const frameDocument = frame.current?.contentDocument; + const page = frameDocument?.body.firstElementChild as HTMLElement | null; + const view = frameDocument?.defaultView; + if (!page || !view) return; + + const measure = () => setContentHeight(Math.ceil(page.getBoundingClientRect().height)); + measure(); + + /* + Observed, because the page changes height without reloading: choosing a spacing rewrites classes + in this document, and the device buttons reflow it at a new width. + + No feedback loop — the frame's height is set from this, and the wrapper's height does not depend + on the frame's. + */ + const observer = new view.ResizeObserver(measure); + observer.observe(page); + return () => observer.disconnect(); + }, [loaded]); + + /* + As tall as the page, or as tall as the room, whichever is less. + + A long page still fills the panel and scrolls, exactly as it did. WIDTH is the device and height is + not: media queries answer width, so the layout at 390 is the phone's layout whatever the height. + */ + const shownHeight = contentHeight ? Math.min(contentHeight, Math.max(0, available.height)) : Math.max(0, available.height); /* When the frame is wider than the room, show its MIDDLE.