feat(web): build read-only operator views
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
bin/
|
||||
dist/
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
apps/web/dist/
|
||||
apps/web/.vite/
|
||||
*.db
|
||||
|
||||
@@ -24,9 +24,12 @@
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.1"
|
||||
}
|
||||
|
||||
+153
-56
@@ -6,24 +6,65 @@ import {
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
useRouterState,
|
||||
} from '@tanstack/react-router';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
import { cn } from './cadence-ui/lib/cn';
|
||||
import {
|
||||
BlockedQueuePage,
|
||||
RunDetailPage,
|
||||
RunsPage,
|
||||
ThreadTimelinePage,
|
||||
} from './features/operator-console';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5_000,
|
||||
refetchOnWindowFocus: true,
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const rootRoute = createRootRoute({
|
||||
component: RootLayout,
|
||||
});
|
||||
|
||||
const indexRoute = createRoute({
|
||||
const runsRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: HomePage,
|
||||
component: RunsPage,
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([indexRoute]);
|
||||
const blockedQueueRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/blocked',
|
||||
component: BlockedQueuePage,
|
||||
});
|
||||
|
||||
const runDetailRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/runs/$runId',
|
||||
component: RunDetailRoute,
|
||||
});
|
||||
|
||||
const threadTimelineRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/threads/$threadId',
|
||||
component: ThreadTimelineRoute,
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([
|
||||
runsRoute,
|
||||
blockedQueueRoute,
|
||||
runDetailRoute,
|
||||
threadTimelineRoute,
|
||||
]);
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
defaultPreload: 'intent',
|
||||
});
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
@@ -42,63 +83,119 @@ export function App() {
|
||||
|
||||
function RootLayout() {
|
||||
return (
|
||||
<div className="shell">
|
||||
<header className="masthead">
|
||||
<div>
|
||||
<p className="eyebrow">AI Workflow Skill</p>
|
||||
<Link className="brand" to="/">
|
||||
Orch Control Plane
|
||||
</Link>
|
||||
<div className="relative min-h-screen overflow-hidden" data-theme="brand">
|
||||
<div className="pointer-events-none fixed inset-0">
|
||||
<div className="absolute inset-x-0 top-0 h-[38rem] bg-[radial-gradient(circle_at_top,oklch(0.9_0.06_174/.72),transparent_60%)]" />
|
||||
<div className="absolute inset-y-0 right-0 w-[32rem] bg-[radial-gradient(circle_at_center,oklch(0.82_0.14_88/.18),transparent_56%)]" />
|
||||
<div className="absolute inset-0 opacity-35 [background-image:linear-gradient(to_right,color-mix(in_oklch,var(--color-border)_46%,transparent)_1px,transparent_1px),linear-gradient(to_bottom,color-mix(in_oklch,var(--color-border)_46%,transparent)_1px,transparent_1px)] [background-size:72px_72px]" />
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto flex min-h-screen w-full max-w-[1500px] flex-col gap-6 px-4 py-4 lg:flex-row lg:px-6 lg:py-6">
|
||||
<aside className="hidden lg:flex lg:w-[18rem] lg:shrink-0">
|
||||
<div className="flex h-[calc(100vh-3rem)] w-full flex-col rounded-[2rem] border border-[color-mix(in_oklch,var(--color-border)_80%,white)] bg-[color-mix(in_oklch,var(--color-card)_84%,white_16%)] p-5 shadow-[var(--shadow-md)] backdrop-blur">
|
||||
<div className="space-y-4">
|
||||
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.28em] text-[var(--color-primary)]">
|
||||
AI Workflow Skill
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<Link
|
||||
className="type-display text-[2.4rem] leading-[0.92] tracking-[-0.05em] text-[var(--color-foreground)]"
|
||||
to="/"
|
||||
>
|
||||
Orch Control Plane
|
||||
</Link>
|
||||
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
|
||||
Read-only operator surfaces for runs, blocked work, and thread history.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="mt-8 grid gap-2">
|
||||
<NavLink exact label="Runs overview" to="/" />
|
||||
<NavLink label="Blocked queue" to="/blocked" />
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto rounded-[1.5rem] border border-[color-mix(in_oklch,var(--color-border)_78%,transparent)] bg-[color-mix(in_oklch,var(--color-surface)_82%,white_18%)] p-4">
|
||||
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.24em] text-[var(--color-muted-foreground)]">
|
||||
Current slice
|
||||
</p>
|
||||
<ul className="mt-3 grid gap-2 text-sm leading-6 text-[var(--color-foreground)]">
|
||||
<li>Runs list with health counts</li>
|
||||
<li>Run detail with task board</li>
|
||||
<li>Global blocked queue</li>
|
||||
<li>Thread timeline and artifacts</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-4">
|
||||
<header className="rounded-[1.75rem] border border-[color-mix(in_oklch,var(--color-border)_80%,white)] bg-[color-mix(in_oklch,var(--color-card)_84%,white_16%)] px-5 py-4 shadow-[var(--shadow-sm)] backdrop-blur lg:hidden">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="space-y-2">
|
||||
<p className="text-[0.68rem] font-semibold uppercase tracking-[0.28em] text-[var(--color-primary)]">
|
||||
AI Workflow Skill
|
||||
</p>
|
||||
<Link
|
||||
className="type-display block text-[2rem] leading-[0.94] tracking-[-0.05em] text-[var(--color-foreground)]"
|
||||
to="/"
|
||||
>
|
||||
Orch Control Plane
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<NavLink exact label="Runs overview" to="/" />
|
||||
<NavLink label="Blocked queue" to="/blocked" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="min-w-0 flex-1">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
<p className="masthead-copy">
|
||||
Phase 1 keeps the UI thin while the backend contract settles around
|
||||
`orchd`.
|
||||
</p>
|
||||
</header>
|
||||
<main className="content">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HomePage() {
|
||||
function NavLink({
|
||||
exact = false,
|
||||
label,
|
||||
to,
|
||||
}: {
|
||||
exact?: boolean;
|
||||
label: string;
|
||||
to: string;
|
||||
}) {
|
||||
const pathname = useRouterState({
|
||||
select: (state) => state.location.pathname,
|
||||
});
|
||||
const isActive = exact ? pathname === to : pathname === to || pathname.startsWith(`${to}/`);
|
||||
|
||||
return (
|
||||
<section className="hero-grid">
|
||||
<article className="hero-card hero-card-primary">
|
||||
<p className="eyebrow">Current slice</p>
|
||||
<h1>Read-only operator shell for a future multi-user web product.</h1>
|
||||
<p className="lede">
|
||||
The monorepo now has a dedicated React app, a Go HTTP service, and a
|
||||
first API contract for runs, blocked work, and thread history.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="hero-card hero-card-secondary">
|
||||
<h2>Backend spine</h2>
|
||||
<ul className="detail-list">
|
||||
<li>`cmd/orchd` serves `chi` routes against the existing SQLite state.</li>
|
||||
<li>`internal/query` shapes run, blocked-task, and thread reads.</li>
|
||||
<li>`api/openapi.yaml` is the contract anchor for future typed clients.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article className="hero-card">
|
||||
<h2>Frontend posture</h2>
|
||||
<p>
|
||||
React, Vite, TanStack Router, and TanStack Query are present now so
|
||||
Phase 2 can focus on actual operator views instead of build plumbing.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article className="hero-card">
|
||||
<h2>Next UI targets</h2>
|
||||
<ul className="detail-list">
|
||||
<li>Runs dashboard with task-count health signals.</li>
|
||||
<li>Run detail board grouped by orchestration state.</li>
|
||||
<li>Blocked queue and thread timeline wired to live refresh.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
<Link
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-full border px-3.5 py-2 text-sm font-medium transition-colors',
|
||||
'border-[color-mix(in_oklch,var(--color-border)_76%,transparent)] bg-transparent text-[var(--color-muted-foreground)]',
|
||||
'hover:border-[var(--color-border-strong)] hover:bg-[color-mix(in_oklch,var(--color-surface)_78%,white_22%)] hover:text-[var(--color-foreground)]',
|
||||
isActive &&
|
||||
'border-[color-mix(in_oklch,var(--color-primary)_32%,var(--color-border))] bg-[color-mix(in_oklch,var(--color-primary)_12%,white)] text-[var(--color-primary)]',
|
||||
)}
|
||||
to={to}
|
||||
>
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-current" />
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function RunDetailRoute() {
|
||||
const { runId } = runDetailRoute.useParams();
|
||||
return <RunDetailPage runId={runId} />;
|
||||
}
|
||||
|
||||
function ThreadTimelineRoute() {
|
||||
const { threadId } = threadTimelineRoute.useParams();
|
||||
return <ThreadTimelinePage threadId={threadId} />;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
export type Run = {
|
||||
run_id: string;
|
||||
goal: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type RunListItem = {
|
||||
run: Run;
|
||||
task_counts: Record<string, number>;
|
||||
total_tasks: number;
|
||||
};
|
||||
|
||||
export type Task = {
|
||||
run_id: string;
|
||||
task_id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
default_to?: string;
|
||||
priority: string;
|
||||
acceptance_json: unknown;
|
||||
latest_attempt_no?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TaskAttempt = {
|
||||
run_id: string;
|
||||
task_id: string;
|
||||
attempt_no: number;
|
||||
assigned_to: string;
|
||||
thread_id: string;
|
||||
base_ref?: string;
|
||||
base_commit?: string;
|
||||
branch_name?: string;
|
||||
worktree_path?: string;
|
||||
workspace_status?: string;
|
||||
result_commit?: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type Artifact = {
|
||||
artifact_id: string;
|
||||
message_id: string;
|
||||
path: string;
|
||||
kind: string;
|
||||
metadata_json: unknown;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type Message = {
|
||||
message_id: string;
|
||||
thread_id: string;
|
||||
from_agent: string;
|
||||
to_agent: string;
|
||||
kind: string;
|
||||
summary: string;
|
||||
body: string;
|
||||
payload_json: unknown;
|
||||
created_at: string;
|
||||
artifacts: Artifact[];
|
||||
};
|
||||
|
||||
export type Thread = {
|
||||
thread_id: string;
|
||||
run_id: string;
|
||||
task_id: string;
|
||||
subject: string;
|
||||
created_by: string;
|
||||
assigned_to: string;
|
||||
status: string;
|
||||
priority: string;
|
||||
latest_message_id?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type ThreadDetail = {
|
||||
thread: Thread;
|
||||
messages: Message[];
|
||||
};
|
||||
|
||||
export type BlockedTask = {
|
||||
task: Task;
|
||||
attempt: TaskAttempt;
|
||||
question: Message;
|
||||
};
|
||||
|
||||
export type BlockedQueueItem = BlockedTask & {
|
||||
run: Run;
|
||||
};
|
||||
|
||||
export type RunDetail = {
|
||||
run: Run;
|
||||
task_counts: Record<string, number>;
|
||||
total_tasks: number;
|
||||
tasks: Task[];
|
||||
blocked_tasks: BlockedTask[];
|
||||
};
|
||||
|
||||
type ErrorEnvelope = {
|
||||
error?: {
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
|
||||
class ApiError extends Error {
|
||||
code?: string;
|
||||
status: number;
|
||||
|
||||
constructor(message: string, status: number, code?: string) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.code = code;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
const apiBaseUrl = (import.meta.env.VITE_ORCH_API_BASE_URL ?? '').replace(/\/$/, '');
|
||||
|
||||
async function request<T>(path: string): Promise<T> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let message = `Request failed with status ${response.status}`;
|
||||
let code: string | undefined;
|
||||
|
||||
try {
|
||||
const payload = (await response.json()) as ErrorEnvelope;
|
||||
message = payload.error?.message ?? message;
|
||||
code = payload.error?.code;
|
||||
} catch {
|
||||
// Ignore malformed error payloads and fall back to the HTTP status.
|
||||
}
|
||||
|
||||
throw new ApiError(message, response.status, code);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
export async function listRuns() {
|
||||
const payload = await request<{ runs: RunListItem[] }>('/api/runs');
|
||||
return payload.runs;
|
||||
}
|
||||
|
||||
export async function getRunDetail(runId: string) {
|
||||
const payload = await request<{ run: RunDetail }>(`/api/runs/${encodeURIComponent(runId)}`);
|
||||
return payload.run;
|
||||
}
|
||||
|
||||
export async function listRunBlocked(runId: string) {
|
||||
const payload = await request<{ blocked: BlockedTask[] }>(
|
||||
`/api/runs/${encodeURIComponent(runId)}/blocked`,
|
||||
);
|
||||
return payload.blocked;
|
||||
}
|
||||
|
||||
export async function getThreadDetail(threadId: string) {
|
||||
const payload = await request<{ thread: ThreadDetail }>(
|
||||
`/api/threads/${encodeURIComponent(threadId)}`,
|
||||
);
|
||||
return {
|
||||
...payload.thread,
|
||||
messages: payload.thread.messages.map((message) => ({
|
||||
...message,
|
||||
artifacts: message.artifacts ?? [],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listBlockedQueue() {
|
||||
const runs = await listRuns();
|
||||
const blockedByRun = await Promise.all(
|
||||
runs.map(async (item) => {
|
||||
const blocked = await listRunBlocked(item.run.run_id);
|
||||
return blocked.map((entry) => ({
|
||||
...entry,
|
||||
run: item.run,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return blockedByRun
|
||||
.flat()
|
||||
.sort((left, right) => Date.parse(right.question.created_at) - Date.parse(left.question.created_at));
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
export function formatDateTime(value: string) {
|
||||
const parsed = new Date(value);
|
||||
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
}).format(parsed);
|
||||
}
|
||||
|
||||
export function statusLabel(value: string) {
|
||||
return value.replaceAll('_', ' ');
|
||||
}
|
||||
|
||||
export function pluralize(count: number, singular: string) {
|
||||
return `${count} ${count === 1 ? singular : `${singular}s`}`;
|
||||
}
|
||||
|
||||
export function hasStructuredData(value: unknown): boolean {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value.trim().length > 0 && value.trim() !== '{}' && value.trim() !== '[]';
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return Object.keys(value as Record<string, unknown>).length > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function formatJson(value: unknown) {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function uniqueCount<T>(items: T[], select: (item: T) => string | undefined) {
|
||||
const values = items
|
||||
.map(select)
|
||||
.filter((value): value is string => Boolean(value));
|
||||
|
||||
return new Set(values).size;
|
||||
}
|
||||
+15
-133
@@ -1,33 +1,25 @@
|
||||
@import "tailwindcss";
|
||||
@source "./**/*.{ts,tsx}";
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI", sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 196, 98, 0.32), transparent 28%),
|
||||
radial-gradient(circle at bottom right, rgba(8, 145, 178, 0.16), transparent 26%),
|
||||
linear-gradient(135deg, #f4efe3 0%, #f9f7f1 55%, #eef3f4 100%);
|
||||
color: #172026;
|
||||
--border: rgba(23, 32, 38, 0.12);
|
||||
--panel: rgba(255, 255, 255, 0.82);
|
||||
--panel-strong: rgba(255, 255, 255, 0.92);
|
||||
--ink-soft: rgba(23, 32, 38, 0.7);
|
||||
--accent: #c96f20;
|
||||
--accent-deep: #0f766e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
html {
|
||||
min-height: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(circle at top, oklch(0.92 0.06 175 / 0.86), transparent 35%),
|
||||
radial-gradient(circle at right center, oklch(0.84 0.13 88 / 0.22), transparent 30%),
|
||||
linear-gradient(135deg, oklch(0.975 0.012 172) 0%, oklch(0.962 0.015 164) 45%, oklch(0.988 0.006 92) 100%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -35,120 +27,10 @@ a {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.shell {
|
||||
min-height: 100vh;
|
||||
padding: 32px 20px 48px;
|
||||
.type-display {
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
|
||||
.masthead {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
align-items: end;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto 28px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-block;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
font-size: clamp(2rem, 4vw, 3.2rem);
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 0.78rem;
|
||||
color: var(--accent-deep);
|
||||
}
|
||||
|
||||
.masthead-copy {
|
||||
max-width: 34rem;
|
||||
margin: 0;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
grid-column: span 12;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 24px;
|
||||
background: var(--panel);
|
||||
backdrop-filter: blur(12px);
|
||||
box-shadow: 0 20px 50px rgba(23, 32, 38, 0.08);
|
||||
}
|
||||
|
||||
.hero-card-primary {
|
||||
background:
|
||||
linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(255, 248, 237, 0.94));
|
||||
}
|
||||
|
||||
.hero-card-secondary {
|
||||
background:
|
||||
linear-gradient(145deg, rgba(240, 249, 255, 0.95), rgba(240, 253, 250, 0.9));
|
||||
}
|
||||
|
||||
.hero-card h1,
|
||||
.hero-card h2 {
|
||||
margin: 0 0 12px;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.hero-card h1 {
|
||||
font-size: clamp(2rem, 4.4vw, 4.4rem);
|
||||
line-height: 0.94;
|
||||
max-width: 12ch;
|
||||
}
|
||||
|
||||
.hero-card h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.lede,
|
||||
.hero-card p {
|
||||
margin: 0;
|
||||
max-width: 48rem;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.detail-list li + li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 840px) {
|
||||
.masthead {
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
}
|
||||
|
||||
.hero-card-primary {
|
||||
grid-column: span 7;
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.hero-card-secondary {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.hero-card:not(.hero-card-primary):not(.hero-card-secondary) {
|
||||
grid-column: span 6;
|
||||
}
|
||||
.type-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
Vendored
+8
@@ -1 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_ORCH_API_BASE_URL?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
const proxyTarget = process.env.ORCHD_PROXY_TARGET ?? 'http://127.0.0.1:8080';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [tailwindcss(), react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': 'http://localhost:8080',
|
||||
'/health': 'http://localhost:8080',
|
||||
'/api': proxyTarget,
|
||||
'/health': proxyTarget,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ As of now:
|
||||
- `orchd` now serves a minimal read-only web API with `chi`, including `/health`, runs list/detail, run task list, blocked-task list, and thread detail endpoints backed by the existing SQLite state
|
||||
- HTTP tests now cover the initial read-only `orchd` slice, and the new frontend workspace builds successfully with `pnpm run web:build`
|
||||
- Phase 2 frontend work has now started by bootstrapping `apps/web` with copied-in `cadence-ui` tokens and foundational components for button, input, textarea, dialog, form, tabs, card, badge, and alert, with the shared token stylesheet loaded from the frontend entrypoint
|
||||
- the first real Phase 2 read-only operator UI is now implemented in `apps/web`, including routed runs list, run detail, blocked queue, and thread timeline views backed by the existing `orchd` HTTP API, plus Tailwind v4 consumer wiring so the source-owned Cadence UI components render correctly in the app
|
||||
- a repo-local `scripts/package_skill_clis.sh` packaging flow now builds bundled skill CLI assets for `inbox`, `orch`, and `council-review`
|
||||
- `orch` now implements `run init/show`, `task add`, `dep add`, `ready`, `dispatch`, `reconcile`, `wait`, `blocked`, `answer`, `retry`, `reassign`, `cancel`, `cleanup`, and `status`
|
||||
- `orch` can create runs, gate tasks through dependencies, dispatch work through `inbox`, reconcile worker thread state back into task state, answer blocked tasks, retry or reassign work, cancel tasks or runs, clean attempt worktrees, and create per-attempt Git worktrees during strict dispatch
|
||||
@@ -93,9 +94,9 @@ Current implementation status:
|
||||
- `Milestone 6: Waiting Primitives` is complete
|
||||
- `Milestone 7: Council Review` is complete
|
||||
- `Milestone 8: Web Product Phase 1 Skeleton` is complete
|
||||
- `Milestone 9: Web Product Phase 2 UI Foundation` is in progress
|
||||
- `Milestone 9: Web Product Phase 2 Read-Only Operator UI` is complete for the initial operator surface
|
||||
|
||||
The council review v1 surface is complete, the first web-product skeleton now exists as a separate monorepo workspace plus read-only HTTP backend slice, and Phase 2 frontend work has started on top of the internal Cadence UI component library.
|
||||
The council review v1 surface is complete, the first web-product skeleton now exists as a separate monorepo workspace plus read-only HTTP backend slice, and the first real operator-facing Phase 2 read-only web views now exist on top of the internal Cadence UI component library.
|
||||
|
||||
### Milestone 1: Go Skeleton
|
||||
|
||||
@@ -416,38 +417,45 @@ Remaining:
|
||||
|
||||
- Phase 2 should turn the frontend shell into actual run, task-board, blocked-queue, and thread-detail pages using the new HTTP contract
|
||||
|
||||
### Milestone 9: Web Product Phase 2 UI Foundation
|
||||
### Milestone 9: Web Product Phase 2 Read-Only Operator UI
|
||||
|
||||
Goal:
|
||||
|
||||
- bootstrap the frontend UI layer on top of the Phase 1 shell and read-only backend contract
|
||||
- implement the first real operator-facing read-only web UI on top of the Phase 1 shell and current `orchd` API contract
|
||||
|
||||
Add:
|
||||
|
||||
- copied-in `cadence-ui` primitives and token CSS under `apps/web/src/cadence-ui`
|
||||
- app-wide token style wiring in the frontend entrypoint
|
||||
- any additional component installs needed as real screens land
|
||||
- Tailwind v4 consumer setup so the copied-in Cadence UI source renders correctly in the app
|
||||
- routed screens for runs list, run detail, blocked queue, and thread timeline
|
||||
- typed frontend read helpers for the current `orchd` endpoints
|
||||
|
||||
Definition of done:
|
||||
|
||||
- `apps/web` imports the shared Cadence token stylesheet from the frontend entrypoint
|
||||
- the initial shared component set builds successfully inside the workspace
|
||||
- the Cadence UI source-owned components render correctly inside the consumer app
|
||||
- the first routed read-only operator screens ship against the existing `orchd` contract
|
||||
- future web screens can compose from `cadence-ui` primitives instead of raw one-off HTML controls
|
||||
|
||||
Status:
|
||||
|
||||
- in progress
|
||||
- completed for the first read-only operator slice
|
||||
|
||||
Completed so far:
|
||||
|
||||
- `apps/web/src/cadence-ui/` now contains copied-in Cadence UI tokens plus foundational components for button, input, textarea, dialog, form, tabs, card, badge, and alert
|
||||
- `apps/web/package.json` now includes the required Radix, `react-hook-form`, `motion`, and utility dependencies for the copied-in components
|
||||
- `apps/web/src/main.tsx` now imports `./cadence-ui/tokens/styles.css`
|
||||
- `pnpm install` refreshed the workspace lockfile, and `pnpm run web:build` succeeds with the copied-in component slice
|
||||
- `apps/web` now includes Tailwind v4 consumer wiring in Vite and the global stylesheet so the copied-in Cadence UI utility classes render correctly
|
||||
- `apps/web` now includes a typed frontend read layer for runs, run detail, blocked queue aggregation, and thread detail
|
||||
- `apps/web` now ships routed runs list, run detail, blocked queue, and thread timeline pages using Cadence UI source-owned components for cards, tabs, alerts, inputs, badges, buttons, dialogs, and textareas
|
||||
- the run detail view now includes grouped task boards and blocked-task summaries, while the thread timeline view now shows message payload and artifact metadata inspectors
|
||||
- `pnpm run web:build` succeeds for the new operator UI, and local Vite-to-`orchd` proxy smoke checks confirm the frontend can read the seeded runs, blocked, and thread endpoints through the dev server
|
||||
|
||||
Remaining:
|
||||
|
||||
- build the actual runs list, run detail, blocked queue, and thread timeline screens on top of the Cadence UI primitives
|
||||
- add operator write actions such as answer, retry, reassign, and cancel on top of the new read-only screens
|
||||
- add council result/report views and realtime event handling on top of the current routed UI
|
||||
- install additional `cadence-ui` components on demand as the product surface expands
|
||||
|
||||
## Immediate Next Task
|
||||
@@ -456,11 +464,13 @@ If a new agent is taking over now, the next concrete step should be:
|
||||
|
||||
1. treat `Milestone 8: Web Product Phase 1 Skeleton` as complete unless a new user request reopens the backend skeleton itself
|
||||
2. keep the authored inbox test-plan set in `docs/tests/inbox/` synchronized if future `orch` or web work changes shared CLI-visible behavior
|
||||
3. continue `Milestone 9: Web Product Phase 2 UI Foundation` by implementing the first runs list, run detail, blocked queue, and thread timeline screens on top of the existing `apps/web` and `orchd` contract
|
||||
4. install additional `cadence-ui` components on demand when those screens need them, instead of reintroducing bespoke primitives into `apps/web`
|
||||
5. keep `api/openapi.yaml`, `api/events.md`, and `docs/web-product-monorepo.md` synchronized as the web surface expands
|
||||
3. treat `Milestone 9: Web Product Phase 2 Read-Only Operator UI` as complete for the initial operator surface and build the next web slice on top of the shipped read pages rather than replacing them
|
||||
4. start the next web phase by wiring operator write actions such as answer, retry, reassign, and cancel into the existing runs, blocked, and thread views
|
||||
5. add council result/report screens and realtime event handling after the operator write path is clear
|
||||
6. install additional `cadence-ui` components on demand when those screens need them, instead of reintroducing bespoke primitives into `apps/web`
|
||||
7. keep `api/openapi.yaml`, `api/events.md`, and `docs/web-product-monorepo.md` synchronized as the web surface expands
|
||||
|
||||
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, and the web product is now past the bare frontend shell stage, so the next step should be actual Phase 2 product screens built on top of the Cadence UI foundation rather than reopening earlier milestones.
|
||||
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, and the web product now has its first real operator-facing read surfaces, so the next step should be write-capable operator workflows and council/realtime expansion rather than reopening the frontend shell or basic read pages.
|
||||
|
||||
## Recommended Driver Choices
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Web Phase 2 Read-Only UI
|
||||
|
||||
## Status
|
||||
|
||||
- `completed`
|
||||
|
||||
## Owner
|
||||
|
||||
- Codex
|
||||
|
||||
## Started At
|
||||
|
||||
- `2026-03-20`
|
||||
|
||||
## Goal
|
||||
|
||||
- Implement the first real Phase 2 operator UI in `apps/web` by shipping the read-only runs list, run detail, blocked queue, and thread timeline views on top of the existing `orchd` HTTP API and Cadence UI source-owned components.
|
||||
|
||||
## Scope
|
||||
|
||||
- create an execution trace for the Phase 2 read-only UI workstream
|
||||
- wire the frontend to the existing read-only API contract
|
||||
- implement routed views for runs list, run detail, blocked queue, and thread timeline
|
||||
- reuse Cadence UI source-owned components instead of introducing new foundational UI primitives
|
||||
- validate the frontend build and keep `docs/implementation-roadmap.md` synchronized
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] create the active execution roadmap for the Phase 2 read-only UI workstream
|
||||
- [x] inspect the current frontend shell and backend read contract
|
||||
- [x] add the frontend data layer and route structure for the Phase 2 read-only views
|
||||
- [x] implement the runs list, run detail, blocked queue, and thread timeline screens
|
||||
- [x] ensure Cadence UI styles render correctly in the consumer app
|
||||
- [x] validate the frontend build and fix any type or integration issues
|
||||
- [x] update `docs/implementation-roadmap.md`
|
||||
- [x] archive this roadmap with a completion summary if the slice is fully complete
|
||||
|
||||
## Files
|
||||
|
||||
- `docs/roadmaps/archive/web-phase2-read-only-ui.md`
|
||||
- `docs/implementation-roadmap.md`
|
||||
- `apps/web/package.json`
|
||||
- `apps/web/src/app.tsx`
|
||||
- `apps/web/src/features/operator-console.tsx`
|
||||
- `apps/web/src/lib/api.ts`
|
||||
- `apps/web/src/lib/format.ts`
|
||||
- `apps/web/src/styles.css`
|
||||
- `apps/web/src/vite-env.d.ts`
|
||||
- `apps/web/vite.config.ts`
|
||||
- `pnpm-lock.yaml`
|
||||
|
||||
## Decisions
|
||||
|
||||
- keep the first Phase 2 UI slice read-only and route it against the existing HTTP API instead of coupling UI delivery to new backend mutations
|
||||
- prefer composition from the copied-in Cadence UI source over new hand-rolled UI primitives
|
||||
|
||||
## Blockers
|
||||
|
||||
- none
|
||||
|
||||
## Next Step
|
||||
|
||||
- build the next web slice on top of the shipped read-only screens by adding operator write actions, council views, and realtime event handling without replacing the new routed operator shell
|
||||
|
||||
## Completion Summary
|
||||
|
||||
- replaced the placeholder `apps/web` landing screen with a routed read-only operator UI that now ships runs list, run detail, blocked queue, and thread timeline pages backed by the existing `orchd` API
|
||||
- added a typed frontend read layer for the current HTTP contract, including client-side blocked queue aggregation across runs and thread timeline payload/artifact rendering
|
||||
- wired Tailwind v4 into the consumer app so the copied-in Cadence UI source-owned components render correctly, and used those components throughout the new pages instead of introducing new foundational UI primitives
|
||||
- validated the slice with `pnpm run web:build`, seeded a local demo run/thread dataset through the existing `orch` and `inbox` CLIs, and verified the Vite dev server can proxy the runs, run detail, and thread endpoints through to `orchd`
|
||||
Generated
+259
-5
@@ -47,6 +47,12 @@ importers:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2(vite@8.0.1(@types/node@24.12.0)(jiti@2.6.1))
|
||||
'@types/node':
|
||||
specifier: ^24.5.2
|
||||
version: 24.12.0
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
version: 19.2.14
|
||||
@@ -55,13 +61,16 @@ importers:
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@8.0.1)
|
||||
version: 6.0.1(vite@8.0.1(@types/node@24.12.0)(jiti@2.6.1))
|
||||
tailwindcss:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
version: 8.0.1(@types/node@24.12.0)(jiti@2.6.1)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -74,6 +83,22 @@ packages:
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||
|
||||
@@ -403,6 +428,96 @@ packages:
|
||||
'@rolldown/pluginutils@1.0.0-rc.7':
|
||||
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
|
||||
|
||||
'@tailwindcss/node@4.2.2':
|
||||
resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.2.2':
|
||||
resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.2.2':
|
||||
resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.2.2':
|
||||
resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.2.2':
|
||||
resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
|
||||
resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
|
||||
resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
|
||||
resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
|
||||
resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
|
||||
resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.2.2':
|
||||
resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide@4.2.2':
|
||||
resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@tailwindcss/vite@4.2.2':
|
||||
resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==}
|
||||
peerDependencies:
|
||||
vite: ^5.2.0 || ^6 || ^7 || ^8
|
||||
|
||||
'@tanstack/history@1.161.6':
|
||||
resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==}
|
||||
engines: {node: '>=20.19'}
|
||||
@@ -439,6 +554,9 @@ packages:
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/node@24.12.0':
|
||||
resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
|
||||
|
||||
'@types/react-dom@19.2.3':
|
||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||
peerDependencies:
|
||||
@@ -484,6 +602,10 @@ packages:
|
||||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -516,10 +638,17 @@ packages:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
isbot@5.1.36:
|
||||
resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -590,6 +719,9 @@ packages:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
motion-dom@12.38.0:
|
||||
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
|
||||
|
||||
@@ -696,6 +828,13 @@ packages:
|
||||
tailwind-merge@3.5.0:
|
||||
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
|
||||
|
||||
tailwindcss@4.2.2:
|
||||
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
|
||||
|
||||
tapable@2.3.0:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
@@ -714,6 +853,9 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -800,6 +942,25 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.9.1
|
||||
@@ -1061,6 +1222,74 @@ snapshots:
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.7': {}
|
||||
|
||||
'@tailwindcss/node@4.2.2':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.20.1
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.2.2
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.2.2':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.2.2':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.2.2
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.2.2
|
||||
'@tailwindcss/oxide-darwin-x64': 4.2.2
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.2.2
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.2.2
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.2.2
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.2.2
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.2.2
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.2.2
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
|
||||
|
||||
'@tailwindcss/vite@4.2.2(vite@8.0.1(@types/node@24.12.0)(jiti@2.6.1))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.2.2
|
||||
'@tailwindcss/oxide': 4.2.2
|
||||
tailwindcss: 4.2.2
|
||||
vite: 8.0.1(@types/node@24.12.0)(jiti@2.6.1)
|
||||
|
||||
'@tanstack/history@1.161.6': {}
|
||||
|
||||
'@tanstack/query-core@5.91.2': {}
|
||||
@@ -1105,6 +1334,10 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/node@24.12.0':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.14
|
||||
@@ -1113,10 +1346,10 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vitejs/plugin-react@6.0.1(vite@8.0.1)':
|
||||
'@vitejs/plugin-react@6.0.1(vite@8.0.1(@types/node@24.12.0)(jiti@2.6.1))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||
vite: 8.0.1
|
||||
vite: 8.0.1(@types/node@24.12.0)(jiti@2.6.1)
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
dependencies:
|
||||
@@ -1136,6 +1369,11 @@ snapshots:
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
@@ -1154,8 +1392,12 @@ snapshots:
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
isbot@5.1.36: {}
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
@@ -1205,6 +1447,10 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
motion-dom@12.38.0:
|
||||
dependencies:
|
||||
motion-utils: 12.36.0
|
||||
@@ -1302,6 +1548,10 @@ snapshots:
|
||||
|
||||
tailwind-merge@3.5.0: {}
|
||||
|
||||
tailwindcss@4.2.2: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tiny-warning@1.0.3: {}
|
||||
@@ -1315,6 +1565,8 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
@@ -1334,7 +1586,7 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
vite@8.0.1:
|
||||
vite@8.0.1(@types/node@24.12.0)(jiti@2.6.1):
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.3
|
||||
@@ -1342,4 +1594,6 @@ snapshots:
|
||||
rolldown: 1.0.0-rc.10
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
|
||||
Reference in New Issue
Block a user