the page builder gets Edit/View and the three widths

The same bar Layout has, from the same opt-in: the section says it has a preview and the controls
appear. Pages — the list — still says nothing, because there is nothing there to view at tablet width.

VIEW paints nothing at all. Not dimmed chrome or hidden buttons: no injection, so the frame holds
exactly the document the site served. That is what View has to mean here — the outlines, the labels and
the drop zone are the editor's, and a preview that keeps any of them is not showing you the page.

Done by putting `mode` in the callback's dependencies rather than by branching on it somewhere else.
Switching hands SitePreview a new function; its effect runs the previous paint's cleanup and then does
nothing. The chrome goes without a reload and without a second code path that could disagree with the
first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 04:28:51 +01:00
co-authored by Claude Opus 5
parent fb651f06e2
commit a79dc2b1af
+25 -4
View File
@@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router';
import { useClient } from 'hooks/useClient';
import { readableError } from '../errors';
import { SitePreview, useDevPort } from '../SitePreview';
import { usePreviewable, useViewMode } from '../ViewMode';
import { paintCanvas, type CanvasSection } from '../canvas';
import { AddSectionDialog, type SectionLayout } from './AddSectionDialog';
import { usePages, type Page } from '../usePages';
@@ -33,6 +34,11 @@ export const PageBuilderPanel = () => {
const devPort = useDevPort();
const { pages, announce } = usePages();
// This section has a preview, so the bar offers Edit/View and the three widths — the same opt-in
// Layout makes. Pages, the list, does not: there is nothing there to view at tablet width.
usePreviewable();
const { mode } = useViewMode();
const [error, setError] = useState<string | null>(null);
const [sections, setSections] = useState<CanvasSection[]>([]);
@@ -62,14 +68,29 @@ export const PageBuilderPanel = () => {
Stable, because SitePreview holds it and re-runs it on every load. A new function each render would
be a new injection each render.
*/
/*
In VIEW, nothing is painted at all.
Not the chrome dimmed or the buttons hidden — no injection, so the frame holds exactly the document
the site served. That is what View has to mean here: outlines, labels and a drop zone are the
editor's, and a preview that keeps any of them is not showing you the page.
The check is INSIDE the callback and `mode` is a dependency, so switching to View hands SitePreview
a new function: its effect runs the previous paint's cleanup and then paints nothing. That is what
takes the chrome away, with no reload and no second code path.
*/
const onDocument = useCallback(
(frameDocument: Document) =>
paintCanvas(frameDocument, {
(frameDocument: Document) => {
if (mode === 'view') return;
return paintCanvas(frameDocument, {
sections,
onAddSection: () => handlersRef.current.add(),
onDeleteSection: (section) => handlersRef.current.remove(section.id),
}),
[sections],
});
},
// `mode` is in here so switching to View gives SitePreview a NEW function, which runs the previous
// paint's cleanup and then paints nothing. That is what removes the chrome without a reload.
[sections, mode],
);
/*