# Orchestra Web UI Plan ## Goal Build a proper TypeScript single-page application for local operation of Orchestra. It must make live task/session state observable, make every supported control actionable, and render harness approval requests as the actual pending decision rather than as a vague `blocked` badge. Authentication is deliberately out of scope for this first local-only version; the API contract must still keep a clean boundary where auth can be added later. ## Product scope ### Monitoring - Task board with queued, leased, blocked, completed, and failed states. - Task detail with description, lifecycle timeline, current worker/harness, lease expiry, handoff/report references, and Git/worktree facts when a session is live. - Worker view: online/offline state, capacity, last heartbeat, active task, and harness identity. - Session view: agent status, occupancy, turn-boundary health, recent pane capture, extracted blocker, and update timestamp. - Artifact view for handoffs and reports, with JSON formatting for canonical handoffs and plain-text/Markdown rendering for reports. ### Controls - Create a task with all currently accepted task fields. - Request a handoff/rotation through the existing task-control path. - Grant or deny a currently displayed harness permission. - Explicitly release, block, or complete a task only where the backend can validate the required evidence. - Refresh and polling controls; no control is rendered merely as decoration. ## Architecture ### Frontend Create `web/` as a Vite + React + TypeScript application. - React Router routes: `/`, `/tasks/:taskID`, `/workers`, and `/artifacts/:ref`. - TanStack Query for polling, cache invalidation after controls, retry/error state, and optimistic UI only after the server returns success. - A small component layer (CSS modules or Tailwind, chosen during setup) with accessible dialogs, keyboard-focus handling, and responsive layouts. - Build output is embedded into the Go binary with `go:embed` and served at `/`; `/v1/*` remains API-only. The Go server serves `index.html` as the SPA fallback and static assets with cache headers. - A typed API client generated from hand-maintained TypeScript interfaces in `web/src/api/types.ts`; those interfaces are exercised against Go JSON response tests so frontend/backend field drift fails CI. ### Backend API additions Keep existing endpoints working. Add a UI-oriented read model rather than forcing the browser to join unrelated raw endpoints itself. | Endpoint | Purpose | |---|---| | `GET /v1/ui/overview` | Tasks, workers, orchestration health, and active-session summaries in one pollable response. | | `GET /v1/ui/tasks/{id}` | Full task detail, ordered lifecycle events, active session, blocker, artifact refs, and available actions. | | `GET /v1/ui/tasks/{id}/capture?source=recent` | Current pane capture plus revision/timestamp; never silently returns stale text as live. | | `GET /v1/artifacts/{ref}` | Existing artifact fetch, with content type supplied by the UI read model where known. | | `POST /v1/ui/tasks` | Create task; validates the same fields as `POST /v1/tasks`. | | `POST /v1/ui/tasks/{id}/actions/{action}` | Narrow action wrapper for handoff request, approval grant/deny, release, block, and completion; returns the resulting task/session state. | The raw lifecycle endpoints stay available for harness/worker use. The UI wrapper is responsible for describing whether an action is currently allowed, why it is unavailable, and what evidence/body it needs. ## Correct approval design The approval UI must reflect the real permission prompt, not the existing event-only `ApprovalGranted`/`ApprovalDenied` records. 1. Add a structured `PendingApproval` model: ```ts type PendingApproval = { kind: "shell" | "edit" | "unknown"; summary: string; command?: string; diff?: string; paneId: string; captureRevision: number; detectedAt: string; }; ``` 2. Parse the current `pane.read` text worker-side. Preserve the exact shell command or proposed edit/diff shown by the harness. If parsing is uncertain, return `kind: "unknown"` with the raw relevant excerpt and disable the grant button; do not guess. 3. Make approval a real execution command. `grant` sends the correct `agent.send_keys`/`pane.send_keys` confirmation to the pane only if the same `pane_id`, permission kind, and capture revision are still pending. `deny` sends the explicit reject key sequence. Record the corresponding `ApprovalGranted`/`ApprovalDenied` event only after the herdr action is acknowledged. 4. The coordinator cannot directly control a workpc-owned Unix herdr socket. Extend the federation worker protocol with a durable, worker-pulled control-command stream (or command queue): - coordinator queues `grant_approval` / `deny_approval` addressed to a worker and task; - worker validates its local current capture revision and sends the key; - worker posts an acknowledged result or a stale/rejected result; - the UI polls the final result and removes/enables controls accordingly. This is required before rendering remote approval buttons as enabled. A local coordinator-owned session may use the same command contract via a local implementation, so UI behavior is identical across machines. 5. Render approvals in an accessible modal and task-detail panel: visible command/diff, target pane, capture timestamp, keyboard focus trapped in the dialog, `Approve` and `Reject` buttons, a stale-state warning, and no implicit approval on refresh or Enter outside the focused button. ## UI layout - **Overview:** summary counters, worker health strip, active/blocked session cards, and the task board. - **Task detail:** immutable task instruction, timeline, session telemetry, live capture, handoff/report evidence, and the contextual action panel. - **Session capture:** terminal-style, selectable text with an explicit source label (`recent`, `scrollback`, etc.), refresh time, and truncation indicator. - **Approval dialog:** command or edit preview first, then consequence and worker/pane metadata; approval controls only when `PendingApproval` is current and actionable. - **Workers:** capacity/heartbeat table, active lease/session link, and visible offline/degraded reasons. ## Delivery phases 1. **Contracts and read model** — define Go UI DTOs, add overview/task-detail endpoints, tests, and structured local session/capture/approval parsing. 2. **React shell** — scaffold Vite/React/TypeScript, embed production build, implement overview, task detail, workers, polling, loading, and error states. 3. **Evidence and controls** — artifact viewer, task creation, action confirmation UX, and disabled-state explanations. 4. **Federated approvals** — worker command queue, stale-revision protection, actual grant/deny execution, audit events, and UI result handling. 5. **Verification** — Go API tests, TypeScript unit/component tests, Playwright flows, and a live workpc OpenCode E2E covering: task visible → permission rendered with exact command → explicit approval → pane advances → action result and lifecycle update visible. ## Acceptance criteria - `npm run build`, `npm run lint`, TypeScript checks, Go build/vet/tests, and UI browser tests pass in CI. - Every rendered active session has a real source endpoint and a visible degraded/error state if capture is unavailable. - Every enabled control reaches a tested backend path; no placeholder buttons. - A remote approval cannot be granted against a different or stale prompt. - The approval panel displays the exact pending command or edit preview from the current pane capture. - The writer/reviewer workflow can be monitored from task creation through completion without SSH or raw JSON-RPC inspection.