refactor(api): honest FeedResponse; derive pageTitleKey from the registry (R7 S3a·2-3)

- FeedResponse claimed a required `limit`, but /api/search/youtube never
  returns it (only /api/feed does) and nothing in the app reads it. Drop the
  field so searchYoutube's Promise<FeedResponse> stops lying; documented for
  re-add if a consumer appears.
- pageMeta's hand-written 13-case Page->title switch now derives from the
  ModuleDef registry via a new optional `titleKey` (the longer tab title, e.g.
  "Channel manager"; falls back to the short nav labelKey). A new page is one
  touch (add it to MODULES) instead of also editing a switch. A test pins all
  13 title keys byte-identical to the old switch (suite 74).
This commit is contained in:
2026-07-27 22:45:30 +02:00
parent 5de6be8fde
commit 2d4ecdb6c5
4 changed files with 70 additions and 37 deletions
+29 -4
View File
@@ -26,6 +26,9 @@ export type ModuleDef = {
id: Page;
// i18n key for the SHORT display name — the same label the nav rail and the header pill show.
labelKey: string;
// i18n key for the LONGER, descriptive browser-tab title (e.g. "Channel manager" vs the rail's
// "Channels"). Absent → the tab title falls back to `labelKey`. Read via `pageTitleKey`.
titleKey?: string;
icon: LucideIcon;
// Admin/system modules render in their own section (below a divider) in the nav rail.
system?: boolean;
@@ -44,7 +47,12 @@ const notDemo = (me: Me) => !me.is_demo;
// Ordered in nav-rail order — this array IS the sequence the rail and the stepper walk.
const MODULES: readonly ModuleDef[] = [
{ id: "feed", labelKey: "header.account.feed", icon: Home },
{ id: "channels", labelKey: "header.account.channels", icon: Tv },
{
id: "channels",
labelKey: "header.account.channels",
titleKey: "header.channelManager",
icon: Tv,
},
{ id: "playlists", labelKey: "header.account.playlists", icon: ListVideo },
{ id: "plex", labelKey: "plex.navLabel", icon: Clapperboard, gate: (me) => me.plex_enabled },
{ id: "notifications", labelKey: "inbox.navLabel", icon: Bell, hasBadge: true },
@@ -62,10 +70,11 @@ const MODULES: readonly ModuleDef[] = [
hasBadge: true,
gate: notDemo,
},
{ id: "stats", labelKey: "header.account.stats", icon: BarChart3 },
{ id: "stats", labelKey: "header.account.stats", titleKey: "header.usageStats", icon: BarChart3 },
{
id: "scheduler",
labelKey: "header.account.scheduler",
titleKey: "header.scheduler",
icon: Activity,
system: true,
gate: isAdmin,
@@ -73,12 +82,27 @@ const MODULES: readonly ModuleDef[] = [
{
id: "config",
labelKey: "header.account.config",
titleKey: "header.configuration",
icon: SlidersHorizontal,
system: true,
gate: isAdmin,
},
{ id: "users", labelKey: "header.account.users", icon: Users, system: true, gate: isAdmin },
{ id: "audit", labelKey: "header.account.audit", icon: ScrollText, system: true, gate: isAdmin },
{
id: "users",
labelKey: "header.account.users",
titleKey: "header.users",
icon: Users,
system: true,
gate: isAdmin,
},
{
id: "audit",
labelKey: "header.account.audit",
titleKey: "audit.title",
icon: ScrollText,
system: true,
gate: isAdmin,
},
];
// Settings is reachable (its own rail button + the ◀/▶ wrap target) but is not a primary module in
@@ -86,6 +110,7 @@ const MODULES: readonly ModuleDef[] = [
const SETTINGS_MODULE: ModuleDef = {
id: "settings",
labelKey: "header.account.settings",
titleKey: "settings.title",
icon: Settings,
};