diff --git a/manifest.ts b/manifest.ts
index e8fb4fa..152b776 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.22',
+ version: '2026.08.29.23',
platform: '>=1.0.0',
label: 'Fofos',
diff --git a/web/DetailHeader.tsx b/web/DetailHeader.tsx
new file mode 100644
index 0000000..176bb80
--- /dev/null
+++ b/web/DetailHeader.tsx
@@ -0,0 +1,33 @@
+import { useSearchParams } from 'react-router';
+import { DEFAULT_SECTION, isSectionKey, sectionFor } from './sections';
+
+/**
+ * The detail panel's chrome header — the section's own name, not the word "Detail".
+ *
+ * ── Why this replaces a row inside the panel ──
+ *
+ * The panel used to draw its own header strip with the section label and its hint, UNDER the workspace's
+ * chrome header, which said "Detail". Two bars stacked, one of them saying nothing: the top of the panel
+ * was three rows of furniture before any content.
+ *
+ * A panel meta may supply its own `header`, which is the mechanism for exactly this — several plugins
+ * already do it. So the section's name goes where a panel's name goes, and the row inside is gone.
+ *
+ * ── The hint went with it ──
+ *
+ * "Photos and video the site is built from" is useful in the NAV, where somebody is choosing between
+ * ten sections. Repeated above the thing they chose, it is a caption for a decision already made.
+ */
+export const DetailHeader = () => {
+ const [params] = useSearchParams();
+ const raw = params.get('section');
+ const section = sectionFor(isSectionKey(raw) ? raw : DEFAULT_SECTION);
+ const Icon = section.icon;
+
+ return (
+ <>
+
+ {section.label}
+ >
+ );
+};
diff --git a/web/DetailPanel.tsx b/web/DetailPanel.tsx
index 5219b27..3831a80 100644
--- a/web/DetailPanel.tsx
+++ b/web/DetailPanel.tsx
@@ -71,11 +71,6 @@ export const DetailPanel = () => {
return (
-
-
{current.label}
- {current.hint}
-
-
);
diff --git a/web/MediaPanel.tsx b/web/MediaPanel.tsx
index cff6161..a8e1aec 100644
--- a/web/MediaPanel.tsx
+++ b/web/MediaPanel.tsx
@@ -91,10 +91,14 @@ export const MediaPanel = () => {
`rootLabel` so the breadcrumb reads "Media" rather than "originals": the folder's name on disk is
an implementation detail of how this plugin lays out a root, and the section is called Media.
- `panelId` so the folder somebody is in survives a reload — without it every refresh drops them
- back at the top, however deep they were.
+ `urlPath` puts the current folder in `?path=`, so a reload lands where you were and a link can be
+ sent to somebody. it is opt-in in the platform because a screen with TWO browsers would have them
+ fighting over one parameter — this screen has one, so it is safe here and not in general.
+
+ `panelId` stays as well: it remembers the folder for a panel that is re-opened rather than reloaded,
+ which the URL cannot cover.
*/
- return ;
+ return ;
};
const Notice = ({ icon: Icon, children }: { icon: typeof FolderOpen; children: React.ReactNode }) => (
diff --git a/web/panels.ts b/web/panels.ts
index 4a5a23b..7c4f255 100644
--- a/web/panels.ts
+++ b/web/panels.ts
@@ -2,6 +2,7 @@ import type { AppRegistryMeta } from 'officerdev';
import { List, PanelRight } from 'lucide-react';
import { NavPanel } from './NavPanel';
import { DetailPanel } from './DetailPanel';
+import { DetailHeader } from './DetailHeader';
/**
* Two panels: a nav, and whatever it selects.
@@ -26,6 +27,9 @@ export const appRegistryMetas: AppRegistryMeta[] = [
name: 'Detail',
icon: PanelRight,
component: DetailPanel,
+ // The chrome header says which SECTION is open rather than the word "Detail" — see DetailHeader for
+ // why that replaced a second header row inside the panel.
+ header: DetailHeader,
availableOnPanel: false,
},
];