Files
zopu-code/apps/web/src/root.tsx
-Puter aa33dd0993 feat(web): Work OS surface with Work Units, Signals, dual-mode composer
Lane C implementation of the Zopu Web Work OS for dogfooding:

- Work Unit card/detail projections built from per-issue events only
  (artifact counts from distinct artifact.updated paths, PR from
  gitea events, activity timeline, agent summaries). Signal linkage
  is explicitly 0/unavailable until Lane A relations are integrated.
- Persistent composer with Project and Work Unit mode switching.
  Project mode sends to the global Zopu agent; Work Unit mode sends
  to the issue-scoped project-manager agent identity.
- Collapsed Work Unit cards show title, summary, signal/step/artifact
  counts, current activity, PR indicator, needs-input indicator.
- Expanded Work Unit detail shows objective, timeline, artifacts,
  PR with directly usable link, needs-input alert, start action.
- Project-level Signals panel in the sidebar (not attributed to any
  issue until a signal-to-issue relation exists).
- Dark theme with calm, Apple-like visual direction.
- 21 projection tests including 5 cross-issue isolation tests and
  2 signal isolation tests proving one issue cannot inherit
  another issue's artifacts, PR, summary, or project signals.
- Mobile-responsive: detail overlay on mobile, sidebar on desktop.

No backend or schema changes. Uses existing Convex contracts and
Flue agent transport throughout.
2026-07-24 20:59:48 +05:30

124 lines
3.2 KiB
TypeScript

import { useConvexAccessToken, WebAuthProvider } from "@code/auth/web";
import { env } from "@code/env/web";
import { Toaster } from "@code/ui/components/sonner";
import { FlueProvider } from "@flue/react";
import "./index.css";
import { createFlueClient } from "@flue/sdk";
import { useMemo } from "react";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import { ThemeProvider } from "./components/theme-provider";
import { createFlueFetch } from "./lib/flue-transport";
export const links: Route.LinksFunction = () => [
{ href: "https://fonts.googleapis.com", rel: "preconnect" },
{
crossOrigin: "anonymous",
href: "https://fonts.gstatic.com",
rel: "preconnect",
},
{
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
rel: "stylesheet",
},
];
const flueBaseUrl = new URL(env.VITE_FLUE_URL);
const flueFetch = createFlueFetch({ baseUrl: flueBaseUrl });
const AuthenticatedFlueProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const resolveAccessToken = useConvexAccessToken();
const client = useMemo(
() =>
createFlueClient({
baseUrl: flueBaseUrl.toString(),
fetch: flueFetch,
headers: async () => {
const accessToken = await resolveAccessToken();
const headers: Record<string, string> = {};
if (accessToken) {
headers.authorization = `Bearer ${accessToken}`;
}
return headers;
},
}),
[resolveAccessToken]
);
return <FlueProvider client={client}>{children}</FlueProvider>;
};
export const Layout = ({ children }: { children: React.ReactNode }) => (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="bg-[#0e0e0d]">
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
const App = () => (
<WebAuthProvider>
<AuthenticatedFlueProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
forcedTheme="dark"
disableTransitionOnChange
storageKey="vite-ui-theme"
>
<Outlet />
<Toaster richColors />
</ThemeProvider>
</AuthenticatedFlueProvider>
</WebAuthProvider>
);
export default App;
export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
const { status, statusText } = error;
message = status === 404 ? "404" : "Error";
details =
status === 404
? "The requested page could not be found."
: statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
({ message: details, stack } = error);
}
return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
};