diff --git a/web/DetailPanel.tsx b/web/DetailPanel.tsx
index a901f9c..7db51dd 100644
--- a/web/DetailPanel.tsx
+++ b/web/DetailPanel.tsx
@@ -11,7 +11,7 @@ import { PageBuilderPanel } from './sections/PageBuilderPanel';
import { PublishButton } from './PublishButton';
import { SyncButton } from './SyncButton';
import { SaveTargetProvider, useSaveButton } from './SaveTarget';
-import { DEVICES, ViewModeProvider, useViewMode, type DeviceName, type Mode } from './ViewMode';
+import { DEVICES, ViewModeProvider, useViewMode, type DeviceName } from './ViewMode';
import { MessagesPanel } from './MessagesPanel';
import { PlannedSection } from './PlannedSection';
import { useReady } from './useReady';
@@ -84,39 +84,6 @@ const SaveButton = () => {
);
};
-/**
- * Editing, or just looking.
- *
- * Two buttons rather than one that toggles, so the current state is readable without knowing what the
- * label means. A single button saying "View" has to be interpreted — is that what it is, or what it
- * would become? — and that question is asked every time.
- */
-const ModeToggle = () => {
- const { previewable, mode, setMode } = useViewMode();
- if (!previewable) return null;
-
- const options: { value: Mode; label: string; Icon: typeof Pencil }[] = [
- { value: 'edit', label: 'Edit', Icon: Pencil },
- { value: 'view', label: 'View', Icon: Eye },
- ];
-
- return (
-
- );
-};
/** The width the preview is rendered at. See DEVICES for why these three and not others. */
const DeviceToggle = () => {
@@ -209,9 +176,11 @@ export const DetailPanel = () => {
would never be in the same place twice.
*/}
-
-
-
+ {/* Empty, and deliberately still here: the middle group is centred against the BAR, so
+ removing the left group would let Desktop/Tablet/Mobile drift left as the right group
+ changes width. It held Edit/View — see the note in ViewMode about why there is no
+ longer a mode to toggle. */}
+
- {/* 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' && (
-
- The development server, live at {width}px. Save to see a change here.
-
- )}
+ {/* 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.
+
);
};
diff --git a/web/ViewMode.tsx b/web/ViewMode.tsx
index 9e00694..512229b 100644
--- a/web/ViewMode.tsx
+++ b/web/ViewMode.tsx
@@ -30,7 +30,17 @@ import { useSearchParams } from 'react-router';
* The rule was already right; the exception was not.
*/
-export type Mode = 'edit' | 'view';
+/*
+ There is no Mode any more.
+
+ The bar offered Edit and View, and View existed to see the page without the builder's outlines and
+ handles on top of it. It was the default, and it turned out to be the wrong default and then the
+ wrong feature: this panel is where somebody EDITS, and a second mode meant every arrival had a click
+ in front of it and every guard in the panels had a branch that did nothing but wait for that click.
+
+ The clean page is still one click away — it is the site, on its own URL. What the admin owes is the
+ editing surface, always.
+*/
export type DeviceName = 'desktop' | 'tablet' | 'mobile';
/**
@@ -60,9 +70,8 @@ export const DEVICES: Record = {
};
type ViewModeValue = {
- mode: Mode;
device: DeviceName;
- setMode: (mode: Mode) => void;
+
setDevice: (device: DeviceName) => void;
/** True while a section with a preview is open. The bar shows its controls only then. */
previewable: boolean;
@@ -70,9 +79,8 @@ type ViewModeValue = {
};
const ViewModeContext = createContext({
- mode: 'edit',
device: 'desktop',
- setMode: () => {},
+
setDevice: () => {},
previewable: false,
announcePreviewable: () => {},
@@ -82,33 +90,7 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => {
const [params, setParams] = useSearchParams();
/*
- The mode lives in the URL — `?mode=edit`.
-
- docs/navigation-audit.md draws the line: anything ADDRESSABLE goes in the URL. Whether you are
- editing a page or looking at it is exactly that. It survives a reload, it can be sent to somebody,
- and a link to a page being edited is a different thing from a link to the page.
-
- `mode`, not `view`. The parameter names the AXIS and the value names the state, so the pair reads
- as `mode=edit` and `mode=view` rather than the tautological `view=view`.
-
- Absent means View, which is the default and the common case — so an ordinary link carries nothing,
- and the parameter only appears once somebody has chosen.
- */
- const mode: Mode = params.get('mode') === 'edit' ? 'edit' : 'view';
-
- const setMode = useCallback(
- (next: Mode) => {
- const search = new URLSearchParams(params);
- // Written even for the default, once somebody has touched it: a URL that says `mode=view` is
- // unambiguous, and a toggle that sometimes leaves no trace reads as a toggle that failed.
- search.set('mode', next);
- setParams(search, { replace: true });
- },
- [params, setParams],
- );
-
- /*
- The device, alongside the mode, for the same reason.
+ The device lives in the URL, for the reason the whole file gives: it is addressable.
Absent means desktop — the default and the common case — so an ordinary link carries neither
parameter and each appears only once somebody has chosen it.
@@ -130,7 +112,7 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => {
const announcePreviewable = useCallback((has: boolean) => setPreviewable(has), []);
return (
-
+
{children}
);
diff --git a/web/sections/LayoutPanel.tsx b/web/sections/LayoutPanel.tsx
index 7a3b08d..899c2b4 100644
--- a/web/sections/LayoutPanel.tsx
+++ b/web/sections/LayoutPanel.tsx
@@ -219,7 +219,7 @@ export const LayoutPanel = () => {
useSaveTarget(dirty, busy, save);
// This section has a preview, so the bar offers Edit/View and the three widths.
usePreviewable();
- const { mode, device } = useViewMode();
+ const { device } = useViewMode();
/*
The measure, and nothing else.
@@ -242,7 +242,6 @@ export const LayoutPanel = () => {
const onDocument = useCallback(
(frameDocument: Document) => {
- if (mode === 'view') return;
const measure = paintMeasure(frameDocument);
const spacing = applyGutter(frameDocument, gutter);
return () => {
@@ -250,7 +249,7 @@ export const LayoutPanel = () => {
measure();
};
},
- [mode, gutter],
+ [gutter],
);
if (!draft) return
{error ?? 'Loading…'}
;
@@ -292,8 +291,7 @@ export const LayoutPanel = () => {
`shrink-0` so they never take height from the preview, and no `max-h` any more: one tab is short
enough that it does not need capping, which is the other thing this bought.
*/}
- {mode === 'edit' && (
-
+
Colours
@@ -425,7 +423,6 @@ export const LayoutPanel = () => {
- )}
);
diff --git a/web/sections/PageBuilderPanel.tsx b/web/sections/PageBuilderPanel.tsx
index 76408ab..3105cf0 100644
--- a/web/sections/PageBuilderPanel.tsx
+++ b/web/sections/PageBuilderPanel.tsx
@@ -65,7 +65,7 @@ export const PageBuilderPanel = () => {
// 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, device } = useViewMode();
+ const { device } = useViewMode();
const [error, setError] = useState(null);
const [sections, setSections] = useState([]);
@@ -162,7 +162,6 @@ export const PageBuilderPanel = () => {
const onDocument = useCallback(
(frameDocument: Document) => {
- if (mode === 'view') return;
frameDoc.current = frameDocument;
return paintCanvas(frameDocument, {
sections,
@@ -179,7 +178,7 @@ export const PageBuilderPanel = () => {
},
// `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],
+ [sections],
);
/*