diff --git a/web/ViewMode.tsx b/web/ViewMode.tsx index 5c56f0d..9e00694 100644 --- a/web/ViewMode.tsx +++ b/web/ViewMode.tsx @@ -19,12 +19,15 @@ import { useSearchParams } from 'react-router'; * So the bar shows these only when the open section says it has a preview, the same way Save renders * nothing when there is nothing to save. * - * ── Where each piece of state lives ── + * ── Both live in the URL ── * - * The MODE is in the URL: it is addressable, and a link to a page being edited is a different thing - * from a link to the page. The DEVICE is not, yet — it is a way of looking rather than a thing being - * looked at, and nobody has wanted to send one. That distinction is worth keeping deliberate rather - * than letting both drift into whichever place was convenient. + * `?mode=edit&device=mobile`. I had argued the device should stay in React state — a way of LOOKING + * rather than a thing being looked at — and that distinction did not survive contact with the obvious + * use: "here is what it does on a phone" is exactly the link somebody wants to send, and a URL that + * drops the width sends them the desktop. + * + * docs/navigation-audit.md says addressable state goes in the URL, and both of these are addressable. + * The rule was already right; the exception was not. */ export type Mode = 'edit' | 'view'; @@ -104,7 +107,23 @@ export const ViewModeProvider = ({ children }: { children: ReactNode }) => { [params, setParams], ); - const [device, setDevice] = useState('desktop'); + /* + The device, alongside the mode, for the same reason. + + 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. + */ + const raw = params.get('device'); + const device: DeviceName = raw === 'tablet' || raw === 'mobile' ? raw : 'desktop'; + + const setDevice = useCallback( + (next: DeviceName) => { + const search = new URLSearchParams(params); + search.set('device', next); + setParams(search, { replace: true }); + }, + [params, setParams], + ); const [previewable, setPreviewable] = useState(false); // Stable, so the effect in `usePreviewable` is not re-run by this provider re-rendering.