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 ( -
- {options.map(({ value, label, Icon }) => ( - - ))} -
- ); -}; /** 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. */} +
diff --git a/web/SitePreview.tsx b/web/SitePreview.tsx index 5774c10..24ad74f 100644 --- a/web/SitePreview.tsx +++ b/web/SitePreview.tsx @@ -101,7 +101,7 @@ export const SitePreview = ({ const clientRef = useRef(client); clientRef.current = client; - const { device, mode } = useViewMode(); + const { device } = useViewMode(); const { width } = DEVICES[device]; const [ready, setReady] = useState(false); @@ -305,13 +305,11 @@ export const SitePreview = ({
- {/* 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], ); /*