Fixes 3 remaining UI items from the component-wiring audit: - Memory page: project selector dropdown (Item 1) - Agent events: collision_warning + agent_message toasts via sonner (Item 2) - Reasoning delta already wired and working (Item 3) Also picks up uncommitted boocontext rename changes from the subagent batch: - synthesisPipeline.ts tier tool names updated - tiers.ts STANDARD_TOOL_NAMES clears old codecontext tools - tool-utils.ts BUILT_IN_TOOLS updated - .env.example / README.md reference boocontext MCP - ROADMAP.md boocontext entry - codecontext/ dir + docs/codecontext-ts-plan.md removed (already gone from tree)
164 lines
6.1 KiB
TypeScript
164 lines
6.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { BrowserRouter, Routes, Route, useParams } from 'react-router-dom';
|
|
import { api } from '@/api/client';
|
|
import { ProjectSidebar } from '@/components/ProjectSidebar';
|
|
import { RightRail } from '@/components/RightRail';
|
|
import { Home } from '@/pages/Home';
|
|
import { Project } from '@/pages/Project';
|
|
import { Session } from '@/pages/Session';
|
|
import { Settings } from '@/pages/Settings';
|
|
import { Analytics } from '@/pages/Analytics';
|
|
import { Results } from '@/pages/Results';
|
|
import { Memory } from '@/pages/Memory';
|
|
import { Toaster } from '@/components/ui/sonner';
|
|
import { toast } from 'sonner';
|
|
import { useUserEvents } from '@/hooks/useUserEvents';
|
|
import { useCoderUserEvents } from '@/hooks/useCoderUserEvents';
|
|
import { sessionEvents } from '@/hooks/sessionEvents';
|
|
import { useTheme } from '@/lib/theme';
|
|
import { SidebarDrawerProvider, useSidebarDrawer } from '@/hooks/useSidebarDrawer';
|
|
import { RightRailDrawerProvider, useRightRailDrawer } from '@/hooks/useRightRailDrawer';
|
|
import { useViewport } from '@/hooks/useViewport';
|
|
import { ThemeFx } from '@/components/fx/ThemeFx';
|
|
import { FlowLauncherDialog } from '@/components/FlowLauncherDialog';
|
|
import { ArenaLauncherDialog } from '@/components/ArenaLauncherDialog';
|
|
import { KeyboardShortcutsDialog } from '@/components/KeyboardShortcutsDialog';
|
|
|
|
function SessionRightRail() {
|
|
const { id } = useParams<{ id: string }>();
|
|
if (!id) return null;
|
|
return <RightRailForSession sessionId={id} />;
|
|
}
|
|
|
|
function RightRailForSession({ sessionId }: { sessionId: string }) {
|
|
const [projectId, setProjectId] = useState<string | null>(null);
|
|
useEffect(() => {
|
|
api.sessions
|
|
.get(sessionId)
|
|
.then((s) => setProjectId(s.project_id))
|
|
.catch((err) => console.warn('RightRail: failed to fetch session', err));
|
|
}, [sessionId]);
|
|
if (!projectId) return null;
|
|
// v1.6.2: rendered on all viewports. On mobile, RightRail itself renders as
|
|
// a right-side drawer toggled by the header's FolderTree button (via
|
|
// useRightRailDrawer). On desktop, it renders inline as before with its
|
|
// own internal open/close state.
|
|
return <RightRail projectId={projectId} sessionId={sessionId} />;
|
|
}
|
|
|
|
function MobileBackdrop() {
|
|
const { open, setOpen } = useSidebarDrawer();
|
|
const { isMobile } = useViewport();
|
|
if (!isMobile || !open) return null;
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/40 md:hidden"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function MobileRightRailBackdrop() {
|
|
const { open, setOpen } = useRightRailDrawer();
|
|
const { isMobile } = useViewport();
|
|
if (!isMobile || !open) return null;
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/40 md:hidden"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function AppShell() {
|
|
// themes-v1: useTheme() owns the matchMedia subscription for system mode
|
|
// and reconciles cache with /api/settings on mount. Mounted first so the
|
|
// theme class on <html> is correct before any child renders.
|
|
useTheme();
|
|
useUserEvents();
|
|
useCoderUserEvents();
|
|
useEffect(() => {
|
|
const unsub = sessionEvents.subscribe((event) => {
|
|
if (event.type === 'collision_warning') {
|
|
toast.warning(`Multiple agents editing ${event.file_path}`, {
|
|
description: `Agents: ${event.agents.join(', ')}`,
|
|
});
|
|
} else if (event.type === 'agent_message') {
|
|
const truncated =
|
|
event.content.length > 80
|
|
? event.content.slice(0, 80) + '…'
|
|
: event.content;
|
|
toast.info(`Message from ${event.from_agent}`, {
|
|
description: truncated,
|
|
});
|
|
}
|
|
});
|
|
return unsub;
|
|
}, []);
|
|
const [showShortcuts, setShowShortcuts] = useState(false);
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (e.key === '?' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
const tag = (e.target as HTMLElement)?.tagName;
|
|
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !(e.target as HTMLElement)?.isContentEditable) {
|
|
setShowShortcuts((v) => !v);
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener('keydown', handler);
|
|
return () => window.removeEventListener('keydown', handler);
|
|
}, []);
|
|
// v1.10.8c: h-dvh (dynamic viewport) instead of h-screen (100vh) so the
|
|
// root height excludes the iOS URL-bar overlay area. Without this, every
|
|
// descendant — including the terminal pane — measures itself against a
|
|
// height that extends behind the URL bar, and xterm allocates extra rows
|
|
// that scroll out of reach on iPhone.
|
|
//
|
|
// ThemeFx: canvas-based animated backgrounds (Classic rain, Override neon
|
|
// field) rendered at z-0 behind the content wrapper. ThemeFx also manages
|
|
// the bc-anim-on gate class on <html>. Content wrapper at z-10 ensures all
|
|
// UI sits above any fixed canvas regardless of theme.
|
|
return (
|
|
<>
|
|
<ThemeFx />
|
|
<div className="h-dvh flex bg-background text-foreground relative z-10">
|
|
<ProjectSidebar />
|
|
<MobileBackdrop />
|
|
<main className="flex-1 flex flex-col min-w-0">
|
|
<Routes>
|
|
<Route path="/" element={<Home />} />
|
|
<Route path="/project/:id" element={<Project />} />
|
|
<Route path="/session/:id" element={<Session />} />
|
|
<Route path="/settings" element={<Settings />} />
|
|
<Route path="/analytics" element={<Analytics />} />
|
|
<Route path="/results" element={<Results />} />
|
|
<Route path="/memory" element={<Memory />} />
|
|
</Routes>
|
|
</main>
|
|
<MobileRightRailBackdrop />
|
|
<Routes>
|
|
<Route path="/session/:id" element={<SessionRightRail />} />
|
|
</Routes>
|
|
<Toaster position="bottom-right" />
|
|
<FlowLauncherDialog />
|
|
<ArenaLauncherDialog />
|
|
<KeyboardShortcutsDialog open={showShortcuts} onOpenChange={setShowShortcuts} />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<SidebarDrawerProvider>
|
|
<RightRailDrawerProvider>
|
|
<AppShell />
|
|
</RightRailDrawerProvider>
|
|
</SidebarDrawerProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|