import {
BrowserRouter,
Navigate,
Outlet,
Route,
Routes,
useLocation,
useNavigate,
} from "react-router-dom";
import AppLayout from "@cloudscape-design/components/app-layout";
import Badge from "@cloudscape-design/components/badge";
import Button from "@cloudscape-design/components/button";
import SideNavigation from "@cloudscape-design/components/side-navigation";
import TopNavigation from "@cloudscape-design/components/top-navigation";
import Flashbar, {
type FlashbarProps,
} from "@cloudscape-design/components/flashbar";
import {
type ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { Helmet } from "react-helmet-async";
import { PoolsList } from "./pages/PoolsList";
import { ClaimDetail } from "./pages/ClaimDetail";
import { InstanceDetail } from "./pages/InstanceDetail";
import { PoolDetail } from "./pages/PoolDetail";
import { PoolNew } from "./pages/PoolNew";
import { UserApiKeys } from "./pages/UserApiKeys";
import { Settings } from "./pages/Settings";
import { AgentChat } from "./pages/AgentChat";
import { ArchivedThreads } from "./pages/ArchivedThreads";
import { BillingUsagePage } from "./pages/BillingUsage";
import { PageShell } from "./components/PageShell";
import { FeatureFlags } from "./pages/FeatureFlags";
import {
FeatureFlagProvider,
useFeatureFlags,
} from "./components/FeatureFlagContext";
import { AdminRoute } from "./components/AdminRoute";
import { FlashContext, type FlashMsg } from "./components/FlashContext";
import { logout, userInfo } from "./auth/keycloak";
import cuaMark from "@cua/design/assets/brand/cua-mark-white.svg";
import designTokens from "@cua/design/tokens.json";
import { localVisualPreviewPath } from "./local-visual-preview";
import { ChatThreadsProvider } from "./chat/ChatThreadsContext";
import {
isThreadNavigationHref,
useThreadNavigation,
} from "./components/ThreadNavigation";
import { useI18n } from "./i18n/I18nProvider";
const VERSION_CHECK_INTERVAL_MS = 60_000;
// Cloudscape icon paths (@cloudscape-design/components/icon), inlined as
// real DOM nodes so they inherit `currentColor` from the nav link's text
// color — matches hover/active states without a second theming pass.
const SVG_OPEN =
'`,
history: `${SVG_OPEN}`,
key: `${SVG_OPEN}`,
settings: `${SVG_OPEN}`,
flag: `${SVG_OPEN}`,
"add-plus": `${SVG_OPEN}`,
folder: `${SVG_OPEN}`,
};
function TitledPage({ title, children }: { title: string; children: ReactNode }) {
return (
<>
{title} · Cua
{children}
>
);
}
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches,
);
useEffect(() => {
const media = window.matchMedia(query);
const update = () => setMatches(media.matches);
media.addEventListener("change", update);
return () => media.removeEventListener("change", update);
}, [query]);
return matches;
}
function useStaleCheck(): { stale: boolean } {
const [stale, setStale] = useState(false);
const currentEntry = useRef(null);
useEffect(() => {
if (stale) return;
// The entry script is what's actually executing; lazy chunk hashes
// changing don't make the running app stale on their own.
const entry = Array.from(
document.querySelectorAll(
'script[type="module"][src]',
),
)
.map((s) => s.src)
.find((src) => src.includes("/assets/"));
if (!entry) return;
currentEntry.current = entry;
const check = async () => {
try {
// High-signal: if the CDN no longer serves our entry chunk,
// the deploy invalidated us — next dynamic import would fail anyway.
const head = await fetch(currentEntry.current!, {
method: "HEAD",
cache: "no-cache",
});
if (head.status === 404) {
setStale(true);
return;
}
const res = await fetch("/", { cache: "no-cache" });
const html = await res.text();
const match = html.match(
/