import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter, Navigate, NavLink, Route, Routes, useParams, useSearchParams, } from "react-router"; import type { ServerStatus } from "../server"; import { AttentionBell } from "./attention-bell"; import { Screen, Scroller, Seg } from "./frame"; import { useLoaded } from "./lib/api"; import { cn } from "./lib/cn"; import { LOG_KEYS, Log, type LogFilter } from "./screens/log"; import { ProjectDetail } from "./screens/project-detail"; import { isStatusFilter, Projects, STATUS_FILTERS, type StatusFilter, } from "./screens/projects"; import { Settings } from "./screens/settings"; import { isLayout, LAYOUTS, type Layout, Today } from "./screens/today"; const LAYOUT_KEY = "pal.control-room.layout"; /** * A blueprint's registration marks are drawn 6px outside its border, so a scroll * box whose children are blueprints would count them as overflow. The negative * margin borrows that room back from the page gutter, leaving content aligned. */ const MARK_ROOM = "-m-2 p-2"; function rememberedLayout(): Layout { try { const stored = localStorage.getItem(LAYOUT_KEY); return isLayout(stored) ? stored : "matrix"; } catch { return "matrix"; } } function remember(layout: Layout): void { try { localStorage.setItem(LAYOUT_KEY, layout); } catch {} } function TodayRoute() { const [params, setParams] = useSearchParams(); const asked = params.get("layout"); const layout = isLayout(asked) ? asked : rememberedLayout(); return (
{ remember(next); setParams({ layout: next }); }} />
); } function ProjectsRoute() { const [params, setParams] = useSearchParams(); const asked = params.get("status"); const filter: StatusFilter = isStatusFilter(asked) ? asked : "ranked"; return ( setParams({ status: next })} /> } > ); } function ProjectDetailRoute() { const { slug } = useParams(); return ( ); } function LogRoute() { const [params, setParams] = useSearchParams(); const filter = Object.fromEntries( LOG_KEYS.map((key) => [key, params.get(key) ?? ""]) ) as LogFilter; const update = (next: Partial) => { const merged = { ...filter, ...next }; setParams(Object.fromEntries(Object.entries(merged).filter(([, v]) => v))); }; return ( every tool call this install recorded } > ); } function SettingsRoute() { return ( ); } const TABS = [ { to: "/", label: "Today", end: true }, { to: "/projects", label: "Projects", end: false }, { to: "/log", label: "Action log", end: false }, { to: "/settings", label: "Settings", end: false }, ] as const; function Header({ status }: { status: ServerStatus | null }) { return (
PAL control
{status?.machine ?? "…"} · {status ? `${status.ledgerFiles} ledger file(s)` : "…"} · port{" "} {status?.port ?? "…"}
); } function App() { const status = useLoaded("/api/status"); return (
} /> } /> } /> } /> } /> } />
loopback only · every number from ~/.pal · no model runs on this page {status.state === "ready" ? `up since ${status.data.startedAt}` : ""}
); } const root = document.getElementById("root"); if (root) createRoot(root).render( );