From 9565d24536a77f05119eb2a82bcf096aa0523c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 31 Aug 2026 02:19:02 +0100 Subject: [PATCH] the Menu and Footer editors are gone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header and the footer are designed — they belong to whoever writes the site's code, not to whoever edits its content. A panel offering to rearrange them offers a power that should not exist, and every use of it would be a decision made in the wrong place. What is left in Layout is the part that really is a content decision: the colours and the four typeface roles. The preview above them is still the real page, so what the code decides is visible here even though nothing here can change it. Co-Authored-By: Claude Opus 5 (1M context) --- web/sections/LayoutPanel.tsx | 271 ++--------------------------------- 1 file changed, 13 insertions(+), 258 deletions(-) diff --git a/web/sections/LayoutPanel.tsx b/web/sections/LayoutPanel.tsx index 157feae..8968de3 100644 --- a/web/sections/LayoutPanel.tsx +++ b/web/sections/LayoutPanel.tsx @@ -1,7 +1,4 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { GripVertical, Plus, Trash2 } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { useClient } from 'hooks/useClient'; import { readableError } from '../errors'; import { useFlash } from '../useFlash'; @@ -17,6 +14,15 @@ import { useSaveTarget } from '../SaveTarget'; * nav that has to be edited by a developer every time the client adds a page. Layout is what is left * once you take the pages out: the part of every page that is not the page. * + * ── The menu and the footer are NOT here, and that is the design ── + * + * They were, briefly: rows of links, footer columns, a bottom line. They are gone because the header + * and the footer are DESIGNED — they belong to whoever writes the site's code, not to whoever edits its + * content. A panel offering to rearrange them offers a power that should not exist, and every use of it + * would be a decision made in the wrong place. + * + * What is left is the part that IS a content decision: the colours and the typefaces. + * * ── The preview is the point ── * * It draws the menu across the top, a body, and the footer at the bottom, in the chosen colours and @@ -28,13 +34,7 @@ import { useSaveTarget } from '../SaveTarget'; * changed. What it is honest about is the thing being decided — order, wording, colour, typeface. */ -type LayoutLink = { label: string; href: string }; -type FooterColumn = { title: string; links: LayoutLink[] }; - type Layout = { - menu_items: LayoutLink[]; - footer_columns: FooterColumn[]; - footer_note: string | null; background: string | null; foreground: string | null; accent: string | null; @@ -44,8 +44,6 @@ type Layout = { font_display: string | null; /** What the picker may offer — sent by the API so this file holds no third copy of the list. */ fonts: string[]; - /** The pages that exist, for the menu's href suggestions. */ - pages: { path: string; label: string }[]; }; /** The four roles styles.css declares. A template asks for a role; this decides which face plays it. */ @@ -107,15 +105,12 @@ export const LayoutPanel = () => { const patch = (values: Partial) => setDraft((was) => (was ? { ...was, ...values } : was)); /* - Compared field by field rather than whole, because the payload carries two things this does not - own: `fonts` and `pages` are the API telling the client what exists, not values anybody edits. - Including them would be harmless today and wrong the first time a page is added — the panel would - show unsaved changes for a page somebody else created. + Compared field by field rather than whole, because the payload carries something this does not own: + `fonts` is the API saying which faces the build subsets, not a value anybody edits. Including it + would be harmless today and wrong the first time a face is added — the panel would show unsaved + changes for a decision made in the build script. */ const WRITABLE = [ - 'menu_items', - 'footer_columns', - 'footer_note', 'background', 'foreground', 'accent', @@ -149,16 +144,6 @@ export const LayoutPanel = () => { if (!draft) return
{error ?? 'Loading…'}
; - const menu = draft.menu_items; - const setMenu = (items: LayoutLink[]) => patch({ menu_items: items }); - - const columns = draft.footer_columns; - const setColumns = (next: FooterColumn[]) => patch({ footer_columns: next }); - - /** Falls back to what the page itself falls back to, so the sketch is not a different design. */ - const bg = draft.background ?? '#ffffff'; - const fg = draft.foreground ?? '#000000'; - return (
{saved &&

Saved

} @@ -174,142 +159,6 @@ export const LayoutPanel = () => { */} - {/* ── Menu ─────────────────────────────────────────────────────────────────────────────── */} -
-
-

Menu

- {!menu.length && ( - Showing the site’s own pages - )} -
- - {menu.map((item, index) => ( - setMenu(menu.map((was, i) => (i === index ? next : was)))} - onRemove={() => setMenu(menu.filter((_, i) => i !== index))} - onMove={(by) => setMenu(moved(menu, index, by))} - first={index === 0} - last={index === menu.length - 1} - /> - ))} - -
- - - {/* - Start from the site's own pages rather than from nothing. - - The menu falls back to them until somebody sets one, so the first edit is almost always - "the pages, with one change". Typing all eight back in to make that change is the kind of - small cruelty that makes people leave a panel alone. - */} - {!menu.length && draft.pages.length > 0 && ( - - )} -
-
- - {/* ── Footer ───────────────────────────────────────────────────────────────────────────── */} -
-

Footer

- - {columns.map((column, index) => ( -
-
- - setColumns(columns.map((was, i) => (i === index ? { ...was, title: event.currentTarget.value } : was))) - } - /> - -
- - {column.links.map((link, i) => ( - - setColumns( - columns.map((was, c) => - c === index ? { ...was, links: was.links.map((l, li) => (li === i ? next : l)) } : was, - ), - ) - } - onRemove={() => - setColumns( - columns.map((was, c) => (c === index ? { ...was, links: was.links.filter((_, li) => li !== i) } : was)), - ) - } - onMove={(by) => - setColumns(columns.map((was, c) => (c === index ? { ...was, links: moved(was.links, i, by) } : was))) - } - first={i === 0} - last={i === column.links.length - 1} - /> - ))} - - -
- ))} - - - -
- - patch({ footer_note: event.currentTarget.value || null })} - /> -
-
- {/* ── Colours ──────────────────────────────────────────────────────────────────────────── */}

Colours

@@ -376,100 +225,6 @@ export const LayoutPanel = () => { ); }; -/** Reordering, which is the one thing a menu needs more than editing. */ -const moved = (items: T[], from: number, by: number): T[] => { - const to = from + by; - if (to < 0 || to >= items.length) return items; - const next = [...items]; - const [item] = next.splice(from, 1); - next.splice(to, 0, item!); - return next; -}; - -/** - * One link: what it says, and where it goes. - * - * The href is a text field with the site's own pages offered beside it rather than a dropdown of - * them. A menu item can point at an anchor, an external address, or a page that does not exist yet — - * and while a site is being built the last of those is the common case, so a control that could only - * express "one of the pages that already exist" would be in the way most of the time. - */ -const LinkRow = ({ - item, - pages, - onChange, - onRemove, - onMove, - first, - last, -}: { - item: LayoutLink; - pages: { path: string; label: string }[]; - onChange: (item: LayoutLink) => void; - onRemove: () => void; - onMove: (by: number) => void; - first: boolean; - last: boolean; -}) => { - const known = pages.some((page) => page.path === item.href); - - return ( -
-
- - -
- - - - onChange({ ...item, label: event.currentTarget.value })} - /> - -
- onChange({ ...item, href: event.currentTarget.value })} - /> - {/* Said rather than enforced. An unknown href is usually a page not built yet, not a mistake, - and refusing it would make this panel unusable during exactly the phase it is for. */} - {item.href && !known && !item.href.startsWith('http') && !item.href.startsWith('#') && ( - - no page there yet - - )} -
- - - {pages.map((page) => ( - - ))} - - - -
- ); -}; - /** * The site's own `/layout` page, through the PLATFORM's dev proxy. *