Add web product Phase 1 skeleton
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
.orch/
|
||||
bin/
|
||||
dist/
|
||||
node_modules/
|
||||
apps/web/dist/
|
||||
apps/web/.vite/
|
||||
*.db
|
||||
coverage.out
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Event Stream Contract
|
||||
|
||||
## Status
|
||||
|
||||
Planned for the next milestone.
|
||||
The Phase 1 web skeleton does not implement the event stream yet.
|
||||
|
||||
## Intended Endpoint
|
||||
|
||||
- `GET /api/events/stream`
|
||||
|
||||
## Transport Choice
|
||||
|
||||
- start with Server-Sent Events rather than websockets
|
||||
- keep the stream read-only and cursor-based
|
||||
- let the frontend use events to invalidate or refresh TanStack Query caches
|
||||
|
||||
## Query Parameters
|
||||
|
||||
- `after_event_id`: optional monotonic cursor
|
||||
- `run_id`: optional run filter for run-scoped pages
|
||||
- `thread_id`: optional thread filter for timeline views
|
||||
|
||||
## Event Envelope
|
||||
|
||||
Each SSE message should carry one JSON object with this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_id": 42,
|
||||
"event_type": "task_blocked",
|
||||
"run_id": "run_web_001",
|
||||
"task_id": "T1",
|
||||
"thread_id": "thr_123",
|
||||
"summary": "Need the API shape",
|
||||
"payload": {
|
||||
"source": "orch"
|
||||
},
|
||||
"created_at": "2026-03-20T09:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Expected First Event Types
|
||||
|
||||
- `task_added`
|
||||
- `task_ready`
|
||||
- `task_dispatched`
|
||||
- `task_running`
|
||||
- `task_blocked`
|
||||
- `task_answered`
|
||||
- `task_done`
|
||||
- `task_failed`
|
||||
- `thread_claim`
|
||||
- `thread_update`
|
||||
- `thread_reply`
|
||||
- `thread_result`
|
||||
- `council_tallied`
|
||||
- `council_report_persisted`
|
||||
|
||||
## Cursor Rules
|
||||
|
||||
- `event_id` is the only resume cursor
|
||||
- the server should emit events in ascending `event_id` order
|
||||
- reconnect clients should pass the last processed `event_id`
|
||||
- the server should not depend on in-memory subscriptions for correctness
|
||||
|
||||
## Frontend Usage
|
||||
|
||||
- run detail pages should subscribe with `run_id`
|
||||
- thread views may subscribe with `thread_id`
|
||||
- the client should prefer refetch/invalidate behavior over complex local reducers in the first realtime slice
|
||||
|
||||
## Backend Notes
|
||||
|
||||
- source data should come from the existing shared `events` table
|
||||
- `orchd` should treat the stream as a projection over persisted events, not as a separate ephemeral bus
|
||||
- if filtering becomes expensive later, move filtering logic into a dedicated query layer without changing the wire contract
|
||||
@@ -0,0 +1,363 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Orch Web API
|
||||
version: 0.1.0
|
||||
summary: Initial read-only web API for the orchestration control plane.
|
||||
servers:
|
||||
- url: http://localhost:8080
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
summary: Health check
|
||||
operationId: getHealth
|
||||
responses:
|
||||
'200':
|
||||
description: Service is healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [status]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
/api/runs:
|
||||
get:
|
||||
summary: List orchestration runs
|
||||
operationId: listRuns
|
||||
responses:
|
||||
'200':
|
||||
description: Run summaries
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [runs]
|
||||
properties:
|
||||
runs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RunListItem'
|
||||
/api/runs/{runID}:
|
||||
get:
|
||||
summary: Get run detail
|
||||
operationId: getRunDetail
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/RunID'
|
||||
responses:
|
||||
'200':
|
||||
description: Run detail including tasks and blocked-task summaries
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [run]
|
||||
properties:
|
||||
run:
|
||||
$ref: '#/components/schemas/RunDetail'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
/api/runs/{runID}/tasks:
|
||||
get:
|
||||
summary: List tasks for a run
|
||||
operationId: listRunTasks
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/RunID'
|
||||
responses:
|
||||
'200':
|
||||
description: Task list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [tasks]
|
||||
properties:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Task'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
/api/runs/{runID}/blocked:
|
||||
get:
|
||||
summary: List blocked tasks for a run
|
||||
operationId: listBlockedTasks
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/RunID'
|
||||
responses:
|
||||
'200':
|
||||
description: Blocked task list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [blocked]
|
||||
properties:
|
||||
blocked:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BlockedTask'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
/api/threads/{threadID}:
|
||||
get:
|
||||
summary: Get thread timeline
|
||||
operationId: getThreadDetail
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ThreadID'
|
||||
responses:
|
||||
'200':
|
||||
description: Thread detail with messages and artifacts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [thread]
|
||||
properties:
|
||||
thread:
|
||||
$ref: '#/components/schemas/ThreadDetail'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
components:
|
||||
parameters:
|
||||
RunID:
|
||||
name: runID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
ThreadID:
|
||||
name: threadID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
NotFound:
|
||||
description: Requested resource was not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
schemas:
|
||||
ErrorEnvelope:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: not_found
|
||||
message:
|
||||
type: string
|
||||
Run:
|
||||
type: object
|
||||
required: [run_id, goal, summary, status, created_at, updated_at]
|
||||
properties:
|
||||
run_id:
|
||||
type: string
|
||||
goal:
|
||||
type: string
|
||||
summary:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
RunListItem:
|
||||
type: object
|
||||
required: [run, task_counts, total_tasks]
|
||||
properties:
|
||||
run:
|
||||
$ref: '#/components/schemas/Run'
|
||||
task_counts:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
total_tasks:
|
||||
type: integer
|
||||
RunDetail:
|
||||
type: object
|
||||
required: [run, task_counts, total_tasks, tasks, blocked_tasks]
|
||||
properties:
|
||||
run:
|
||||
$ref: '#/components/schemas/Run'
|
||||
task_counts:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
total_tasks:
|
||||
type: integer
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Task'
|
||||
blocked_tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BlockedTask'
|
||||
Task:
|
||||
type: object
|
||||
required:
|
||||
[run_id, task_id, title, summary, status, priority, acceptance_json, created_at, updated_at]
|
||||
properties:
|
||||
run_id:
|
||||
type: string
|
||||
task_id:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
summary:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
default_to:
|
||||
type: string
|
||||
priority:
|
||||
type: string
|
||||
acceptance_json:
|
||||
description: Raw JSON acceptance criteria payload
|
||||
latest_attempt_no:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
TaskAttempt:
|
||||
type: object
|
||||
required: [run_id, task_id, attempt_no, assigned_to, thread_id, status, created_at, updated_at]
|
||||
properties:
|
||||
run_id:
|
||||
type: string
|
||||
task_id:
|
||||
type: string
|
||||
attempt_no:
|
||||
type: integer
|
||||
assigned_to:
|
||||
type: string
|
||||
thread_id:
|
||||
type: string
|
||||
base_ref:
|
||||
type: string
|
||||
base_commit:
|
||||
type: string
|
||||
branch_name:
|
||||
type: string
|
||||
worktree_path:
|
||||
type: string
|
||||
workspace_status:
|
||||
type: string
|
||||
result_commit:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
Artifact:
|
||||
type: object
|
||||
required: [artifact_id, message_id, path, kind, metadata_json, created_at]
|
||||
properties:
|
||||
artifact_id:
|
||||
type: string
|
||||
message_id:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata_json:
|
||||
description: Raw JSON artifact metadata
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
Message:
|
||||
type: object
|
||||
required:
|
||||
[message_id, thread_id, from_agent, to_agent, kind, summary, body, payload_json, created_at]
|
||||
properties:
|
||||
message_id:
|
||||
type: string
|
||||
thread_id:
|
||||
type: string
|
||||
from_agent:
|
||||
type: string
|
||||
to_agent:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
summary:
|
||||
type: string
|
||||
body:
|
||||
type: string
|
||||
payload_json:
|
||||
description: Raw JSON message payload
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
artifacts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Artifact'
|
||||
Thread:
|
||||
type: object
|
||||
required:
|
||||
[thread_id, run_id, task_id, subject, created_by, assigned_to, status, priority, created_at, updated_at]
|
||||
properties:
|
||||
thread_id:
|
||||
type: string
|
||||
run_id:
|
||||
type: string
|
||||
task_id:
|
||||
type: string
|
||||
subject:
|
||||
type: string
|
||||
created_by:
|
||||
type: string
|
||||
assigned_to:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
priority:
|
||||
type: string
|
||||
latest_message_id:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
ThreadDetail:
|
||||
type: object
|
||||
required: [thread, messages]
|
||||
properties:
|
||||
thread:
|
||||
$ref: '#/components/schemas/Thread'
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Message'
|
||||
BlockedTask:
|
||||
type: object
|
||||
required: [task, attempt, question]
|
||||
properties:
|
||||
task:
|
||||
$ref: '#/components/schemas/Task'
|
||||
attempt:
|
||||
$ref: '#/components/schemas/TaskAttempt'
|
||||
question:
|
||||
$ref: '#/components/schemas/Message'
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Orch Control Plane</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@ai-workflow-skill/web",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.91.2",
|
||||
"@tanstack/react-router": "^1.167.5",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
Link,
|
||||
Outlet,
|
||||
RouterProvider,
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
} from '@tanstack/react-router';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const rootRoute = createRootRoute({
|
||||
component: RootLayout,
|
||||
});
|
||||
|
||||
const indexRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: HomePage,
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([indexRoute]);
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
});
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
function HomePage() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { App } from './app';
|
||||
import './styles.css';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (!rootElement) {
|
||||
throw new Error('Missing root element');
|
||||
}
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,154 @@
|
||||
: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;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.shell {
|
||||
min-height: 100vh;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': 'http://localhost:8080',
|
||||
'/health': 'http://localhost:8080',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"ai-workflow-skill/internal/app"
|
||||
"ai-workflow-skill/internal/db"
|
||||
"ai-workflow-skill/internal/httpapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
dbPath string
|
||||
listen string
|
||||
shutdown time.Duration
|
||||
)
|
||||
|
||||
flag.StringVar(&dbPath, "db", ".agents/coord.db", "SQLite database path")
|
||||
flag.StringVar(&listen, "listen", ":8080", "HTTP listen address")
|
||||
flag.DurationVar(&shutdown, "shutdown-timeout", 5*time.Second, "Graceful shutdown timeout")
|
||||
flag.Parse()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
sqlDB, err := db.Open(ctx, dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("open database: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
if err := db.ApplyMigrations(ctx, sqlDB); err != nil {
|
||||
log.Fatalf("apply migrations: %v", err)
|
||||
}
|
||||
|
||||
webApp := app.NewWebService(sqlDB)
|
||||
server := &http.Server{
|
||||
Addr: listen,
|
||||
Handler: httpapi.NewRouter(webApp),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdown)
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("http shutdown: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("orchd listening on %s", listen)
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("serve http api: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ As of now:
|
||||
- a council-review skill forward-test plan directory now exists under `docs/tests/council-review-skill/`, with a shared execution contract and nine council workflow scenarios covering end-to-end flow, unanimous-only defaults, timeout/before-tally errors, explicit minority reporting, invalid report filters, strict tally semantics, malformed reviewer JSON, and target-file inputs
|
||||
- an execution-roadmap workflow now exists under `docs/roadmaps/active/` and `docs/roadmaps/archive/` for agent-level work traces and completion archives
|
||||
- a forward-looking web product monorepo plan now exists under `docs/web-product-monorepo.md`, defining the recommended React frontend, `chi` HTTP service, `cmd/orchd` entrypoint, and shared application/query layering for future web work
|
||||
- the Phase 1 web-product skeleton is now in place, including root `pnpm` workspace files, a standalone React app under `apps/web`, an initial OpenAPI/events contract under `api/`, and a new `cmd/orchd` HTTP service backed by `internal/app`, `internal/query`, and `internal/httpapi`
|
||||
- `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`
|
||||
- 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
|
||||
@@ -88,8 +91,9 @@ Current implementation status:
|
||||
- `Milestone 5: Strict Worktree Support` is complete
|
||||
- `Milestone 6: Waiting Primitives` is complete
|
||||
- `Milestone 7: Council Review` is complete
|
||||
- `Milestone 8: Web Product Phase 1 Skeleton` is complete
|
||||
|
||||
The council review v1 surface is now complete, including final report rendering and metadata persistence.
|
||||
The council review v1 surface is complete, and the first web-product skeleton now exists as a separate monorepo workspace plus read-only HTTP backend slice.
|
||||
|
||||
### Milestone 1: Go Skeleton
|
||||
|
||||
@@ -367,16 +371,59 @@ Remaining:
|
||||
|
||||
- none for the v1 council workflow
|
||||
|
||||
### Milestone 8: Web Product Phase 1 Skeleton
|
||||
|
||||
Goal:
|
||||
|
||||
- create the first durable web-product backbone without replacing the existing CLI workflows
|
||||
|
||||
Add:
|
||||
|
||||
- root `pnpm` workspace files
|
||||
- `apps/web`
|
||||
- `api/openapi.yaml`
|
||||
- `api/events.md`
|
||||
- `cmd/orchd`
|
||||
- `internal/app`
|
||||
- `internal/query`
|
||||
- `internal/httpapi`
|
||||
|
||||
Definition of done:
|
||||
|
||||
- the repository contains the agreed monorepo skeleton
|
||||
- `orchd` can serve a small read-only HTTP API against the existing database
|
||||
- the frontend workspace builds and can evolve independently in later milestones
|
||||
|
||||
Status:
|
||||
|
||||
- completed
|
||||
|
||||
Completed so far:
|
||||
|
||||
- root `package.json`, `pnpm-workspace.yaml`, and `pnpm-lock.yaml` now define the monorepo JS workspace
|
||||
- `apps/web` now contains a Vite + React + TypeScript + TanStack Router + TanStack Query frontend shell
|
||||
- `cmd/orchd` now opens the shared SQLite database, applies migrations, and serves a `chi` router with graceful shutdown handling
|
||||
- `internal/query` now exposes run list/detail, run tasks, blocked-task, and thread-detail read models for the web surface
|
||||
- `internal/app` now provides a thin web-service boundary over the new read service
|
||||
- `internal/httpapi` now owns HTTP routing, JSON/error helpers, and the initial read-only endpoints
|
||||
- `api/openapi.yaml` now documents the implemented read-only endpoints and response shapes
|
||||
- `api/events.md` now captures the planned SSE contract for the next realtime slice
|
||||
- `go test ./...` covers the new HTTP slice, and `pnpm run web:build` succeeds for the frontend workspace
|
||||
|
||||
Remaining:
|
||||
|
||||
- Phase 2 should turn the frontend shell into actual run, task-board, blocked-queue, and thread-detail pages using the new HTTP contract
|
||||
|
||||
## Immediate Next Task
|
||||
|
||||
If a new agent is taking over now, the next concrete step should be:
|
||||
|
||||
1. treat `Milestone 7: Council Review` as complete unless a new user request introduces a new council capability
|
||||
2. keep the authored inbox test-plan set in `docs/tests/inbox/` synchronized if future `orch` work changes shared CLI behavior
|
||||
3. if the next milestone is the web product, use `docs/web-product-monorepo.md` as the starting implementation shape for the monorepo, `cmd/orchd`, and `apps/web`
|
||||
4. choose the next milestone explicitly instead of reopening the completed council v1 slice
|
||||
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. start `Phase 2: Read-Only Web UI` on top of the existing `apps/web` and `orchd` contract, beginning with runs list, run detail, blocked queue, and thread timeline views
|
||||
4. 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, and `orch` now supports the main scheduler loop plus the complete council start/wait/tally/report workflow, so any next step should be a new milestone rather than unfinished council v1 work.
|
||||
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 Phase 1 skeleton now exists, so the next step should be Phase 2 product surface work rather than reopening earlier milestones.
|
||||
|
||||
## Recommended Driver Choices
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Web Product Phase 1 Skeleton
|
||||
|
||||
## Status
|
||||
|
||||
- `completed`
|
||||
|
||||
## Owner
|
||||
|
||||
- Codex
|
||||
|
||||
## Started At
|
||||
|
||||
- `2026-03-20`
|
||||
|
||||
## Goal
|
||||
|
||||
- Implement the first web-product milestone slice described in `docs/web-product-monorepo.md` by creating the monorepo skeleton, a minimal `orchd` HTTP service, and the first read-oriented backend boundaries.
|
||||
|
||||
## Scope
|
||||
|
||||
- add an active execution trace for this web implementation workstream
|
||||
- add the initial monorepo workspace files and `apps/web` scaffold
|
||||
- add `cmd/orchd`, `internal/httpapi`, `internal/app`, and `internal/query`
|
||||
- expose a small read-only HTTP API for health, runs, run detail, blocked tasks, and thread detail
|
||||
- add initial API contract docs under `api/`
|
||||
- keep `docs/implementation-roadmap.md` synchronized with the new implementation state
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] create the active execution roadmap for the Phase 1 web skeleton workstream
|
||||
- [x] scaffold the monorepo workspace files and `apps/web`
|
||||
- [x] add `cmd/orchd` with a minimal `chi`-based HTTP server
|
||||
- [x] introduce initial shared app/query boundaries for read-only web endpoints
|
||||
- [x] add initial API contract documents under `api/`
|
||||
- [x] validate the new slice with builds or targeted tests
|
||||
- [x] update `docs/implementation-roadmap.md`
|
||||
- [x] archive this execution roadmap with a completion summary if the slice is fully complete
|
||||
|
||||
## Files
|
||||
|
||||
- `docs/roadmaps/active/web-product-phase1-skeleton.md`
|
||||
- `docs/implementation-roadmap.md`
|
||||
- `docs/web-product-monorepo.md`
|
||||
- `cmd/orchd/main.go`
|
||||
- `internal/httpapi/`
|
||||
- `internal/app/`
|
||||
- `internal/query/`
|
||||
- `internal/store/`
|
||||
- `api/openapi.yaml`
|
||||
- `api/events.md`
|
||||
- `apps/web/`
|
||||
- `package.json`
|
||||
- `pnpm-workspace.yaml`
|
||||
|
||||
## Decisions
|
||||
|
||||
- implement a narrow read-only backend slice first instead of starting with frontend pages
|
||||
- keep the first HTTP layer thin and let query/app packages own the web-facing backend boundary
|
||||
- preserve the existing CLI entrypoints while introducing the new service
|
||||
|
||||
## Blockers
|
||||
|
||||
- none
|
||||
|
||||
## Next Step
|
||||
|
||||
- start Phase 2 on top of the new `apps/web` and `orchd` skeleton by wiring real runs, run-detail, blocked-queue, and thread-detail screens to the read-only API
|
||||
|
||||
## Completion Summary
|
||||
|
||||
- added the first web-product skeleton described in `docs/web-product-monorepo.md`, including root `pnpm` workspace files, a standalone React frontend shell under `apps/web`, initial API contract documents under `api/`, and a new `cmd/orchd` HTTP service
|
||||
- introduced `internal/app`, `internal/query`, and `internal/httpapi` so the web backend has an explicit read-oriented boundary instead of letting handlers query storage ad hoc
|
||||
- implemented and validated the first read-only HTTP endpoints for health, runs, run detail, run tasks, blocked tasks, and thread detail
|
||||
- verified the slice with `go test ./...` and `pnpm run web:build`, then synchronized `docs/implementation-roadmap.md`
|
||||
@@ -3,6 +3,7 @@ module ai-workflow-skill
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/spf13/cobra v1.10.1
|
||||
modernc.org/sqlite v1.40.1
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"ai-workflow-skill/internal/query"
|
||||
"ai-workflow-skill/internal/store"
|
||||
)
|
||||
|
||||
type WebService struct {
|
||||
reads *query.ReadService
|
||||
}
|
||||
|
||||
func NewWebService(db *sql.DB) *WebService {
|
||||
return &WebService{
|
||||
reads: query.NewReadService(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebService) ListRuns(ctx context.Context) ([]query.RunListItem, error) {
|
||||
return s.reads.ListRuns(ctx)
|
||||
}
|
||||
|
||||
func (s *WebService) GetRunDetail(ctx context.Context, runID string) (query.RunDetail, error) {
|
||||
return s.reads.GetRunDetail(ctx, runID)
|
||||
}
|
||||
|
||||
func (s *WebService) ListRunTasks(ctx context.Context, runID string) ([]store.Task, error) {
|
||||
return s.reads.ListRunTasks(ctx, runID)
|
||||
}
|
||||
|
||||
func (s *WebService) ListBlockedTasks(ctx context.Context, runID string) ([]store.BlockedTask, error) {
|
||||
return s.reads.ListBlockedTasks(ctx, runID)
|
||||
}
|
||||
|
||||
func (s *WebService) GetThreadDetail(ctx context.Context, threadID string) (store.ThreadDetail, error) {
|
||||
return s.reads.GetThreadDetail(ctx, threadID)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"ai-workflow-skill/internal/store"
|
||||
)
|
||||
|
||||
type errorEnvelope struct {
|
||||
Error errorPayload `json:"error"`
|
||||
}
|
||||
|
||||
type errorPayload struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, payload any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(payload)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
status, code := classifyError(err)
|
||||
writeJSON(w, status, errorEnvelope{
|
||||
Error: errorPayload{
|
||||
Code: code,
|
||||
Message: errorMessage(err),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func classifyError(err error) (int, string) {
|
||||
switch {
|
||||
case errors.Is(err, store.ErrInvalidInput):
|
||||
return http.StatusBadRequest, "invalid_input"
|
||||
case errors.Is(err, store.ErrRunNotFound), errors.Is(err, store.ErrTaskNotFound), errors.Is(err, store.ErrThreadNotFound):
|
||||
return http.StatusNotFound, "not_found"
|
||||
case errors.Is(err, store.ErrInvalidState):
|
||||
return http.StatusConflict, "invalid_state"
|
||||
default:
|
||||
return http.StatusInternalServerError, "internal_error"
|
||||
}
|
||||
}
|
||||
|
||||
func errorMessage(err error) string {
|
||||
if err == nil {
|
||||
return "unknown error"
|
||||
}
|
||||
return fmt.Sprintf("%v", err)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"ai-workflow-skill/internal/query"
|
||||
"ai-workflow-skill/internal/store"
|
||||
)
|
||||
|
||||
type readService interface {
|
||||
ListRuns(ctx context.Context) ([]query.RunListItem, error)
|
||||
GetRunDetail(ctx context.Context, runID string) (query.RunDetail, error)
|
||||
ListRunTasks(ctx context.Context, runID string) ([]store.Task, error)
|
||||
ListBlockedTasks(ctx context.Context, runID string) ([]store.BlockedTask, error)
|
||||
GetThreadDetail(ctx context.Context, threadID string) (store.ThreadDetail, error)
|
||||
}
|
||||
|
||||
func NewRouter(service readService) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Use(middleware.RequestID)
|
||||
router.Use(middleware.Recoverer)
|
||||
router.Use(middleware.Timeout(30 * time.Second))
|
||||
|
||||
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"status": "ok",
|
||||
})
|
||||
})
|
||||
|
||||
router.Route("/api", func(r chi.Router) {
|
||||
r.Get("/runs", func(w http.ResponseWriter, r *http.Request) {
|
||||
runs, err := service.ListRuns(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"runs": runs})
|
||||
})
|
||||
|
||||
r.Get("/runs/{runID}", func(w http.ResponseWriter, r *http.Request) {
|
||||
runID := chi.URLParam(r, "runID")
|
||||
run, err := service.GetRunDetail(r.Context(), runID)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"run": run})
|
||||
})
|
||||
|
||||
r.Get("/runs/{runID}/tasks", func(w http.ResponseWriter, r *http.Request) {
|
||||
runID := chi.URLParam(r, "runID")
|
||||
tasks, err := service.ListRunTasks(r.Context(), runID)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"tasks": tasks})
|
||||
})
|
||||
|
||||
r.Get("/runs/{runID}/blocked", func(w http.ResponseWriter, r *http.Request) {
|
||||
runID := chi.URLParam(r, "runID")
|
||||
blocked, err := service.ListBlockedTasks(r.Context(), runID)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"blocked": blocked})
|
||||
})
|
||||
|
||||
r.Get("/threads/{threadID}", func(w http.ResponseWriter, r *http.Request) {
|
||||
threadID := chi.URLParam(r, "threadID")
|
||||
thread, err := service.GetThreadDetail(r.Context(), threadID)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"thread": thread})
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ai-workflow-skill/internal/app"
|
||||
dbpkg "ai-workflow-skill/internal/db"
|
||||
"ai-workflow-skill/internal/store"
|
||||
)
|
||||
|
||||
func TestRouterExposesReadOnlyWebEndpoints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
sqlDB, err := dbpkg.Open(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
if err := dbpkg.ApplyMigrations(ctx, sqlDB); err != nil {
|
||||
t.Fatalf("apply migrations: %v", err)
|
||||
}
|
||||
|
||||
orchStore := store.NewOrchStore(sqlDB)
|
||||
inboxStore := store.NewInboxStore(sqlDB)
|
||||
|
||||
_, err = orchStore.CreateRun(ctx, store.CreateRunInput{
|
||||
RunID: "run_web_001",
|
||||
Goal: "Build the web control plane",
|
||||
Summary: "Initial HTTP slice",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create run: %v", err)
|
||||
}
|
||||
|
||||
_, err = orchStore.AddTask(ctx, store.AddTaskInput{
|
||||
RunID: "run_web_001",
|
||||
TaskID: "T1",
|
||||
Title: "Implement read API",
|
||||
Summary: "Expose run state over HTTP",
|
||||
DefaultTo: "worker-a",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("add task T1: %v", err)
|
||||
}
|
||||
|
||||
_, err = orchStore.AddTask(ctx, store.AddTaskInput{
|
||||
RunID: "run_web_001",
|
||||
TaskID: "T2",
|
||||
Title: "Build React shell",
|
||||
Summary: "Scaffold the frontend workspace",
|
||||
DefaultTo: "worker-b",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("add task T2: %v", err)
|
||||
}
|
||||
|
||||
dispatch, err := orchStore.DispatchTask(ctx, store.DispatchInput{
|
||||
RunID: "run_web_001",
|
||||
TaskID: "T1",
|
||||
ToAgent: "worker-a",
|
||||
Body: "Expose the initial HTTP API.",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch task: %v", err)
|
||||
}
|
||||
|
||||
if _, err := inboxStore.ClaimThread(ctx, store.ClaimInput{
|
||||
ThreadID: dispatch.Attempt.ThreadID,
|
||||
Agent: "worker-a",
|
||||
LeaseSeconds: 300,
|
||||
}); err != nil {
|
||||
t.Fatalf("claim thread: %v", err)
|
||||
}
|
||||
|
||||
if _, _, err := inboxStore.UpdateThreadStatus(ctx, store.UpdateInput{
|
||||
ThreadID: dispatch.Attempt.ThreadID,
|
||||
Agent: "worker-a",
|
||||
Status: "blocked",
|
||||
Summary: "Need the API shape",
|
||||
Body: "Confirm whether run detail should include blocked tasks.",
|
||||
}); err != nil {
|
||||
t.Fatalf("mark thread blocked: %v", err)
|
||||
}
|
||||
|
||||
if _, err := orchStore.ReconcileRun(ctx, "run_web_001"); err != nil {
|
||||
t.Fatalf("reconcile run: %v", err)
|
||||
}
|
||||
|
||||
handler := NewRouter(app.NewWebService(sqlDB))
|
||||
|
||||
assertStatusAndJSONField(t, handler, "/health", http.StatusOK, []string{"status"}, "ok")
|
||||
assertStatusAndJSONField(t, handler, "/api/runs", http.StatusOK, []string{"runs", "0", "run", "run_id"}, "run_web_001")
|
||||
assertStatusAndJSONField(t, handler, "/api/runs/run_web_001", http.StatusOK, []string{"run", "run", "run_id"}, "run_web_001")
|
||||
assertStatusAndJSONField(t, handler, "/api/runs/run_web_001/tasks", http.StatusOK, []string{"tasks", "0", "task_id"}, "T1")
|
||||
assertStatusAndJSONField(t, handler, "/api/runs/run_web_001/blocked", http.StatusOK, []string{"blocked", "0", "task", "task_id"}, "T1")
|
||||
assertStatusAndJSONField(t, handler, "/api/threads/"+dispatch.Attempt.ThreadID, http.StatusOK, []string{"thread", "thread", "thread_id"}, dispatch.Attempt.ThreadID)
|
||||
}
|
||||
|
||||
func TestRouterMapsNotFoundErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
sqlDB, err := dbpkg.Open(ctx, dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
if err := dbpkg.ApplyMigrations(ctx, sqlDB); err != nil {
|
||||
t.Fatalf("apply migrations: %v", err)
|
||||
}
|
||||
|
||||
handler := NewRouter(app.NewWebService(sqlDB))
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/runs/missing-run", nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", rec.Code)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
|
||||
code := nestedString(t, payload, "error", "code")
|
||||
if code != "not_found" {
|
||||
t.Fatalf("expected not_found error code, got %q", code)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStatusAndJSONField(t *testing.T, handler http.Handler, path string, wantStatus int, fieldPath []string, want string) {
|
||||
t.Helper()
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != wantStatus {
|
||||
t.Fatalf("GET %s: expected status %d, got %d", path, wantStatus, rec.Code)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("GET %s: decode response: %v", path, err)
|
||||
}
|
||||
|
||||
got := nestedString(t, payload, fieldPath...)
|
||||
if got != want {
|
||||
t.Fatalf("GET %s: expected %q at %v, got %q", path, want, fieldPath, got)
|
||||
}
|
||||
}
|
||||
|
||||
func nestedString(t *testing.T, value any, path ...string) string {
|
||||
t.Helper()
|
||||
|
||||
current := value
|
||||
for _, part := range path {
|
||||
switch typed := current.(type) {
|
||||
case map[string]any:
|
||||
current = typed[part]
|
||||
case []any:
|
||||
if len(part) != 1 || part[0] < '0' || part[0] > '9' {
|
||||
t.Fatalf("path segment %q is not a numeric index", part)
|
||||
}
|
||||
index := int(part[0] - '0')
|
||||
if index >= len(typed) {
|
||||
t.Fatalf("index %d out of range for path %v", index, path)
|
||||
}
|
||||
current = typed[index]
|
||||
default:
|
||||
t.Fatalf("unsupported type %T at path %v", current, path)
|
||||
}
|
||||
}
|
||||
|
||||
got, ok := current.(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected string at path %v, got %T", path, current)
|
||||
}
|
||||
return got
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"ai-workflow-skill/internal/store"
|
||||
)
|
||||
|
||||
type ReadService struct {
|
||||
db *sql.DB
|
||||
orch *store.OrchStore
|
||||
inbox *store.InboxStore
|
||||
}
|
||||
|
||||
type RunListItem struct {
|
||||
Run store.Run `json:"run"`
|
||||
TaskCounts map[string]int `json:"task_counts"`
|
||||
TotalTasks int `json:"total_tasks"`
|
||||
}
|
||||
|
||||
type RunDetail struct {
|
||||
Run store.Run `json:"run"`
|
||||
TaskCounts map[string]int `json:"task_counts"`
|
||||
TotalTasks int `json:"total_tasks"`
|
||||
Tasks []store.Task `json:"tasks"`
|
||||
BlockedTasks []store.BlockedTask `json:"blocked_tasks"`
|
||||
}
|
||||
|
||||
func NewReadService(db *sql.DB) *ReadService {
|
||||
return &ReadService{
|
||||
db: db,
|
||||
orch: store.NewOrchStore(db),
|
||||
inbox: store.NewInboxStore(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ReadService) ListRuns(ctx context.Context) ([]RunListItem, error) {
|
||||
rows, err := s.db.QueryContext(
|
||||
ctx,
|
||||
`SELECT run_id, goal, summary, status, created_at, updated_at
|
||||
FROM runs
|
||||
ORDER BY updated_at DESC, created_at DESC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query runs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var runs []store.Run
|
||||
runIDs := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var (
|
||||
run store.Run
|
||||
createdAt, updated string
|
||||
)
|
||||
if err := rows.Scan(
|
||||
&run.RunID,
|
||||
&run.Goal,
|
||||
&run.Summary,
|
||||
&run.Status,
|
||||
&createdAt,
|
||||
&updated,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scan run list row: %w", err)
|
||||
}
|
||||
|
||||
run.CreatedAt = parseRFC3339(createdAt)
|
||||
run.UpdatedAt = parseRFC3339(updated)
|
||||
runs = append(runs, run)
|
||||
runIDs = append(runIDs, run.RunID)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate runs: %w", err)
|
||||
}
|
||||
|
||||
countsByRunID, err := s.collectTaskCounts(ctx, runIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]RunListItem, 0, len(runs))
|
||||
for _, run := range runs {
|
||||
taskCounts := countsByRunID[run.RunID]
|
||||
if taskCounts == nil {
|
||||
taskCounts = map[string]int{}
|
||||
}
|
||||
|
||||
items = append(items, RunListItem{
|
||||
Run: run,
|
||||
TaskCounts: taskCounts,
|
||||
TotalTasks: totalTasks(taskCounts),
|
||||
})
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *ReadService) GetRunDetail(ctx context.Context, runID string) (RunDetail, error) {
|
||||
overview, err := s.orch.GetRunOverview(ctx, runID)
|
||||
if err != nil {
|
||||
return RunDetail{}, err
|
||||
}
|
||||
|
||||
blocked, err := s.orch.ListBlockedTasks(ctx, runID)
|
||||
if err != nil {
|
||||
return RunDetail{}, err
|
||||
}
|
||||
|
||||
return RunDetail{
|
||||
Run: overview.Run,
|
||||
TaskCounts: overview.TaskCounts,
|
||||
TotalTasks: totalTasks(overview.TaskCounts),
|
||||
Tasks: overview.Tasks,
|
||||
BlockedTasks: blocked,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ReadService) ListRunTasks(ctx context.Context, runID string) ([]store.Task, error) {
|
||||
detail, err := s.GetRunDetail(ctx, runID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return detail.Tasks, nil
|
||||
}
|
||||
|
||||
func (s *ReadService) ListBlockedTasks(ctx context.Context, runID string) ([]store.BlockedTask, error) {
|
||||
return s.orch.ListBlockedTasks(ctx, runID)
|
||||
}
|
||||
|
||||
func (s *ReadService) GetThreadDetail(ctx context.Context, threadID string) (store.ThreadDetail, error) {
|
||||
return s.inbox.GetThread(ctx, threadID)
|
||||
}
|
||||
|
||||
func (s *ReadService) collectTaskCounts(ctx context.Context, runIDs []string) (map[string]map[string]int, error) {
|
||||
result := make(map[string]map[string]int, len(runIDs))
|
||||
if len(runIDs) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
args := make([]any, 0, len(runIDs))
|
||||
for _, runID := range runIDs {
|
||||
args = append(args, runID)
|
||||
}
|
||||
|
||||
rows, err := s.db.QueryContext(
|
||||
ctx,
|
||||
`SELECT run_id, status, COUNT(*)
|
||||
FROM tasks
|
||||
WHERE run_id IN (`+placeholders(len(runIDs))+`)
|
||||
GROUP BY run_id, status`,
|
||||
args...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query task counts for runs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
runID string
|
||||
status string
|
||||
count int
|
||||
)
|
||||
if err := rows.Scan(&runID, &status, &count); err != nil {
|
||||
return nil, fmt.Errorf("scan run task count: %w", err)
|
||||
}
|
||||
if result[runID] == nil {
|
||||
result[runID] = make(map[string]int)
|
||||
}
|
||||
result[runID][status] = count
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate run task counts: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func totalTasks(counts map[string]int) int {
|
||||
total := 0
|
||||
for _, count := range counts {
|
||||
total += count
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func placeholders(count int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, count*2-1)
|
||||
for i := 0; i < count; i++ {
|
||||
if i > 0 {
|
||||
buf = append(buf, ',')
|
||||
}
|
||||
buf = append(buf, '?')
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func parseRFC3339(value string) time.Time {
|
||||
parsed, err := time.Parse(time.RFC3339Nano, value)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "ai-workflow-skill",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.25.0",
|
||||
"scripts": {
|
||||
"web:dev": "pnpm --filter @ai-workflow-skill/web dev",
|
||||
"web:build": "pnpm --filter @ai-workflow-skill/web build",
|
||||
"web:preview": "pnpm --filter @ai-workflow-skill/web preview",
|
||||
"web:typecheck": "pnpm --filter @ai-workflow-skill/web typecheck"
|
||||
}
|
||||
}
|
||||
Generated
+697
@@ -0,0 +1,697 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.91.2
|
||||
version: 5.91.2(react@19.2.4)
|
||||
'@tanstack/react-router':
|
||||
specifier: ^1.167.5
|
||||
version: 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
version: 19.2.14
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@8.0.1)
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
|
||||
packages:
|
||||
|
||||
'@emnapi/core@1.9.1':
|
||||
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
|
||||
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||
|
||||
'@oxc-project/types@0.120.0':
|
||||
resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.7':
|
||||
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
|
||||
|
||||
'@tanstack/history@1.161.6':
|
||||
resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==}
|
||||
engines: {node: '>=20.19'}
|
||||
|
||||
'@tanstack/query-core@5.91.2':
|
||||
resolution: {integrity: sha512-Uz2pTgPC1mhqrrSGg18RKCWT/pkduAYtxbcyIyKBhw7dTWjXZIzqmpzO2lBkyWr4hlImQgpu1m1pei3UnkFRWw==}
|
||||
|
||||
'@tanstack/react-query@5.91.2':
|
||||
resolution: {integrity: sha512-GClLPzbM57iFXv+FlvOUL56XVe00PxuTaVEyj1zAObhRiKF008J5vedmaq7O6ehs+VmPHe8+PUQhMuEyv8d9wQ==}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
|
||||
'@tanstack/react-router@1.167.5':
|
||||
resolution: {integrity: sha512-s1nP6l/7BYZfSwhoNbB7/rUmZ07q/AvkmhBoiDQl3tgy5dpb9Q1qjtIapYdvCOrao1aA/QCaWqxcbGc2Ct1bvQ==}
|
||||
engines: {node: '>=20.19'}
|
||||
peerDependencies:
|
||||
react: '>=18.0.0 || >=19.0.0'
|
||||
react-dom: '>=18.0.0 || >=19.0.0'
|
||||
|
||||
'@tanstack/react-store@0.9.2':
|
||||
resolution: {integrity: sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@tanstack/router-core@1.167.5':
|
||||
resolution: {integrity: sha512-8fRgJ0zNJf77R4grCaJQ5Imatjyc4YT5v8rlsPkYYYeUlcFNLbuFRhLlAMdND9gRUMznpnbRDXngpTPgx2K7HQ==}
|
||||
engines: {node: '>=20.19'}
|
||||
hasBin: true
|
||||
|
||||
'@tanstack/store@0.9.2':
|
||||
resolution: {integrity: sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA==}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/react-dom@19.2.3':
|
||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.2.0
|
||||
|
||||
'@types/react@19.2.14':
|
||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||
|
||||
'@vitejs/plugin-react@6.0.1':
|
||||
resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
peerDependencies:
|
||||
'@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
|
||||
babel-plugin-react-compiler: ^1.0.0
|
||||
vite: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@rolldown/plugin-babel':
|
||||
optional: true
|
||||
babel-plugin-react-compiler:
|
||||
optional: true
|
||||
|
||||
cookie-es@2.0.0:
|
||||
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
isbot@5.1.36:
|
||||
resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.32.0:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.8:
|
||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
react-dom@19.2.4:
|
||||
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
|
||||
react@19.2.4:
|
||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
rolldown@1.0.0-rc.10:
|
||||
resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
seroval-plugins@1.5.1:
|
||||
resolution: {integrity: sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
seroval: ^1.0
|
||||
|
||||
seroval@1.5.1:
|
||||
resolution: {integrity: sha512-OwrZRZAfhHww0WEnKHDY8OM0U/Qs8OTfIDWhUD4BLpNJUfXK4cGmjiagGze086m+mhI+V2nD0gfbHEnJjb9STA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
tiny-warning@1.0.3:
|
||||
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
use-sync-external-store@1.6.0:
|
||||
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
vite@8.0.1:
|
||||
resolution: {integrity: sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
'@vitejs/devtools': ^0.1.0
|
||||
esbuild: ^0.27.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitejs/devtools':
|
||||
optional: true
|
||||
esbuild:
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@emnapi/core@1.9.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.2.0
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.1':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.9.1
|
||||
'@emnapi/runtime': 1.9.1
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@oxc-project/types@0.120.0': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-rc.10':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.1.1
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.10': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.7': {}
|
||||
|
||||
'@tanstack/history@1.161.6': {}
|
||||
|
||||
'@tanstack/query-core@5.91.2': {}
|
||||
|
||||
'@tanstack/react-query@5.91.2(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/query-core': 5.91.2
|
||||
react: 19.2.4
|
||||
|
||||
'@tanstack/react-router@1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/history': 1.161.6
|
||||
'@tanstack/react-store': 0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/router-core': 1.167.5
|
||||
isbot: 5.1.36
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
'@tanstack/react-store@0.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@tanstack/store': 0.9.2
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
use-sync-external-store: 1.6.0(react@19.2.4)
|
||||
|
||||
'@tanstack/router-core@1.167.5':
|
||||
dependencies:
|
||||
'@tanstack/history': 1.161.6
|
||||
'@tanstack/store': 0.9.2
|
||||
cookie-es: 2.0.0
|
||||
seroval: 1.5.1
|
||||
seroval-plugins: 1.5.1(seroval@1.5.1)
|
||||
tiny-invariant: 1.3.3
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
'@tanstack/store@0.9.2': {}
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@types/react@19.2.14':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vitejs/plugin-react@6.0.1(vite@8.0.1)':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||
vite: 8.0.1
|
||||
|
||||
cookie-es@2.0.0: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
isbot@5.1.36: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.32.0:
|
||||
optional: true
|
||||
|
||||
lightningcss@1.32.0:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.32.0
|
||||
lightningcss-darwin-arm64: 1.32.0
|
||||
lightningcss-darwin-x64: 1.32.0
|
||||
lightningcss-freebsd-x64: 1.32.0
|
||||
lightningcss-linux-arm-gnueabihf: 1.32.0
|
||||
lightningcss-linux-arm64-gnu: 1.32.0
|
||||
lightningcss-linux-arm64-musl: 1.32.0
|
||||
lightningcss-linux-x64-gnu: 1.32.0
|
||||
lightningcss-linux-x64-musl: 1.32.0
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
postcss@8.5.8:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
react-dom@19.2.4(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.4: {}
|
||||
|
||||
rolldown@1.0.0-rc.10:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.120.0
|
||||
'@rolldown/pluginutils': 1.0.0-rc.10
|
||||
optionalDependencies:
|
||||
'@rolldown/binding-android-arm64': 1.0.0-rc.10
|
||||
'@rolldown/binding-darwin-arm64': 1.0.0-rc.10
|
||||
'@rolldown/binding-darwin-x64': 1.0.0-rc.10
|
||||
'@rolldown/binding-freebsd-x64': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.10
|
||||
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.10
|
||||
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.10
|
||||
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.10
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.10
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.10
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
seroval-plugins@1.5.1(seroval@1.5.1):
|
||||
dependencies:
|
||||
seroval: 1.5.1
|
||||
|
||||
seroval@1.5.1: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tiny-warning@1.0.3: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
vite@8.0.1:
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.8
|
||||
rolldown: 1.0.0-rc.10
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- apps/*
|
||||
Reference in New Issue
Block a user