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