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.
124 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
};
|