import type { ReactNode } from "react"; import Link from "next/link"; import { notFound } from "next/navigation"; import { isSocialChannel } from "yt-dlp-transcript-common/lib/channelConfig"; import { ChannelTabs } from "./components/ChannelTabs"; import { RefreshSnapshotButton } from "./components/RefreshSnapshotButton"; import { readChannelConfigCached } from "./lib/channelConfigCache"; // Shared chrome for every channel route: the breadcrumb, the name, the refresh // control and the Overview | Videos switch. This is what StatusHeader used to // be, moved up a level now that the channel has more than one page. // // Next 16 caveats this file is written around (see // node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/layout.md): // // - LAYOUTS DO NOT RERENDER on navigation and cannot read searchParams. So the // active-tab check lives in a client component (ChannelTabs), and anything // that varies with `?stage=` — the report freshness, the stage panels — stays // in page.tsx. // - NO loading.tsx HERE. In this app it turns notFound() 404s into 200s and // never shows on a client-side sibling navigation anyway. // // force-dynamic for the same reason page.tsx carries it: this reads the channel // config off disk on every request and must never be statically prerendered. export const dynamic = "force-dynamic"; export default async function ChannelLayout({ children, params, }: { children: ReactNode; params: Promise<{ slug: string }>; }) { const { slug } = await params; // Deduped with page.tsx's read of the same file via React cache(). const config = await readChannelConfigCached(slug); if (!config) notFound(); return (