import type { LucideIcon } from "lucide-react"; import { ClipboardCheck, Cpu, Globe, HardDrive, LayoutDashboard, ListChecks, ListPlus, ScrollText, Settings, Tags, Tv, Bookmark, Trash2, } from "lucide-react"; // Single source of truth for the editor's primary navigation. Consumed by BOTH // the sidebar (editor/app/layout.tsx) and the command palette // (editor/app/components/CommandPalette.tsx) so they never drift — the old // palette hard-coded a stale 5-item subset. Icons are used by the palette and // the compact sidebar; `badgeKey` drives the live count pills / changelog dot. // // FOUR GROUPS, ONE NOUN EACH: the Corpus (what is on disk), the Operations that // derive things from it, the Sites that publish it, and the Machine that runs // it. That is the model in plans/editor-operations-ia.md, and the nav is where // it is either true or not — a group called "Pool" holding ten unrelated tools // was the shape of the problem, not a labelling accident. // // THE CAP IS THE RULE UMTOOL ALREADY WROTE, and it is quoted here rather than // re-derived so the two consoles do not drift into two nav philosophies. // umtool/components/AppNav.tsx: "SEVEN visible entries, and the cap is still // NINE … The next tool goes UNDER one of these, not beside them — which is // exactly what happened to the four judging piles and the sources page: they // are the `song ▸` group now, at the same URLs they always had." // // So: FOLD, DO NOT ADD, and every retired route REDIRECTS rather than 404s // (see editor/next.config.ts). The entries marked "interim" were folded by // later slices; the end state is eleven, and slice 5 reached it (2026-08-30). // // A SECOND RECORDED EXCEPTION, at thirteen: /tags. A curated tag is a // CORPUS noun — a vocabulary over videos, cutting across every channel and // every site — and there is no channel, operation or site it could fold under // without becoming that thing's property: under /sites it would be one site's // word list, under /channels it would be per channel, and both are exactly what // the feature is not. Agreed with the operator 2026-09-21; plans/curated-tags.md // carries the same sentence. // // ONE RECORDED EXCEPTION, at twelve: /storage. A storage location is a place // the machine keeps bytes — a Machine NOUN, like a worker or a job — and not a // fold of Cleanup, which is an operation over bytes that already have a place. // Agreed with the operator 2026-09-17; plans/editor-operations-ia.md carries // the same sentence. export type NavBadgeKey = "jobs" | "changelog" | "cleanable"; export type NavLink = { href: string; label: string; icon: LucideIcon; badgeKey?: NavBadgeKey; // Extra search terms for the palette (not shown), e.g. synonyms. keywords?: string; }; export type NavGroup = { label: string; links: NavLink[]; }; export const NAV_GROUPS: NavGroup[] = [ { label: "Corpus", links: [ { href: "/", label: "Dashboard", icon: LayoutDashboard, keywords: "home overview monitor widget pipeline" }, { href: "/channels", label: "Channels", icon: Tv }, { href: "/tags", label: "Tags", icon: Tags, keywords: "curated vocabulary pin suppress rule chip subject collab topic label", }, { href: "/review", label: "Review", icon: ClipboardCheck, keywords: "duplicates duplicate shorts media integrity scan corrupt review queue", }, ], }, { label: "Operations", links: [ { href: "/operations", label: "Operations", icon: ListPlus, keywords: "auto queue runner policy lane sweep pipeline digest diarization attribution download transcription actionable needs attention todo sync schedule cadence cron", }, ], }, { label: "Sites", links: [ { href: "/sites", label: "Sites", icon: Globe, keywords: "family charts stats graphs search aliases synonyms suggestions regex search terms deploy publish release cloudflare build static export pool index stats archives normalize live chat hub homepage" }, ], }, { label: "Machine", links: [ { href: "/jobs", label: "Jobs", icon: ListChecks, badgeKey: "jobs", keywords: "queue tasks active running live monitor stuck" }, { href: "/workers", label: "Workers", icon: Cpu, keywords: "transcription whisper gpu" }, { href: "/cleanup", label: "Cleanup", icon: Trash2, badgeKey: "cleanable", keywords: "clean reclaim disk space prune audio" }, { href: "/storage", label: "Storage", icon: HardDrive, keywords: "drive disk platter mount volume media location relocate cold" }, { href: "/saved-videos", label: "Saved videos", icon: Bookmark, keywords: "backup" }, { href: "/settings", label: "Settings", icon: Settings, keywords: "config preferences" }, { href: "/changelog", label: "Changelog", icon: ScrollText, badgeKey: "changelog", keywords: "release notes" }, ], }, ]; // /widget/builder IS NOT HERE, deliberately. The monitor widget is a PROJECTION // of the board's payload — the same lanes, the same figures, drawn for a second // screen — and a projection is not a place. It is reached from the pipeline // band on the dashboard, which is the thing it mirrors. // // THAT COSTS IT THE COMMAND PALETTE, and the cost is stated rather than papered // over: the palette renders NAV_GROUPS and has no way to type a URL, so leaving // the nav removes it from the palette too. "monitor" and "widget" are keywords // on Dashboard instead, which is where the link now lives — the palette takes // you to the page that has it, not to a dead end. // Flat list of every nav link (palette convenience). export const NAV_LINKS: NavLink[] = NAV_GROUPS.flatMap((g) => g.links);