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, }, ];