the bar is gone; the device toggle is the editor's

It sat across the top of the detail panel with Sync, Build and Publish — three
things that act on the site, beside one that decides how you are looking at it.
The toggle belongs with the settings it scopes: a spacing is chosen per width,
so the control saying which width is the first thing in that column.

Sync, Build and Publish are unmounted until they have a place of their own.
ViewModeProvider stays; it was never the bar's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 07:38:43 +01:00
co-authored by Claude Opus 5
parent b5ca67e68e
commit e45e3d84a8
2 changed files with 44 additions and 67 deletions
+9 -54
View File
@@ -1,8 +1,6 @@
import { Navigate } from 'react-router';
import { href, useNav } from './routes';
import { DEFAULT_SECTION } from './sections';
import { Eye, Monitor, Pencil, Smartphone, Tablet } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { sectionFor, sectionOpen, type SectionKey } from './sections';
import { AdministrationPanel } from './AdministrationPanel';
import { MediaPanel } from './MediaPanel';
@@ -10,10 +8,7 @@ import { GalleriesPanel } from './sections/GalleriesPanel';
import { LayoutPanel } from './sections/LayoutPanel';
import { PagesPanel } from './sections/PagesPanel';
import { PageBuilderPanel } from './sections/PageBuilderPanel';
import { PublishButton } from './PublishButton';
import { SyncButton } from './SyncButton';
import { DEVICES, ViewModeProvider, useViewMode } from './ViewMode';
import { DeviceToggle } from './DeviceToggle';
import { ViewModeProvider } from './ViewMode';
import { MessagesPanel } from './MessagesPanel';
import { PlannedSection } from './PlannedSection';
import { useReady } from './useReady';
@@ -58,16 +53,6 @@ const PANELS: Partial<Record<SectionKey, () => React.JSX.Element>> = {
media: MediaPanel,
};
/** The width the preview is rendered at. See DEVICES for why these three and not others. */
const BarDeviceToggle = () => {
const { previewable } = useViewMode();
if (!previewable) return null;
// The same control the editor sections use — see DeviceToggle, which reads `?device=` rather than a
// context so every panel on the screen shares one answer.
return <DeviceToggle size="bar" />;
};
export const DetailPanel = () => {
/*
A page selected inside Pages opens the BUILDER, not the list with a row lit up. By SLUG, so the URL
@@ -113,48 +98,18 @@ export const DetailPanel = () => {
return (
<div className="flex size-full flex-col overflow-hidden">
{/*
The site's own actions, pinned above whatever the section is showing.
No bar.
OUTSIDE the scroll area on purpose. Every panel scrolls its own contents, so anything drawn
inside one leaves the screen as soon as somebody reads to the bottom — and Publish is what you
reach for AFTER reading to the bottom. It sits here instead, as a sibling of the scrolling
region rather than the first thing inside it.
It held the device toggle and Sync / Build / Publish across the top of this panel. The toggle
moved to the top of the EDITOR, which is where the settings it scopes already are; the three
actions are out until they have a place that is theirs rather than a corner of the panel that
happens to be pinned. `SyncButton` and `PublishButton` still exist and are still mounted
nowhere — see git history for what the bar looked like.
Not in the workspace's chrome header either, where it lived for an afternoon. That header is
shared furniture drawn the same way by every panel in the app; these two buttons belong to this
plugin and to the site it administers.
`ViewModeProvider` stays. It is not the bar's: the preview and the two panels with one read
`device` from it, and `usePreviewable` is how a section says it has a page to show.
*/}
<ViewModeProvider>
{/*
Three groups: how you are looking at it, at what width, and what to do with it.
The middle one is centred against the BAR rather than against what is beside it — the two
outer groups change width as sections come and go, and a middle that drifted with them
would never be in the same place twice.
*/}
<div className="flex shrink-0 items-center gap-2 border-b border-border px-4 py-2">
{/* 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. */}
<div className="flex flex-1 justify-start" />
<div className="flex justify-center">
<BarDeviceToggle />
</div>
<div className="flex flex-1 items-center justify-end gap-2">
{/* The order is the sequence: make the media web-ready, then make it public. Sync sits
left of Build for the same reason Build sits left of Publish — it is what has to have
happened first, and a stale `derived/` is a picture missing from the page you just
built.
No Save. Every control writes itself — see useAutosave. */}
<SyncButton />
<PublishButton />
</div>
</div>
<div className="min-h-0 flex-1">{Panel ? <Panel /> : <PlannedSection section={current} />}</div>
</ViewModeProvider>
</div>
+35 -13
View File
@@ -1,3 +1,4 @@
import { DeviceToggle } from './DeviceToggle';
import { useEditorSlot } from './useEditorSlot';
/**
@@ -8,23 +9,44 @@ import { useEditorSlot } from './useEditorSlot';
* to a row of two dropdowns. A panel keeps the page whole and puts the controls where a workspace puts
* controls.
*
* It holds nothing. The panel that owns a selection builds the editor and announces it — see
* useEditorSlot for why it carries the element rather than a description of one.
* It holds nothing of its own except the width. The panel that owns a selection builds the editor and
* announces it — see useEditorSlot for why it carries the element rather than a description of one.
*
* ── The device toggle is here, above everything ──
*
* It was in a bar across the top of the detail panel, which put it beside Sync and Publish — three
* things that act on the SITE next to one that decides how you are looking at it. It belongs with the
* settings it scopes: a spacing is chosen per width, and the control saying which width should be the
* first thing in the column those choices are made in.
*
* Always drawn, including when nothing is selected. It is not part of the editor — it decides what the
* PREVIEW shows, so hiding it until a block is picked would mean selecting something to change the
* width and then deselecting it.
*
* It reads `?device=` rather than a context, which is what lets it live in a panel that is a sibling of
* the one holding the preview. See DeviceToggle.
*/
export const EditorPanel = () => {
const [editor] = useEditorSlot();
if (!editor) {
return (
<div className="flex size-full items-center justify-center p-6 text-center">
{/* Names what the panel is FOR rather than saying it is empty. "Nothing selected" describes
this component's state; this describes the reader's next move. */}
<p className="text-xs text-muted-foreground">
Pick a block in the page to edit it, or press Spacing on a band.
</p>
return (
<div className="flex size-full flex-col overflow-hidden">
{/* Outside the scroll area, so a long editor cannot carry it off the screen. */}
<div className="flex shrink-0 justify-center border-b border-border px-3 py-2">
<DeviceToggle size="bar" />
</div>
);
}
return <div className="size-full overflow-auto p-3">{editor}</div>;
{editor ? (
<div className="min-h-0 flex-1 overflow-auto p-3">{editor}</div>
) : (
<div className="flex min-h-0 flex-1 items-center justify-center p-6 text-center">
{/* Names what the panel is FOR rather than saying it is empty. "Nothing selected" describes
this component's state; this describes the reader's next move. */}
<p className="text-xs text-muted-foreground">
Pick a block in the page to edit it, or press Spacing on a band.
</p>
</div>
)}
</div>
);
};