import { readFileSync } from "node:fs"; import type { Metadata } from "next"; import { Suspense } from "react"; import Link from "next/link"; import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import { getLatestChangelogDate } from "yt-dlp-transcript-common/lib/changelog"; import { listSites } from "yt-dlp-transcript-common/lib/site"; import { fontVars } from "yt-dlp-transcript-common/styles/fonts"; import { ThemeScript } from "yt-dlp-transcript-common/components/ThemeScript"; import { ThemeProvider } from "yt-dlp-transcript-common/components/ThemeProvider"; import { ThemeToggle } from "yt-dlp-transcript-common/components/ThemeToggle"; import { BrandMark } from "yt-dlp-transcript-common/components/BrandMark"; import { ICON_PALETTES } from "yt-dlp-transcript-common/lib/brand"; import { AppFrame } from "./components/AppFrame"; import { AutoRefresh } from "./components/AutoRefresh"; import { CommandPalette } from "./components/CommandPalette"; import { ChangelogNavLink } from "./components/ChangelogNavLink"; import { SiteScopeSelect } from "./components/SiteScopeSelect"; import { SiteScopeProvider } from "./components/SiteScopeProvider"; import { readActiveSite } from "./lib/activeSiteServer"; import { Toaster } from "yt-dlp-transcript-common/components/ui/sonner"; import { NAV_GROUPS, type NavLink } from "./lib/nav"; import { CleanableBadge } from "./components/CleanableBadge"; import { JobsBadge } from "./components/SidebarBadges"; import "./globals.css"; export async function generateMetadata(): Promise { const settings = getSettings(); return { title: { default: settings.adminTitle, template: `%s — ${settings.adminTitle}`, }, description: "Editor for yt-dlp-transcript-browser", }; } // NAV_GROUPS now lives in ./lib/nav (shared with the command palette). function readChangelogLatestDate(): string | null { try { const source = readFileSync(getPaths().editorChangelogFile, "utf8"); return getLatestChangelogDate(source); } catch { return null; } } export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const settings = getSettings(); const headerLabel = settings.adminTitle; const allJobs = getRegistry().list(); const activeJobs = allJobs.filter( (j) => j.status === "running" || j.status === "queued", ).length; const latestChangelogDate = readChangelogLatestDate(); const sites = listSites().map((s) => ({ siteId: s.siteId, siteTitle: s.siteTitle, })); const siteIds = sites.map((s) => s.siteId); // THE SITE SCOPE, read once per request from its cookie and supplied to the // picker (and anything else that asks) by SiteScopeProvider. A layout has no // searchParams, so a `?site=` link is applied by the picker from the URL and // by the scoped pages from their own searchParams. See app/lib/activeSite.ts. const scope = await readActiveSite(null, siteIds); const navItemClass = "px-2.5 py-1.5 rounded-md text-foreground/80 hover:text-foreground hover:bg-muted whitespace-nowrap flex items-center gap-2 transition-colors"; const renderLink = (link: NavLink) => { const Icon = link.icon; if (link.badgeKey === "changelog") { return (