From cee2f5c8e2aaabf7bcd14032031ef618f663dff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 1 Sep 2026 03:24:23 +0100 Subject: [PATCH] Desktop is the panel's width, and the guides are the only marking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things, both asked for after looking at it. The tint came out. Shading the margins was meant to say "the page does not use this", and against a white page it read as a blue background — a colour the site does not have, on the screen that exists to show the site's colours. The two hairlines say the same thing without claiming anything. And Desktop is fluid. `width: null` means the frame takes whatever the panel gives it, which is the honest answer for a device that has no width: a desktop is whatever window somebody opened. 1440 was a guess that then had to be centred and scrolled inside the panel — a page rendered at one width and looked at inside another, with dark room either side of it. Tablet and mobile stay pinned, because 834 and 390 are claims about real devices and pinning them is the only way the preview can answer "what does this look like on a phone". Fluid, the frame IS a desktop window: the site's media queries answer the panel's real width, and the measure guides mark where the content stops in the pixels you are actually looking at. Co-Authored-By: Claude Opus 5 (1M context) --- web/SitePreview.tsx | 23 +++++++++++++++++------ web/ViewMode.tsx | 17 +++++++++++++++-- web/canvas.ts | 31 ------------------------------- 3 files changed, 32 insertions(+), 39 deletions(-) diff --git a/web/SitePreview.tsx b/web/SitePreview.tsx index 24ad74f..a9cffef 100644 --- a/web/SitePreview.tsx +++ b/web/SitePreview.tsx @@ -102,7 +102,13 @@ export const SitePreview = ({ clientRef.current = client; const { device } = useViewMode(); - const { width } = DEVICES[device]; + /* + `null` means fluid — Desktop takes the panel's width. See DEVICES. + + Everything below reads `frame`: the real number once the panel has been measured. Until then it is + the panel's own width, which is what a fluid frame is anyway. + */ + const { width: deviceWidth } = DEVICES[device]; const [ready, setReady] = useState(false); const [failed, setFailed] = useState(null); @@ -123,6 +129,9 @@ export const SitePreview = ({ const box = useRef(null); const [available, setAvailable] = useState({ width: 0, height: 0 }); + // The width the frame is actually rendering at, for the caption and the centring maths. + const frameWidth = deviceWidth ?? Math.round(available.width); + useEffect(() => { const element = box.current; if (!element) return; @@ -250,8 +259,8 @@ export const SitePreview = ({ useEffect(() => { const element = scroller.current; if (!element) return; - element.scrollLeft = Math.max(0, (width - element.clientWidth) / 2); - }, [width, available.width, loaded]); + element.scrollLeft = Math.max(0, (frameWidth - element.clientWidth) / 2); + }, [frameWidth, available.width, loaded]); return (
@@ -273,7 +282,9 @@ export const SitePreview = ({ the platforms where scrollbars are always classic. */ className="mx-auto overflow-auto rounded-lg border border-border bg-white [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" - style={{ width: Math.min(width, available.width || width), height: shownHeight }} + // Fluid fills the panel; a fixed device is capped by it so a 1440 frame in a 900 panel + // scrolls rather than overflowing the workspace. + style={{ width: deviceWidth === null ? '100%' : Math.min(frameWidth, available.width || frameWidth), height: shownHeight }} > {message ? (

{message}

@@ -299,7 +310,7 @@ export const SitePreview = ({ title={`The site at /${path}`} className="border-0" // The device's real width, and the room's real height. No transform: see the note above. - style={{ width, height: shownHeight }} + style={{ width: deviceWidth === null ? '100%' : frameWidth, height: shownHeight }} /> )} @@ -308,7 +319,7 @@ export const SitePreview = ({ {/* Always, now that editing is the only mode. It says which server this is and at what width, which the device buttons show but the SERVER half of it — dev, not production — they do not. */}

- The development server, live at {width}px. Save to see a change here. + The development server, live at {frameWidth}px. Save to see a change here.

); diff --git a/web/ViewMode.tsx b/web/ViewMode.tsx index 512229b..a723d23 100644 --- a/web/ViewMode.tsx +++ b/web/ViewMode.tsx @@ -63,8 +63,21 @@ export type DeviceName = 'desktop' | 'tablet' | 'mobile'; * desktop 1440 — a common laptop, comfortably past the 1280 the content column caps at, so the * margins either side are visible rather than implied. */ -export const DEVICES: Record = { - desktop: { label: 'Desktop', width: 1440 }, +/** + * `width: null` means FLUID — the frame takes whatever the panel gives it. + * + * Desktop is fluid and the other two are not, which is the honest split. Tablet and mobile are claims + * about a real device: 834 and 390 are widths a page will actually be asked to render at, and pinning + * them is the only way the preview can answer "what does this look like on a phone". + * + * There is no such width for desktop. A desktop is whatever window somebody opened, so 1440 was a + * guess that then had to be scaled to fit the panel — a page rendered at one width and shown at + * another, with the measure guides landing wherever the scale put them. Fluid, the frame IS a desktop + * window: the site's media queries answer the panel's real width and the guides mark the measure in + * the pixels you are looking at. + */ +export const DEVICES: Record = { + desktop: { label: 'Desktop', width: null }, tablet: { label: 'Tablet', width: 834 }, mobile: { label: 'Mobile', width: 390 }, }; diff --git a/web/canvas.ts b/web/canvas.ts index c89e746..bf2ff5d 100644 --- a/web/canvas.ts +++ b/web/canvas.ts @@ -512,37 +512,6 @@ export function paintMeasure(document: Document): () => void { // lines against the edges would be noise claiming to be information. if (box.width >= (document.documentElement.clientWidth || 0) - 1) return; - /* - The margins THEMSELVES, not only the lines that bound them. - - Two hairlines say where the measure ends and leave the reader to notice that the space outside is - empty. Tinting it says what that space IS: room the page is not using, which is the question - somebody widening a gutter or judging a desktop layout is actually asking. On a narrow frame there - is nothing to tint and the block below returns before drawing any of it. - */ - for (const [side, from, width] of [ - ['left', 0, box.left], - ['right', box.right, (document.documentElement.clientWidth || 0) - box.right], - ] as const) { - if (width <= 0) continue; - const margin = document.createElement('div'); - margin.setAttribute(MARK, `measure-margin-${side}`); - style(margin, [ - 'position:fixed', - 'top:0', - 'bottom:0', - `left:${Math.round(from)}px`, - `width:${Math.round(width)}px`, - // Faint, because this is the part of the screen the page does NOT occupy: it has to read as - // absence. Anything stronger competes with the content for the eye. - `background:color-mix(in srgb, ${ACCENT} 7%, transparent)`, - 'pointer-events:none', - 'z-index:2147482999', - ]); - document.body.append(margin); - guides.push(margin); - } - for (const [side, x] of [ ['left', box.left], ['right', box.right],