From 5e96274ebdbe05c9d85989d41e6f80c8b10d2e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 00:20:39 +0100 Subject: [PATCH] Administration sits at the foot of the panel, not the end of the list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit it was last in SECTIONS, so it rendered as the tenth row and then stopped — directly under Messages, with empty panel beneath it. that is the bottom of the LIST. what it wanted is the bottom of the PANEL, the way Update-all sits in the plugins nav. `mt-auto` on a second group, divided by the same border the rows already use. it holds whether there are ten sections or two, and it does not need a spacer element to push it down. the split is a `pinned` flag on the section rather than the nav testing for the key. which sections are the client's site and which are this plugin's own machinery is a fact about the section, and the nav should be reading it, not knowing it. one array still — `sectionFor` and `isSectionKey` have to resolve every key, and a second exported list is a second place to forget one. the row body came out into `renderRow` so both groups draw the same row. no change to what a row looks like. Co-Authored-By: Claude Opus 5 --- manifest.ts | 2 +- web/NavPanel.tsx | 77 +++++++++++++++++++++++++++++------------------- web/sections.ts | 10 +++++++ 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/manifest.ts b/manifest.ts index 2e2e1e9..bb5cf8b 100644 --- a/manifest.ts +++ b/manifest.ts @@ -35,7 +35,7 @@ import type { PluginManifest } from '@@/plugins/manifest'; */ export const manifest: PluginManifest = { publisher: 'officerdev', - version: '2026.08.29.02', + version: '2026.08.29.03', platform: '>=1.0.0', label: 'Fofos', diff --git a/web/NavPanel.tsx b/web/NavPanel.tsx index d6a766c..115c41d 100644 --- a/web/NavPanel.tsx +++ b/web/NavPanel.tsx @@ -22,6 +22,15 @@ import { DEFAULT_SECTION, SECTIONS, isSectionKey, type SectionKey } from './sect * from the same value the detail panel reads, which is the one place it can come from. */ +/** + * The nav has a FOOT. + * + * Everything above it is a part of the client's site, in sitemap order. Below the divider is this + * plugin's own machinery, which is not a page and does not belong in a list of them — the same split + * the plugins nav makes with Update-all, and the reason it sits at the bottom of the panel rather than + * at the bottom of the list. `mt-auto` pins it there whether there are ten sections or two. + */ + const rowClass = (active: boolean) => `flex w-full items-start gap-2.5 border-b border-border px-3 py-2.5 text-left transition-colors ${ active ? 'bg-muted' : 'hover:bg-muted/50' @@ -32,40 +41,46 @@ export const NavPanel = () => { const raw = params.get('section'); const active: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION; + const renderRow = (section: (typeof SECTIONS)[number]) => { + const Icon = section.icon; + + // Everything else in the URL is preserved. This is one screen of a workspace, and the global + // query string carries state that belongs to the shell rather than to this panel. + const search = new URLSearchParams(params); + search.set('section', section.key); + + return ( + + + + + {section.label} + {/* Dimmed and labelled rather than hidden. A nav that shows only what is finished gives + no sense of what the thing is for — see sections.ts. */} + {!section.built && ( + soon + )} + + {section.hint} + + + ); + }; + return ( ); }; diff --git a/web/sections.ts b/web/sections.ts index 4ed1209..d76f933 100644 --- a/web/sections.ts +++ b/web/sections.ts @@ -48,6 +48,15 @@ export type Section = { /** The fofos path this reads, relative to the plugin's mount. Null when nothing is built. */ path: string | null; built: boolean; + /** + * Pinned to the FOOT of the nav rather than sitting in the list. + * + * A flag rather than the nav hard-coding a key, because "which sections are the site's and which are + * the plugin's own" is a fact about the section. It is also why this stays in one array: `sectionFor` + * and `isSectionKey` have to resolve every key, and a second exported list would be a second place to + * forget one. + */ + pinned?: boolean; }; export const SECTIONS: Section[] = [ @@ -135,6 +144,7 @@ export const SECTIONS: Section[] = [ hint: 'Where fofos is, and the token to reach it', path: null, built: true, + pinned: true, }, ];