From 6098ae22854cebd2803ba8520fc6d950f81f9ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 2 Sep 2026 16:23:05 +0100 Subject: [PATCH] the preview measures the page with its minimum height off The site's shell is min-h-dvh so a short page puts its footer at the bottom of the window. Measuring it then asks the frame how tall the frame is, and the answer is whatever it already was. So the minimum is neutralised by a stylesheet injected into the frame, and what is left is the height of what is on the page. The cost, stated: the preview does not show a footer pushed to the bottom of a window, because it is not a window. It shows the page ending where it ends. Co-Authored-By: Claude Opus 5 (1M context) --- web/SitePreview.tsx | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/web/SitePreview.tsx b/web/SitePreview.tsx index 336b1be..08c91ad 100644 --- a/web/SitePreview.tsx +++ b/web/SitePreview.tsx @@ -271,22 +271,36 @@ export const SitePreview = ({ 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 WRAPPER, with its minimum height taken off ── - 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. + The site's `` is `min-h-screen` and the page shell inside it is `min-h-dvh`, so measuring + either asks the frame how tall the frame is and the answer is whatever it already was. Both are + right on a real page — a short one should put its footer at the bottom of the window — and both + make this circular. + + So the minimum is neutralised in the frame before measuring, by a stylesheet this injects. What is + left is the height of what is actually on the page, which is what the panel should be. + + The cost, stated: the preview does not show the footer pushed to the bottom of a window, because it + is not a window. It shows the page ending where the page ends. */ const [contentHeight, setContentHeight] = useState(0); useEffect(() => { if (!loaded) return; const frameDocument = frame.current?.contentDocument; - const page = frameDocument?.body.firstElementChild as HTMLElement | null; + const page = frameDocument?.querySelector('[data-page-shell]') ?? (frameDocument?.body.firstElementChild as HTMLElement | null); const view = frameDocument?.defaultView; if (!page || !view) return; + /* + Injected rather than set inline: `min-h-dvh` is a class, and an inline `min-height` would have to + win against it by specificity. A stylesheet of our own, last in the document, does. + */ + const relax = frameDocument!.createElement('style'); + relax.textContent = '[data-page-shell]{min-height:0!important}body{min-height:0!important}'; + frameDocument!.head.append(relax); + const measure = () => setContentHeight(Math.ceil(page.getBoundingClientRect().height)); measure(); @@ -299,7 +313,10 @@ export const SitePreview = ({ */ const observer = new view.ResizeObserver(measure); observer.observe(page); - return () => observer.disconnect(); + return () => { + observer.disconnect(); + relax.remove(); + }; }, [loaded]); /*