the preview takes the room, the controls take what they need
Edit put a fixed 420px band above a scrolling column, so the preview was a letterbox in the mode where changes are actually being made — and there was empty panel below the controls while the thing being judged was squeezed. One column now, for both modes, and it never scrolls as a whole. The preview is flex-1, so it is always as tall as whatever is left. The controls are shrink-0, so they are exactly as tall as their content and no taller. Edit takes height from the preview and only as much as it needs. Capped at 55% with their own scroll, because shrink-0 alone would let a short window hand the controls everything and leave the preview at nothing — the same failure in the other direction. The frame is also capped at the device's real height. A 390x844 phone in a 1200px panel would otherwise be drawn as a phone 1200 pixels tall, which is not a phone. Past that it stops growing and the space stays empty, which is honest: there is no device that shape. Two modes were two return paths; now switching modes hides a block rather than rendering a different component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,24 +158,21 @@ export const LayoutPanel = () => {
|
||||
if (!draft) return <div className="p-4 text-xs text-muted-foreground">{error ?? 'Loading…'}</div>;
|
||||
|
||||
/*
|
||||
Viewing: the preview and nothing else, filling the panel.
|
||||
ONE layout for both modes: the preview takes the room, the controls take what they need.
|
||||
|
||||
Not the same thing as hiding the controls. The preview is given the whole height, which is what
|
||||
makes View worth having — at edit height a phone is a letterbox, and a phone is the width somebody
|
||||
most wants to look at properly.
|
||||
It used to be two — View returned the preview alone, and Edit put a fixed 420px band above a
|
||||
scrolling column. That made the preview a letterbox in the mode where you are actually changing
|
||||
things, with empty panel below the controls, and it meant switching modes was a different component
|
||||
rather than a smaller one.
|
||||
|
||||
Now the column fills the panel and never scrolls as a whole. The preview is `flex-1`, so it is
|
||||
always as tall as whatever is left; the controls are `shrink-0`, so they are exactly as tall as
|
||||
their content and no taller. Edit takes height from the preview, and only as much as it needs.
|
||||
*/
|
||||
if (mode === 'view') {
|
||||
return (
|
||||
<div className="flex size-full flex-col p-4">
|
||||
<LayoutPreview port={devPort} fill />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col gap-4 overflow-auto p-4">
|
||||
{saved && <p className="text-xs text-success">Saved</p>}
|
||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||
<div className="flex size-full flex-col gap-3 p-4">
|
||||
{saved && <p className="shrink-0 text-xs text-success">Saved</p>}
|
||||
{error && <p className="shrink-0 text-xs text-destructive">{error}</p>}
|
||||
|
||||
{/*
|
||||
The REAL page, embedded — not a drawing of it.
|
||||
@@ -187,6 +184,16 @@ export const LayoutPanel = () => {
|
||||
*/}
|
||||
<LayoutPreview port={devPort} />
|
||||
|
||||
{/*
|
||||
The controls, and only in Edit.
|
||||
|
||||
`shrink-0` so they never give up height to the preview, `max-h-[55%]` so they can never take
|
||||
the whole panel either — on a short window the preview would otherwise be squeezed to nothing,
|
||||
which is the failure this whole change is about. Past that they scroll on their own rather than
|
||||
scrolling the preview off the top.
|
||||
*/}
|
||||
{mode === 'edit' && (
|
||||
<div className="flex max-h-[55%] shrink-0 flex-col gap-4 overflow-auto">
|
||||
{/* ── Colours ──────────────────────────────────────────────────────────────────────────── */}
|
||||
<section className="flex flex-col gap-3 rounded-lg border border-border p-4">
|
||||
<h3 className="text-sm font-medium">Colours</h3>
|
||||
@@ -250,6 +257,8 @@ export const LayoutPanel = () => {
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -273,7 +282,7 @@ export const LayoutPanel = () => {
|
||||
* was the other option and it is a guess about where the browser kept it; the platform tried that and
|
||||
* says so in its own comments.
|
||||
*/
|
||||
const LayoutPreview = ({ port, fill }: { port: number | null; fill?: boolean }) => {
|
||||
const LayoutPreview = ({ port }: { port: number | null }) => {
|
||||
const client = useClient();
|
||||
/*
|
||||
Through a ref, and this is the whole reason the preview hung.
|
||||
@@ -289,7 +298,7 @@ const LayoutPreview = ({ port, fill }: { port: number | null; fill?: boolean })
|
||||
const clientRef = useRef(client);
|
||||
clientRef.current = client;
|
||||
|
||||
const { device } = useViewMode();
|
||||
const { device, mode } = useViewMode();
|
||||
const { width, height } = DEVICES[device];
|
||||
|
||||
const [ready, setReady] = useState(false);
|
||||
@@ -345,13 +354,21 @@ const LayoutPreview = ({ port, fill }: { port: number | null; fill?: boolean })
|
||||
*/
|
||||
const scale = available.width ? Math.min(1, available.width / width) : 1;
|
||||
|
||||
// In View the frame gets the panel's height; editing, a fixed band, so the controls below stay put
|
||||
// instead of moving every time somebody switches device.
|
||||
const shownHeight = fill ? Math.max(0, available.height) : Math.min(height * scale, 420);
|
||||
/*
|
||||
Whatever room is left, capped at the device's own height.
|
||||
|
||||
The cap matters: a 390x844 phone in a 1200px-tall panel would otherwise be drawn as a phone 1200
|
||||
pixels tall, which is not a phone. Past its real height the frame stops growing and the space stays
|
||||
empty, which is honest — there is no device that shape.
|
||||
|
||||
It was a fixed 420px band in Edit. That made the preview a letterbox in the mode where changes are
|
||||
being made, and left empty panel below the controls.
|
||||
*/
|
||||
const shownHeight = Math.max(0, Math.min(available.height, height * scale));
|
||||
|
||||
return (
|
||||
<section className={`flex flex-col gap-2 ${fill ? 'min-h-0 flex-1' : ''}`}>
|
||||
<div ref={box} className={`min-h-0 ${fill ? 'flex-1' : ''}`}>
|
||||
<section className="flex min-h-0 flex-1 flex-col gap-2">
|
||||
<div ref={box} className="min-h-0 flex-1">
|
||||
<div
|
||||
className="mx-auto overflow-hidden rounded-lg border border-border bg-white"
|
||||
// The scaled footprint, so the border hugs the frame and the page below does not jump when
|
||||
@@ -382,8 +399,10 @@ const LayoutPreview = ({ port, fill }: { port: number | null; fill?: boolean })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!fill && (
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{/* Only while editing. In View the preview is the whole point, and a caption is a line of height
|
||||
taken from it to say something the device buttons already show. */}
|
||||
{mode === 'edit' && (
|
||||
<p className="shrink-0 text-[10px] text-muted-foreground">
|
||||
The development server, live at {width}px. Save to see a change here.
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user