Add web product Phase 1 skeleton

This commit is contained in:
2026-03-20 00:20:38 +08:00
parent 0355d7a847
commit a7ef1e0154
24 changed files with 2287 additions and 6 deletions
+3
View File
@@ -2,6 +2,9 @@
.orch/
bin/
dist/
node_modules/
apps/web/dist/
apps/web/.vite/
*.db
coverage.out
.DS_Store
+77
View File
@@ -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
+363
View File
@@ -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'
+12
View File
@@ -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>
+25
View File
@@ -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"
}
}
+104
View File
@@ -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>
);
}
+17
View File
@@ -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>,
);
+154
View File
@@ -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;
}
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+20
View File
@@ -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"]
}
+14
View File
@@ -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',
},
},
});
+66
View File
@@ -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)
}
}
+53 -6
View File
@@ -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`
+1
View File
@@ -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
)
+2
View File
@@ -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=
+39
View File
@@ -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)
}
+58
View File
@@ -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)
}
+87
View File
@@ -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
}
+196
View File
@@ -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
}
+211
View File
@@ -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
}
+11
View File
@@ -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"
}
}
+697
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
packages:
- apps/*