From 8c9e4ecb85187552ff7430c26087b620d9844ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 31 Aug 2026 19:19:53 +0100 Subject: [PATCH] Layout's three bands are nav rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top Menu, Content, Footer — the layout IS a page, and those are the only three things on it. They were reachable by scrolling and by knowing which tab held what; as rows they take the shape the nav already teaches with the pages under Pages, and the open one is `band` in the query string. Fixed rather than fetched, unlike pages. A band cannot be added or removed from the admin — only the code that draws it can — so reading them from the database would be a query that can only ever return the same three rows in the same order. Co-Authored-By: Claude Opus 5 (1M context) --- web/NavPanel.tsx | 43 ++++++++++++++++++++++++++++++++++++++++++- web/sections.ts | 21 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/web/NavPanel.tsx b/web/NavPanel.tsx index a42942e..455e35f 100644 --- a/web/NavPanel.tsx +++ b/web/NavPanel.tsx @@ -1,6 +1,6 @@ import { Link, useSearchParams } from 'react-router'; import { Lock } from 'lucide-react'; -import { DEFAULT_SECTION, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections'; +import { DEFAULT_SECTION, LAYOUT_BANDS, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections'; import { useReady } from './useReady'; import { pageParam, usePages } from './usePages'; @@ -145,6 +145,47 @@ export const NavPanel = () => { Drawn whether or not Pages is the open section. The list of pages IS the site, and hiding it until you click makes the nav a menu of menus. */ + /* + Layout's three bands, as rows under Layout. + + The layout IS a page — a header, the slot a page goes in, and a footer — and those three are the + only things on this screen. They were reachable by scrolling and by knowing which tab held what; + as rows they are the same shape as the pages under Pages, which is the shape the nav already + teaches. + + Fixed rather than fetched: unlike pages, these three are structural. A band cannot be added or + removed from the admin, only the code that draws it can, so a list read from the database would + be a query that can only ever return the same three rows. + */ + if (section.key === 'layout') { + return ( +
+ {row} + {LAYOUT_BANDS.map((band) => { + const childSearch = new URLSearchParams(params); + childSearch.set('section', 'layout'); + childSearch.set('band', band.key); + childSearch.delete('media'); + + const current = active === 'layout' && params.get('band') === band.key; + + return ( + + {band.label} + + ); + })} +
+ ); + } + if (section.key !== 'pages' || !pages?.length) return row; return ( diff --git a/web/sections.ts b/web/sections.ts index e171cbd..7e30292 100644 --- a/web/sections.ts +++ b/web/sections.ts @@ -70,6 +70,27 @@ export type Section = { pinned?: boolean; }; +/** + * The three bands the layout is made of, in the order they appear on the page. + * + * The layout IS a page: a header, the slot a page goes in, and a footer. These are the nav rows under + * Layout, and the `band` in the query string. + * + * FIXED, unlike the pages under Pages. A band cannot be added or removed from the admin — only the + * code that draws it can — so reading them from the database would be a query that can only ever + * return the same three rows in the same order. + */ +export const LAYOUT_BANDS = [ + { key: 'header', label: 'Top Menu' }, + { key: 'content', label: 'Content' }, + { key: 'footer', label: 'Footer' }, +] as const; + +export type LayoutBand = (typeof LAYOUT_BANDS)[number]['key']; + +export const isLayoutBand = (value: string | null): value is LayoutBand => + value !== null && LAYOUT_BANDS.some((band) => band.key === value); + export const SECTIONS: Section[] = [ { /*