import { AgentSidebar } from "@agent-native/core/client/agent-chat"; import { configureTracking } from "@agent-native/core/client/analytics"; import { appPath } from "@agent-native/core/client/api-path"; import { AppProviders, createAgentNativeQueryClient, useActionQuery, } from "@agent-native/core/client/hooks"; import { getLocaleInitScript, type LocaleCode, type LocaleMessages, type LocalizationPreference, useT, } from "@agent-native/core/client/i18n"; import { CommandMenu, useCommandMenuShortcut, } from "@agent-native/core/client/navigation"; import { ErrorReportActions, getThemeInitScript, } from "@agent-native/core/client/ui"; import { resolveLocaleFromRequest } from "@agent-native/core/server"; import type { ListContentDatabasesResponse } from "@shared/api"; import { IconDatabase, IconDeviceDesktop, IconHierarchy2, IconFileText, IconFolderOpen, IconLoader2, IconMoon, IconSun, } from "@tabler/icons-react"; import { useTheme } from "next-themes"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse, useLoaderData, useLocation, useNavigate, useNavigation, useRouteLoaderData, useRouteError, } from "react-router"; import type { LinksFunction, LoaderFunctionArgs, ShouldRevalidateFunctionArgs, } from "react-router"; // Styled sonner wrapper — passed via AppProviders `toaster` prop to avoid duplicate. import { Toaster as Sonner } from "@/components/ui/sonner"; // shadcn useToast-based toaster — separate from sonner, must stay inline. import { Toaster } from "@/components/ui/toaster"; import { AppToolkitProvider } from "@/components/ui/toolkit-provider"; import changelog from "../CHANGELOG.md?raw"; import { useDbSync } from "./hooks/use-db-sync"; import { useNavigationState } from "./hooks/use-navigation-state"; import { i18nCatalog } from "./i18n"; import { contentCommandDocumentPath, groupContentCommandSearchResults, type CommandSearchDocumentsResponse, } from "./lib/content-command-search"; import stylesheet from "./global.css?url"; import katexStylesheet from "katex/dist/katex.min.css?url"; configureTracking({ getDefaultProps: (_name, properties) => ({ ...properties, app: "agent-native-content", }), }); export const links: LinksFunction = () => [ { rel: "stylesheet", href: stylesheet }, { rel: "stylesheet", href: katexStylesheet }, ]; interface RootLoaderData { locale: LocaleCode; preference: LocalizationPreference; dir: "ltr" | "rtl"; messages: LocaleMessages; } export async function loader({ request, }: LoaderFunctionArgs): Promise { const resolved = resolveLocaleFromRequest({ request }); const messages = ((await i18nCatalog.loadMessages?.(resolved.locale)) as | LocaleMessages | null | undefined) ?? i18nCatalog.messages; return { locale: resolved.locale, preference: resolved.preference, dir: resolved.dir, messages, }; } export function shouldRevalidate({ defaultShouldRevalidate, formMethod, }: ShouldRevalidateFunctionArgs) { return formMethod ? defaultShouldRevalidate : false; } // Pass args to match content's 3-way theme-cycle UX (no disableTransitionOnChange). const THEME_INIT_SCRIPT = getThemeInitScript("system", true); const DEFAULT_LOADER_DATA: RootLoaderData = { locale: "en-US", preference: { locale: "system" }, dir: "ltr", messages: i18nCatalog.messages, }; const themeOptions = [ { value: "system", label: "System", icon: IconDeviceDesktop }, { value: "light", label: "Light", icon: IconSun }, { value: "dark", label: "Dark", icon: IconMoon }, ] as const; const THEME_PREFERENCE_STORAGE_KEY = "content-theme-preference"; type ThemeOption = (typeof themeOptions)[number]["value"]; function isThemeOption(value: string | null | undefined): value is ThemeOption { return value === "light" || value === "system" || value === "dark"; } function readStoredThemePreference(): ThemeOption { if (typeof window === "undefined") return "system"; try { const storedTheme = window.localStorage.getItem( THEME_PREFERENCE_STORAGE_KEY, ); if (storedTheme === "auto") return "system"; return isThemeOption(storedTheme) ? storedTheme : "system"; } catch { return "system"; } } function writeStoredThemePreference(theme: ThemeOption) { if (typeof window === "undefined") return; try { window.localStorage.setItem(THEME_PREFERENCE_STORAGE_KEY, theme); } catch { // Ignore storage failures and still let next-themes update the page. } } function nextTheme(theme: ThemeOption): ThemeOption { const currentIndex = themeOptions.findIndex( (option) => option.value === theme, ); return themeOptions[(currentIndex + 1) % themeOptions.length].value; } export function Layout({ children }: { children: React.ReactNode }) { const loaderData = useRouteLoaderData("root") ?? DEFAULT_LOADER_DATA; const localeInitScript = getLocaleInitScript({ locale: loaderData.locale, preference: loaderData.preference, messages: loaderData.messages, }); return (