import { configureTracking } from "@agent-native/core/client/analytics";
import { appPath } from "@agent-native/core/client/api-path";
import {
AppProviders,
callAction,
createAgentNativeQueryClient,
useDbSync,
} from "@agent-native/core/client/hooks";
import { getLocaleInitScript } from "@agent-native/core/client/i18n";
import { getThemeInitScript } from "@agent-native/core/client/ui";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLocation,
} from "react-router";
import type { LinksFunction } from "react-router";
import { AuthProvider } from "@/components/auth/AuthProvider";
import { ProviderCorpusJobNotifier } from "@/components/ProviderCorpusJobNotifier";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { Toaster } from "@/components/ui/toaster";
import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
import { notifyProviderCorpusJobSyncEvent } from "@/lib/provider-corpus-job-sync";
import { TAB_ID } from "@/lib/tab-id";
import { CommandPalette } from "./components/layout/CommandPalette";
import { Layout as AppLayout } from "./components/layout/Layout";
import { i18nCatalog } from "./i18n";
import stylesheet from "./global.css?url";
configureTracking({
getDefaultProps: (_name, properties) => ({
...properties,
app: "agent-native-analytics",
}),
});
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
const THEME_INIT_SCRIPT = getThemeInitScript("dark", true);
const LOCALE_INIT_SCRIPT = getLocaleInitScript();
export function Layout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
function DbSyncBridge() {
// Invalidate react-query caches on DB changes (agent edits, other tabs,
// cron jobs). SQL chart queries can be expensive, so they stay on explicit
// refresh/filter semantics instead of joining the broad action fallback.
// Screen-refresh is handled automatically inside AgentSidebar.
const queryClient = useQueryClient();
useDbSync({
queryClient,
ignoreSource: TAB_ID,
onEvent: notifyProviderCorpusJobSyncEvent,
actionInvalidatePredicate: shouldInvalidateAnalyticsQueryForAction,
// These boot-time maintenance calls update their own local state and do
// not imply that every mounted Analytics query needs to restart.
suppressActionInvalidationFor: [
"ensure-demo-dashboards",
"manage-agent-engine",
],
});
return null;
}
export function shouldInvalidateAnalyticsQueryForAction(query: {
queryKey: readonly unknown[];
}): boolean {
const [scope, name] = query.queryKey;
if (
scope === "sql-chart" ||
scope === "sql-dashboards-sidebar" ||
scope === "analyses-sidebar" ||
scope === "extensions"
) {
return false;
}
// The notifier refreshes for corpus-job events and only polls while a job is
// actively running. Unrelated actions must not restart its idle query.
if (scope === "action" && name === "provider-corpus-jobs") return false;
return true;
}
function DemoDashboardInstaller() {
useEffect(() => {
void callAction("ensure-demo-dashboards", {}).catch((err) => {
console.warn("[analytics] demo dashboard install failed", err);
});
}, []);
return null;
}
export default function Root() {
const [queryClient] = useState(() => createAgentNativeQueryClient());
const location = useLocation();
// Public, unauthenticated uptime status pages (`/status/`) render
// SSR-first without the authenticated app chrome (sidebar/chat/command
// palette). See app/routes/status.$slug.tsx and the `/status` public path in
// server/plugins/auth.ts.
const isPublicStatusPath =
location.pathname === "/status" || location.pathname.startsWith("/status/");
if (isPublicStatusPath) {
return (
);
}
return (
// defaultTheme="dark": analytics defaults to dark mode if no stored preference.
// toaster={null}: suppress AppProviders' built-in sonner; analytics renders
// both its styled Sonner and the legacy shadcn Toaster explicitly below.
);
}
export { ErrorBoundary } from "@agent-native/core/client/ui";