From d62e8f967ef0f6f97e073e0e09140412ff4b2e61 Mon Sep 17 00:00:00 2001 From: Anthony Franco Date: Wed, 27 May 2026 11:23:22 -0600 Subject: [PATCH 01/14] Oz design brief + ADR-0008: 5-section nav, runs/priorities folded into Dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct the Oz control-plane IA to match the founder's stated layout: - Left nav is exactly five sections — Dashboard, Workspaces, CLIs, Personas, Settings. No standalone Runs or Priorities pages. - Dashboard is built around the Oz chat as the command center; priorities and runs are supporting panels inside it (Oz watches runs from the Dashboard). - Design prompt rewritten to design from the brief alone (don't reuse the current shipped oz-dashboard UI), which is what caused the prior drift. - ADR-0008: five surfaces + new decision on Dashboard composition; six-section layout recorded under rejected alternatives. v0.4 priority work items realigned. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../0008-oz-control-plane-architecture.md | 4 +- .../v0.4-oz-control-plane/README.md | 11 ++--- docs/oz-design-brief.md | 43 ++++++++++--------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/cocoder/decisions/0008-oz-control-plane-architecture.md b/cocoder/decisions/0008-oz-control-plane-architecture.md index 852fe5d..571e851 100644 --- a/cocoder/decisions/0008-oz-control-plane-architecture.md +++ b/cocoder/decisions/0008-oz-control-plane-architecture.md @@ -20,7 +20,8 @@ v0.1 ships CoCoder terminal-only, and the dogfood loop now works end to end: lau 4. **GUI ⇄ Oz parity.** Every GUI action (launch, reorder, edit) is also expressible as an Oz instruction, and the reverse; the two stay in sync (e.g. priorities are drag-reorderable in the GUI *and* reorderable by asking Oz). 5. **Orchestration sessions execute externally.** Oscar/Bob/etc. run in iTerm today (an embedded Electron terminal harness later). Oz observes and controls them — status, transcript, evidence, stop, attach — but does not embed the live terminals yet. 6. **Persona execution model.** Each persona binds to a **CLI (adapter) + model** (with a `default` option that defers to the CLI's own default); a persona may delegate to **sub-agents/services that each independently select CLI + model** (a configuration hierarchy); each persona has a **visible | headless** run mode. Oz itself is headless. -7. **Oz surfaces** (top-level navigation), never exposing raw JSON: **Dashboard** (workspace picker, Oz chat, priorities with drag-reorder + an "ad-hoc run" launcher above the list), **Workspaces** (roots + roles), **CLIs** (register + Test), **Personas** (CLI/model + sub-agent hierarchy + visible/headless + "new persona via priority"), **Runs** (list + detail), **Settings** (human-friendly forms only). +7. **Oz surfaces** — exactly **five** top-level navigation items, never exposing raw JSON: **Dashboard**, **Workspaces** (roots + roles), **CLIs** (register + Test), **Personas** (CLI/model + sub-agent hierarchy + visible/headless + "new persona via priority"), **Settings** (human-friendly forms only). There is no standalone Runs page and no standalone Priorities page. +8. **The Dashboard is the operator's hub, built around the Oz chat as the command center.** The Oz conversation is the primary surface; **priorities** (ordered list, drag-reorder, + an "ad-hoc run" launcher above the list) and **runs** (what's running now / recent, with run detail — transcript, evidence, status, stop, attach — opening in place) are **supporting panels inside the Dashboard**, not separate screens. This follows from Oz being the per-workspace watcher (decision 3): the founder watches and drives runs from the Dashboard, through Oz. ## Consequences @@ -34,3 +35,4 @@ v0.1 ships CoCoder terminal-only, and the dogfood loop now works end to end: lau - **Oz as a passive status board** (buttons only; chat advisory) — rejected; the founder chose chat-as-primary-command-interface. - **Embed live orchestration terminals in Oz now** — deferred to the Electron terminal harness; keeping sessions external in iTerm keeps v0.x shippable. - **Per-tool screens with no unifying control plane** — rejected; Oz is the single operator surface. +- **Six-section nav with standalone Runs + Priorities screens** (an earlier draft of this ADR / `docs/oz-design-brief.md`) — rejected; it mirrored the current shipped app rather than the founder's intent. Runs and Priorities are panels inside the Dashboard, because Oz watches runs and the founder works from the Dashboard conversation. diff --git a/cocoder/priorities/v0.4-oz-control-plane/README.md b/cocoder/priorities/v0.4-oz-control-plane/README.md index 0d8b6e9..d927634 100644 --- a/cocoder/priorities/v0.4-oz-control-plane/README.md +++ b/cocoder/priorities/v0.4-oz-control-plane/README.md @@ -10,19 +10,20 @@ Turn Oz into a real **operator control plane**: a per-workspace, in-dashboard he ## Architecture (decided) -- [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) — Oz control-plane architecture (Oz persona model, command + watch, GUI⇄Oz parity, external orchestration sessions, persona exec model incl. visible/headless, the six surfaces). +- [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) — Oz control-plane architecture (Oz persona model, command + watch, GUI⇄Oz parity, external orchestration sessions, persona exec model incl. visible/headless, the five surfaces with runs + priorities folded into the Dashboard). - [ADR-0007](../../decisions/0007-workspace-files-and-multiroot-description.md) (revised 2026-05-27) — root-role taxonomy primary / writable / read-only. - Screen/flow brief + design prompt: [`docs/oz-design-brief.md`](../../../docs/oz-design-brief.md) (intent; refined by the claude.ai/design output). ## Work items (provisional — refine after design output lands) -1. **Dashboard** — workspace picker, in-app Oz chat (primary command interface), priorities list with **drag-and-drop reorder** + an **ad-hoc "run without a priority"** launcher. +1. **Dashboard** — the operator's hub, built around the **Oz chat as the command center** (primary command interface). Supporting panels *inside* the Dashboard: **priorities** list with **drag-and-drop reorder** + an **ad-hoc "run without a priority"** launcher; **runs** (what's running now / recent) with run detail (live transcript / evidence / status / stop / attach) opening in place as Oz's window into externally-running iTerm sessions. No standalone Runs or Priorities pages. 2. **Workspaces** — add/edit workspaces (name + description) and roots with the three roles (primary / writable / read-only); plumb `role` as a first-class registry field (the registry entry schema already `.passthrough()`es — extends WS-DESC work). 3. **CLIs** — register adapters + a **Test** button returning success or the exact error. 4. **Personas** — per-persona **CLI + model** (with `default`), **sub-agent/service CLI+model hierarchy**, **visible/headless** run mode; list defaults incl. Oz; "create a new persona via a priority." -5. **Runs** — list + run detail (live transcript / evidence / status / stop / attach) as Oz's window into externally-running iTerm sessions. -6. **Oz oversight / debugger** — Oz as live watcher of all workspace runs; the Orchestrator Debugger surface (reference: CoBuilder `ORCH DEBUGGER`); terminal-state awareness (the daemon already refuses mutations on terminal runs — surface it). -7. **Settings** — human-friendly only, never JSON; extensible. +5. **Oz oversight / debugger** — Oz as live watcher of all workspace runs (surfaced in the Dashboard runs panel); the Orchestrator Debugger surface (reference: CoBuilder `ORCH DEBUGGER`); terminal-state awareness (the daemon already refuses mutations on terminal runs — surface it). +6. **Settings** — human-friendly only, never JSON; extensible. + +Note: the left nav is **five sections** — Dashboard, Workspaces, CLIs, Personas, Settings. Runs and Priorities are Dashboard panels, not top-level pages (corrects the earlier six-section draft). ## Open questions diff --git a/docs/oz-design-brief.md b/docs/oz-design-brief.md index ed984f6..7d5cc45 100644 --- a/docs/oz-design-brief.md +++ b/docs/oz-design-brief.md @@ -3,7 +3,7 @@ **Status:** intent — pending the claude.ai/design output, then implementation. **Decides nothing on its own:** the settled architecture is in [ADR-0008](../cocoder/decisions/0008-oz-control-plane-architecture.md) (Oz control-plane model) and [ADR-0007](../cocoder/decisions/0007-workspace-files-and-multiroot-description.md) (root roles). This file captures the **screen/flow brief** and the **verbatim design prompt** used to generate the Oz UI, so the intent is durable and feeds implementation under the `v0.4-oz-control-plane` priority. -Authored 2026-05-27. Audience: technical founder/operator. Visual styling is owned by the claude.ai/design system and is intentionally unspecified here. +Authored 2026-05-27. Revised 2026-05-27 to match the founder's stated layout: a **five-section** left nav (Dashboard · Workspaces · CLIs · Personas · Settings) with **runs and priorities folded into the Dashboard**, and the **Oz chat as the command center** — superseding the earlier six-section draft that mirrored the current app (standalone Runs + Priorities screens). Audience: technical founder/operator. Visual styling is owned by the claude.ai/design system and is intentionally unspecified here. --- @@ -12,7 +12,7 @@ Authored 2026-05-27. Audience: technical founder/operator. Visual styling is own ``` # Design brief: "Oz" — the control plane for CoCoder -You have NO prior context on CoCoder. Read this fully, then (1) map the user flows, (2) design each screen, (3) define states for each. Use your existing design system for all visual styling — this brief is about information architecture, flows, screens, components, and data, not aesthetics. +You have NO prior context on CoCoder, and you must NOT reuse any existing CoCoder UI as a reference — design from this brief alone. Read it fully, then (1) map the user flows, (2) design each screen, (3) define states for each. Use your existing design system for all visual styling — this brief is about information architecture, flows, screens, components, and data, not aesthetics. ## What CoCoder is CoCoder is a local, self-improving AI software-orchestration engine for a technical founder. Instead of coding directly, the founder directs a small team of AI "personas" (each backed by a coding CLI like Claude Code, Codex, or Cursor) that plan, build, test, and review software across one or more code repositories. Work is organized into prioritized units; the AI team executes them as "runs" the founder supervises. @@ -20,6 +20,10 @@ CoCoder is a local, self-improving AI software-orchestration engine for a techni ## What you're designing: Oz Oz is the control plane — the single app where the founder runs the whole operation. The audience is a technical founder/operator comfortable with terms like run, persona, CLI, root, priority. Design it as a desktop-class web app (persistent left-side navigation; will later be wrapped in Electron). Never show raw JSON anywhere — always human-friendly forms, tables, and controls. +## Navigation — exactly five top-level sections +The left nav has FIVE items and only these: Dashboard, Workspaces, CLIs, Personas, Settings. +- There is NO standalone "Runs" section and NO standalone "Priorities" section. Both live INSIDE the Dashboard (see below). Do not invent a separate Runs page or Priorities page. + ## Core concepts (the data model you're designing around) - Workspace — a named, described project context. It bundles one or more root folders. Everything in Oz is scoped to the currently selected workspace. There can be many workspaces. - Root folder — a directory in a workspace. Each root has: Name, Path, and a Role: @@ -33,23 +37,25 @@ Oz is the control plane — the single app where the founder runs the whole oper - Oz — itself a headless persona: the in-app chatbot + watcher described below. ## The Oz interaction model (important) -- Oz is an in-dashboard, headless chatbot and the primary command interface: the founder converses with Oz to do everything — launch runs, add/reorder priorities, kick off ad-hoc tasks (code reviews, refactors, research), and ask for status. GUI controls (buttons, drag-drop) are shortcuts for the same actions Oz can take — both must exist and stay in sync. -- Oz is also the primary watcher/interface for all runs in the selected workspace: it monitors every run and surfaces progress, decisions it needs from the founder, and results — all inside Oz. +- Oz is an in-dashboard, headless chatbot and THE primary command interface: the founder converses with Oz to do everything — launch runs, add/reorder priorities, kick off ad-hoc tasks (code reviews, refactors, research), and ask for status. GUI controls (buttons, drag-drop) are shortcuts for the same actions Oz can take — both must exist and stay in sync. +- Oz is also the primary watcher/interface for all runs in the selected workspace: it monitors every run and surfaces progress, decisions it needs from the founder, and results — all inside the Dashboard. - There is one Oz per workspace. Switching the workspace switches the Oz conversation, its priorities, and its runs. - The actual orchestration sessions execute externally (today in iTerm terminal windows; later an embedded Electron terminal). Oz does not embed those live terminals — it observes/controls them and shows their transcript/evidence/status. Design run views as Oz's window into externally-running sessions. ## Your task -1. User flows first. Before screens, map the key flows and include them: e.g., "first-time setup → add a CLI → test it → create a workspace → add roots → set personas → launch the first run"; "daily: pick workspace → talk to Oz / pick a priority → launch → watch the run → review result"; "ad-hoc: launch a run with no priority to do a refactor/review/research." +1. User flows first. Before screens, map the key flows and include them: e.g., "first-time setup → add a CLI → test it → create a workspace → add roots → set personas → launch the first run"; "daily: pick workspace → talk to Oz / pick a priority → launch → watch the run in the Dashboard → review result"; "ad-hoc: launch a run with no priority to do a refactor/review/research." 2. Design each screen below with its component breakdown. 3. Define states for each screen: empty (nothing configured yet), loading, active/live, and error. ## Screens -### 1. Dashboard (per selected workspace) -- Workspace picker at the top — switches the entire context (Oz conversation, priorities, runs). -- Oz Terminal — the chatbot conversation with this workspace's Oz: the primary place to issue commands and watch runs. Show conversation history, an input, and inline run/status updates Oz surfaces. Make clear this is a live, working conversation, not a help bot. -- Priorities — the workspace's ordered priority list, reorderable by drag-and-drop (top = next up). Each priority shows its name/summary/status and a Launch action. At the top of the list, an "Launch a run without a priority" action: lets the founder describe an ad-hoc task for the orchestrator to run (e.g., add new priorities, code review, refactor, research) without it being a formal priority. -- Should give an at-a-glance sense of what's running now and what's next. +### 1. Dashboard (the operator's home; per selected workspace) +This is the operational hub. It is built AROUND the Oz conversation; priorities and runs are supporting panels, not separate pages. +- Workspace picker at the top — switches the entire context (which Oz, its priorities, its runs). +- Oz Terminal — THE command center and the primary surface of the whole app. It is the chat conversation with this workspace's headless Oz persona: the founder types here to launch runs, add/reorder priorities, start ad-hoc tasks, and ask for status. Show conversation history, an input, and inline run/status updates Oz surfaces. Make clear this is a live, working conversation that drives the system, not a help bot. Everything else on the Dashboard is a supporting panel that mirrors actions Oz can also take by chat. +- Priorities (supporting panel — lives here, not its own page) — the workspace's ordered priority list, reorderable by drag-and-drop (top = next up). Each priority shows its name/summary/status and a Launch action. At the top of the list, a "Launch a run without a priority" action: lets the founder describe an ad-hoc task for the orchestrator to run (e.g., add new priorities, code review, refactor, research) without it being a formal priority. +- Runs (supporting panel — lives here, not its own page) — Oz is the watcher for every run in this workspace. Show what's running now and recent runs with status, what they worked on, and timing. Selecting a run opens its detail in place (drawer/inspector within the Dashboard): live read-only transcript, evidence/results, status, and controls (stop, attach / copy-attach-command). Remember: the live session runs externally (iTerm today, embedded Electron terminal later) — this is Oz's window into it, never an embedded live terminal. +- Overall, the Dashboard should give an at-a-glance sense of what's running now and what's next, with the Oz conversation front and center. ### 2. Workspaces - List of workspaces (name + description); create/edit/delete. @@ -69,32 +75,29 @@ Oz is the control plane — the single app where the founder runs the whole oper - Sub-agents / services — a persona may delegate specific tasks to sub-agents/services; each sub-agent also gets its own CLI + Model selection, so the UI must express a hierarchy (persona → its sub-agents, each independently configurable). - A way to craft a priority that creates a new persona — i.e., starting "make a new persona" enqueues it as a workspace priority for the AI team to build, rather than a raw form. -### 5. Runs (per selected workspace) -- Workspace picker at the top (same pattern as Dashboard). -- A list of runs with status, what they worked on, and timing. -- A run detail view: live read-only transcript, evidence/results, status, and controls (stop, attach/copy-attach-command). Remember: the live session runs externally (iTerm) — this is Oz's view into it. - -### 6. Settings +### 5. Settings - Human-friendly configuration only — never raw JSON. Use forms/toggles. Treat the exact contents as flexible; design a clean, extensible settings layout (e.g., defaults, global preferences) that can grow. ## Global rules +- Exactly five top-level nav items: Dashboard, Workspaces, CLIs, Personas, Settings. Runs and Priorities are panels inside the Dashboard, never top-level pages. +- The Oz chat is the command center — the Dashboard is organized around it; other Dashboard panels are shortcuts for things the founder could also ask Oz to do. - Never expose JSON — everything is forms, tables, selectors, status chips. -- Workspace context is pervasive (Dashboard + Runs have the picker); make the active workspace obvious everywhere. +- Workspace context is pervasive (the Dashboard picker switches Oz, priorities, and runs together); make the active workspace obvious everywhere. - GUI ⇄ Oz parity — anything the founder can do via a button, they can also ask Oz to do; keep them consistent. - Use precise, technical terminology (run, persona, root, priority, CLI). -- Left-nav top level: Dashboard, Workspaces, CLIs, Personas, Runs, Settings. ## Deliverables - A user-flow map (the flows above). - Each screen designed with component breakdown and empty/loading/active/error states. -- The navigation/IA structure tying it together. +- The navigation/IA structure tying it together (five-section nav; runs + priorities inside the Dashboard). ``` --- ## Open items to resolve from the design output +- The **Dashboard composition** — Oz chat is the command center with priorities and runs as supporting panels; confirm the design keeps the conversation primary and doesn't regress runs/priorities into separate pages. - The **CLI + Model hierarchy** (persona → CLI → model, and sub-agent → CLI → model) is the trickiest interaction — confirm the design handles nesting cleanly. - **Settings** contents are intentionally loose; the design should be extensible without inventing final contents. - How the **ad-hoc "run without a priority"** capture maps to an actual run + (optionally) a new priority. -- The seam where the external iTerm session is later replaced by an **embedded Electron terminal** — keep the run-detail "window into the session" contract stable across that swap. +- The seam where the external iTerm session is later replaced by an **embedded Electron terminal** — keep the run-detail "window into the session" contract (the in-Dashboard run inspector) stable across that swap. From 3d305304e5585367c5da418b9bff654294ff34db Mon Sep 17 00:00:00 2001 From: Anthony Franco Date: Wed, 27 May 2026 11:41:19 -0600 Subject: [PATCH 02/14] Land Oz control-plane design spec; advance v0.4 to build-ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The claude.ai/design output (the "Oz — Control Plane Prototype") landed. It realizes v0.4-oz-control-plane (5-nav control plane per ADR-0008), with the embedded Electron terminal harness explicitly deferred to the brief's "v2". - docs/oz-control-plane-design/: the high-fidelity React prototype + brief + Fusion design-system tokens. Reference, not production code. - v0.4-oz-control-plane: status -> Active; "Design spec landed" section + designer-notes.md (simulated-vs-real wiring, what-not-to-inherit, arch invariants, a11y, scope). Decisions flagged for ADR-0010: pause/resume run primitive, `cocoder attach`, transcript streaming, persona-roster reconciliation (new "Doc"; Ian/Phil absent), in-app update channels. - v0.6-cocoder-ide: reframed to the deferred "v2" embedded Electron harness. - PRIORITIES.md: v0.4 promoted to Active; v0.6 reframed. Docs/planning only; no code changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- cocoder/PRIORITIES.md | 11 +- .../v0.4-oz-control-plane/README.md | 33 +- .../v0.4-oz-control-plane/designer-notes.md | 53 + cocoder/priorities/v0.6-cocoder-ide/README.md | 20 + docs/oz-control-plane-design/Oz.html | 33 + docs/oz-control-plane-design/README.md | 130 ++ docs/oz-control-plane-design/app.jsx | 524 ++++++++ docs/oz-control-plane-design/components.jsx | 584 ++++++++ docs/oz-control-plane-design/dashboard.jsx | 1174 +++++++++++++++++ docs/oz-control-plane-design/data.js | 355 +++++ .../assets/kanji-mark-deep-gold.svg | 7 + .../design-system/assets/kanji-mark-gold.svg | 7 + .../design-system/colors_and_type.css | 429 ++++++ .../fonts/JosefinSans-VariableFont_wght.ttf | Bin 0 -> 117720 bytes docs/oz-control-plane-design/dev-notes.js | 95 ++ docs/oz-control-plane-design/oz.css | 462 +++++++ docs/oz-control-plane-design/screens.jsx | 1171 ++++++++++++++++ docs/oz-control-plane-design/tweaks-panel.jsx | 530 ++++++++ 18 files changed, 5614 insertions(+), 4 deletions(-) create mode 100644 cocoder/priorities/v0.4-oz-control-plane/designer-notes.md create mode 100644 cocoder/priorities/v0.6-cocoder-ide/README.md create mode 100644 docs/oz-control-plane-design/Oz.html create mode 100644 docs/oz-control-plane-design/README.md create mode 100644 docs/oz-control-plane-design/app.jsx create mode 100644 docs/oz-control-plane-design/components.jsx create mode 100644 docs/oz-control-plane-design/dashboard.jsx create mode 100644 docs/oz-control-plane-design/data.js create mode 100644 docs/oz-control-plane-design/design-system/assets/kanji-mark-deep-gold.svg create mode 100644 docs/oz-control-plane-design/design-system/assets/kanji-mark-gold.svg create mode 100644 docs/oz-control-plane-design/design-system/colors_and_type.css create mode 100644 docs/oz-control-plane-design/design-system/fonts/JosefinSans-VariableFont_wght.ttf create mode 100644 docs/oz-control-plane-design/dev-notes.js create mode 100644 docs/oz-control-plane-design/oz.css create mode 100644 docs/oz-control-plane-design/screens.jsx create mode 100644 docs/oz-control-plane-design/tweaks-panel.jsx diff --git a/cocoder/PRIORITIES.md b/cocoder/PRIORITIES.md index 2c7622a..fa60888 100644 --- a/cocoder/PRIORITIES.md +++ b/cocoder/PRIORITIES.md @@ -16,6 +16,7 @@ Slim index of active and archived priorities. Open a priority's folder for detai | Slug | Description | Status | Canon | Owner | Blocked on | |---|---|---|---|---|---| | [`v0.1-foundation`](./priorities/v0.1-foundation/README.md) | Ship CoCoder v0.1 — extraction, Oz MVP, docs, public publish | Active | Expand — **Sub-Playbook D activated**; D Solve next. Suite **335/335** (+ dashboard 8/8). | Bob + founder | **Next:** D Solve. B/C Refines parallel (founder). | +| [`v0.4-oz-control-plane`](./priorities/v0.4-oz-control-plane/README.md) | Oz control plane — chat command interface + run oversight; 5-nav UI per ADR-0008 | Active | **Design spec landed 2026-05-27** (`docs/oz-control-plane-design/`); ADR-0010 + build plan next. | Bob + founder | Sequencing vs v0.2/v0.3 — founder. | ## Draft @@ -23,7 +24,7 @@ Slim index of active and archived priorities. Open a priority's folder for detai |---|---|---|---|---|---| | [`v0.2-adapter-extensibility`](./priorities/v0.2-adapter-extensibility/README.md) | Beyond local CLI models — cloud APIs (Anthropic Messages, Kimi K2.6), managed sessions (Cursor SDK), etc. | Draft | — | Bob + founder | After v0.1-foundation Complete **and now after v0.3-workspace-lifecycle** (2026-05-26 resequence). Depends on Sub-Playbook C Oz dashboard. Authored 2026-05-22 per founder ask. | | [`v0.3-workspace-lifecycle`](./priorities/v0.3-workspace-lifecycle/README.md) | Onboard into new/existing projects, manage multi-root workspaces, secure project secrets — via Oz | Draft | — | Bob + founder | **Sequenced before v0.2** (2026-05-26). Near-term "Dogfood Loop Enablement" slice first. Depends on Oz dashboard (Sub-Playbook C). ADR-0007 accepted. | -| [`v0.4-oz-control-plane`](./priorities/v0.4-oz-control-plane/README.md) | Oz as a real control plane — in-app chat command interface + run oversight/debugger; UI per ADR-0008 | Draft | — | Bob + founder | Founder decision. Depends on the claude.ai/design output + ADR-0008. Stub authored 2026-05-27. | +| [`v0.6-cocoder-ide`](./priorities/v0.6-cocoder-ide/README.md) | CoCoder IDE — embedded Electron terminal harness ("v2"): in-app live sessions + editor surfaces, extending the v0.4 control plane | Draft | — | Bob + founder | Reserved; after v0.4 ships. The design spec's deferred "v2". Authored 2026-05-27 per founder ask. | ## Recently Archived @@ -64,4 +65,10 @@ Slim index of active and archived priorities. Open a priority's folder for detai **Owner:** Bob + founder **Summary:** Turn Oz into a real operator control plane — a per-workspace, in-dashboard headless chatbot that is the primary command interface and the primary watcher/debugger for every run. **What:** Build the Oz UI per [ADR-0008](./decisions/0008-oz-control-plane-architecture.md) (Dashboard with Oz chat + drag-reorder priorities + ad-hoc run launcher; Workspaces with primary/writable/read-only roots; CLIs with Test; Personas with CLI/model + sub-agent hierarchy + visible/headless; Runs list+detail; Settings) plus the Oz oversight/debugger mechanism. Screen/flow brief + design prompt in `docs/oz-design-brief.md`. Root roles per ADR-0007 (revised 2026-05-27). -**Status:** Draft (stub). Founder decision on sequencing. Depends on the claude.ai/design output + ADR-0008. Authored 2026-05-27 per founder ask. See [`priorities/v0.4-oz-control-plane/README.md`](./priorities/v0.4-oz-control-plane/README.md). +**Status:** Active — **design spec landed 2026-05-27** at `docs/oz-control-plane-design/` (high-fidelity React prototype = source of truth for *what*; reference, not production). Next: ADR-0010 (pause/resume run primitive, `cocoder attach`, transcript streaming, persona-roster reconciliation incl. new "Doc", in-app update channels) → build plan → implement. Designer notes at [`priorities/v0.4-oz-control-plane/designer-notes.md`](./priorities/v0.4-oz-control-plane/designer-notes.md). Embedded Electron terminal harness is the spec's deferred "v2" → v0.6. Authored 2026-05-27 per founder ask. See [`priorities/v0.4-oz-control-plane/README.md`](./priorities/v0.4-oz-control-plane/README.md). + +### [v0.6-cocoder-ide](./priorities/v0.6-cocoder-ide/README.md) +**Owner:** Bob + founder +**Summary:** The CoCoder IDE proper — the design spec's deferred "v2": an embedded Electron terminal harness (in-app live orchestration sessions) + editor surfaces, extending the v0.4 control plane. +**What:** Replace external iTerm orchestration sessions with an in-app embedded terminal harness; keep the v0.4 Run Detail contract stable so transcript/evidence/stop/attach work for both external (v0.4) and embedded (v0.6) sessions. Builds on the `cocoder attach` CLI + pause/resume primitive landed in v0.4. +**Status:** Draft — reserved; after v0.4 (the control plane) ships. Slug/sequencing provisional (founder). Authored 2026-05-27 per founder ask. See [`priorities/v0.6-cocoder-ide/README.md`](./priorities/v0.6-cocoder-ide/README.md). diff --git a/cocoder/priorities/v0.4-oz-control-plane/README.md b/cocoder/priorities/v0.4-oz-control-plane/README.md index d927634..b52cbb1 100644 --- a/cocoder/priorities/v0.4-oz-control-plane/README.md +++ b/cocoder/priorities/v0.4-oz-control-plane/README.md @@ -1,6 +1,6 @@ # v0.4 — Oz Control Plane -**Status:** Draft (stub) +**Status:** Active — **design spec landed 2026-05-27** (`docs/oz-control-plane-design/`); ADR-0010 + build plan next. **Owner:** Bob + founder **Sequencing:** Founder decision. Depends on the claude.ai/design output for the Oz UI and on [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md). Builds on v0.3 work item #6 ("Oz as control plane") and the founder-flagged "improve Oz dashboard / Oz oversight + debugger" theme. @@ -12,7 +12,27 @@ Turn Oz into a real **operator control plane**: a per-workspace, in-dashboard he - [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) — Oz control-plane architecture (Oz persona model, command + watch, GUI⇄Oz parity, external orchestration sessions, persona exec model incl. visible/headless, the five surfaces with runs + priorities folded into the Dashboard). - [ADR-0007](../../decisions/0007-workspace-files-and-multiroot-description.md) (revised 2026-05-27) — root-role taxonomy primary / writable / read-only. -- Screen/flow brief + design prompt: [`docs/oz-design-brief.md`](../../../docs/oz-design-brief.md) (intent; refined by the claude.ai/design output). +- Screen/flow brief + design prompt: [`docs/oz-design-brief.md`](../../../docs/oz-design-brief.md) (intent; **now realized by the spec below**). + +## Design spec — landed 2026-05-27 + +The claude.ai/design output landed as a high-fidelity, working React prototype (the "Oz — Control Plane Prototype"). It is the **source of truth for *what* to build**; it is **reference, not production code** (production is a fresh build informed by it). + +- **Spec + prototype:** [`docs/oz-control-plane-design/`](../../../docs/oz-control-plane-design/) — brief (`README.md`), `app.jsx` (shell + Oz intent bot), `dashboard.jsx`/`screens.jsx`/`components.jsx`, `data.js` (informal data model), `dev-notes.js` (18 numbered component specs — the binding implementation spec), Fusion `design-system/` tokens (Josefin Sans + gold kanji marks). Open `Oz.html` to run it. +- **Designer implementation notes:** [`designer-notes.md`](./designer-notes.md) — simulated-vs-real wiring, what NOT to inherit, architecture invariants, a11y gaps, scope. +- **Confirms our ADRs:** five-section nav + Runs/Priorities as Dashboard panels (ADR-0008); `Root.role: primary|writable|readonly` (ADR-0007); Fusion tokens (ADR-0001). + +### New decisions this spec forces (→ ADR-0010) + +1. **Pause/resume decision primitive** (largest core touch) — a run reaches a decision point → **pauses** (Oscar blocks for a founder answer) → Oz surfaces a callout → founder answers → run **resumes**, routed through Oscar. Real run-lifecycle state in `packages/core`. +2. **`cocoder attach `** — new CLI; connects to the cmux/tmux session owning that run's iTerm. +3. **Per-run transcript streaming** (websocket/SSE) + **disk persistence** + replay-on-reconnect (`packages/oz-daemon`); real **`run.progress`** from Oscar's step counter. +4. **Oz as a tool-using agent** (intents → actions), replacing the prototype's regex `buildOzReply`. +5. **Persona roster reconciliation** — the spec's roster is Oz · Oscar · Bob · Talia · Quinn · **Doc** (new); **Ian and Phil are absent**. Reconcile against [ADR-0002](../../decisions/0002-talia-quinn-boundary.md) and the shipped persona set before building the Personas screen. +6. **In-app update channels** — founder-confirmed in scope (packaged app self-update). Auth/identity, billing, telemetry/crash reporting are **deferred**. +7. Build-target: a fresh **`packages/cocoder-ide`** (or extend `packages/oz-dashboard`) using dnd-kit (not native HTML5 DnD), Electron native folder picker + min-window clamp; **strip** the Tweaks panel + dev annotations (keep the registry as design docs). + +> The embedded **Electron terminal harness** is explicitly the brief's **"v2"** — tracked separately as [`v0.6-cocoder-ide`](../v0.6-cocoder-ide/README.md). v0.4 keeps orchestration sessions external (iTerm) per ADR-0008 decision 5; the Run Detail transcript is Oz's read-only window into them. ## Work items (provisional — refine after design output lands) @@ -25,6 +45,15 @@ Turn Oz into a real **operator control plane**: a per-workspace, in-dashboard he Note: the left nav is **five sections** — Dashboard, Workspaces, CLIs, Personas, Settings. Runs and Priorities are Dashboard panels, not top-level pages (corrects the earlier six-section draft). +## Priorities panel — display + persisted reorder (design) + +Today `cocoder/PRIORITIES.md` is the single source of truth: two sections (`## Active`, `## Draft`), each a markdown table parsed by `packages/oz-daemon/src/priorities.ts`. A priority has `slug`, `description`, `status`, `section`, `readmePath` (`packages/schemas/src/oz/priorities-http.ts`) — **no `order`/`rank` field**; order is implicitly the table row order. The `v0.x-` prefix in slugs is a roadmap-version label baked into the slug, not a separate field, and "Draft" is the **section** the row sits under, not a glitch. + +- **Human-readable display (shipped in the dashboard):** the Priorities panel shows `description` as the title and demotes the `slug` (incl. its `v0.x` version) to a small tag — slugs stay stable IDs; the readable name leads. +- **Section vs status:** keep both but label them distinctly — `section` (Active/Draft = which table / backlog vs promoted) is organizational; `status` (Active/Draft/Paused/Complete/Cancelled) is lifecycle. Promoting a priority = moving its row to `## Active`. +- **Persisted reorder (no new ID scheme):** drag-reorder reorders rows **within a section**; order = row order in `PRIORITIES.md`. The daemon gains `PATCH /workspaces/:id/priorities` that **rewrites the markdown row order** in place. Keep the version prefix as a label only — it is *not* the ordering mechanism, so reordering never renumbers versions. One git-tracked source of truth, no DB. +- **Ownership:** `PRIORITIES.md` is founder/Oz-owned — Bob is boundary-blocked from it (`cocoder/profiles/cocoder-oscar.profile.json` `bob.excludedWriteBoundary`), so the **daemon** performs the rewrite on a founder/Oz drag action, not an orchestration lane. + ## Open questions - The CLI→model→sub-agent configuration hierarchy UI (nesting). diff --git a/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md b/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md new file mode 100644 index 0000000..5452887 --- /dev/null +++ b/cocoder/priorities/v0.4-oz-control-plane/designer-notes.md @@ -0,0 +1,53 @@ +# CoCoder IDE — Designer Notes + +Implementation guidance from the designer, captured 2026-05-27 (before the full spec/prototype is placed in `docs/cocoder-ide-design/`). These notes are authoritative input for **ADR-0010** and the build plan. The spec ships as a React prototype (`app.jsx` + design tokens + a dev/annotation registry); the prototype is **reference**, not production code. + +## What's simulated in the prototype → real wiring it implies + +| Prototype (fake) | Real wiring to build | +|---|---| +| Oz chat is a regex bot (`app.jsx::buildOzReply`); seed prompts (status, launch next, promote #N, full/partial) | Oz must be a **tool-using agent**: recognize **intents → actions**, call the orchestrator (Claude/chosen CLI), stream replies back. The seed prompts are the intent set, not a protocol. | +| Run transcripts are static arrays | **Stream per run** (websocket or SSE), indexed by `run.id`, with **replay-on-reconnect** and **disk persistence** (close laptop → return to same transcript). | +| `cocoder attach ` (Attach tab just copies a string) | Decide what the **CLI actually does** — most likely connect to the cmux/tmux session that owns that run's iTerm. **New CLI command.** | +| "Decision needed" flow suspends nothing | Wire a real **pause/resume primitive**: run hits a decision point → **pauses** (Oscar blocks waiting for human input) → Oz surfaces the callout → founder answers → run **resumes** with the answer routed through Oscar. | +| CLI Test = 1.1s timeout | Real Test **invokes the CLI** with a no-op command and parses the result. | +| Re-check dependency (same fake shape) | Wire to `which iterm2` / `which cmux` (or platform equivalent). | +| Root folder picker = text input w/ folder icon | Use the platform's **real picker** (Electron `dialog.showOpenDialog`). | + +## Do NOT inherit from the prototype + +- **Drag-and-drop priorities** uses native HTML5 drag → use **dnd-kit** or **react-dnd** (keyboard, touch, a11y, animation included). +- **Tweaks panel** is design-review-only → **strip for production**. The first-run state IS real (drive off "no workspaces configured yet"); theme/density move into **Settings → Appearance**. +- **Dev annotations** are spec, not feature → strip them, but **keep the registry as design docs**. +- **Fixed-width artboard** isn't responsive below ~1280px. Desktop-class is fine per brief, but **Electron should clamp min window size**. + +## Architecture (real, easy to miss) + +- **Oz is workspace-scoped and plural.** Three open tabs = **three live Oz processes**, each with its own conversation, watcher list, retention window. **Don't share state.** **Suspend watchers for backgrounded (non-active) tabs** to save tokens; wake on tab focus. +- **Persona color identity is stable across the app:** Bob = sage, Quinn = coral, Oz = gold, system = muted. They double as recognition cues — keep consistent (esp. transcript view). +- **"Craft new persona" priority** carries the full spec in `priority.spec`. When it runs, Oscar reads the spec and builds the persona (prompt, sub-agents, tests). The persona appears in the Personas list **only after Quinn green-lights** the build. +- **`run.progress`** is currently hardcoded. Real progress is probably **Oscar's step counter** (step 2 of 5) projected to a 0–1 float. **Decide where the truth lives.** +- **Light theme** is wired but lightly tested; some colors are inline hex/rgba. Tokens flip, but verify a full pass. + +## Accessibility (needs work — not there yet) + +No keyboard nav on workspace tabs · drag-reorder has no keyboard path · modal focus-trap not bulletproof · ARIA labels sparse. + +## Out of scope for the control plane (FOUNDER-CONFIRMED 2026-05-27) + +Designer flagged auth, identity, billing, telemetry, crash reporting, update channels as mute-in-brief. Founder decision: + +- **In scope for v0.6:** **Update channels** — the packaged Electron app gets an in-app update mechanism (vs. today's git-clone + pnpm). ADR-0010 must cover update channel/signing/distribution. +- **Deferred (out of scope for v0.6):** Auth & identity, billing, telemetry & crash reporting. (CoCoder stays a local single-operator tool — localhost Oz daemon + per-install token. Revisit telemetry only as an explicit opt-in design if ever needed.) + +## Implications for the orchestration core / oz-daemon (flag, don't assume) + +Several "real wiring" items reach beyond the IDE shell into existing packages — to be validated against the code during spec review, not assumed: + +- **`cocoder attach `** → new `packages/core` CLI command + session-ownership lookup. +- **Pause/resume decision primitive** → run lifecycle state in `packages/core` (ledger/launch) where a run can block awaiting a founder decision and resume with the answer routed through Oscar. This is the most significant core touch. +- **Real `run.progress`** → Oscar emits a step counter the daemon projects to 0–1. +- **Per-run transcript streaming (SSE/WS) + persistence** → `packages/oz-daemon`. +- **Oz-as-tool-using-agent (intents → actions)** → oz-daemon/dashboard + orchestration. + +> ADR-0008 discipline: keep core changes minimal and **flag** required ones rather than assume. Where the IDE genuinely needs a core/daemon primitive (pause/resume especially), that becomes an explicit companion work item in the build plan. diff --git a/cocoder/priorities/v0.6-cocoder-ide/README.md b/cocoder/priorities/v0.6-cocoder-ide/README.md new file mode 100644 index 0000000..e3dfcdb --- /dev/null +++ b/cocoder/priorities/v0.6-cocoder-ide/README.md @@ -0,0 +1,20 @@ +# v0.6 — CoCoder IDE (embedded Electron terminal harness, "v2") + +**Status:** Draft — reserved; depends on `v0.4-oz-control-plane` shipping first. **Owner:** Bob + founder. +**Relates to:** [`v0.4-oz-control-plane`](../v0.4-oz-control-plane/README.md) (the control-plane app this extends), [ADR-0008](../../decisions/0008-oz-control-plane-architecture.md) (deferred the embedded Electron terminal harness). + +## Why + +The Oz control-plane design spec (realized under **v0.4**) explicitly defers one thing to **"v2"**: replacing the **external iTerm orchestration sessions** with an **embedded Electron terminal harness** inside the app — so the founder watches *and runs* sessions in-app rather than Oz being a read-only window onto iTerm. + +This priority is that "v2": the CoCoder IDE proper — the Electron app shell hosting the v0.4 control-plane surfaces **plus** embedded live terminals and (eventually) editor surfaces. + +## Scope (provisional — refine after v0.4 ships) + +- Embedded terminal harness that **drives and observes** orchestration sessions in-app (today external in iTerm — ADR-0008 decision 5). Keep the v0.4 **Run Detail contract stable** so the transcript/evidence/stop/attach surface works whether the session is external (v0.4) or embedded (v0.6). +- Electron app shell hosting the five v0.4 surfaces; editor surfaces as the IDE grows. +- Reuses the v0.4 build (`packages/cocoder-ide` or whatever v0.4 establishes) — this is a phase *within* the same app, not a separate product. + +## Not yet + +This stays a reserved stub until v0.4 (the control plane) is built and shipped. The `cocoder attach ` CLI + pause/resume primitive landed in v0.4 are prerequisites the embedded harness builds on. Slug/sequencing provisional (founder). diff --git a/docs/oz-control-plane-design/Oz.html b/docs/oz-control-plane-design/Oz.html new file mode 100644 index 0000000..ab0f2e7 --- /dev/null +++ b/docs/oz-control-plane-design/Oz.html @@ -0,0 +1,33 @@ + + + + +Oz — CoCoder Control Plane + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/docs/oz-control-plane-design/README.md b/docs/oz-control-plane-design/README.md new file mode 100644 index 0000000..fea4da4 --- /dev/null +++ b/docs/oz-control-plane-design/README.md @@ -0,0 +1,130 @@ +# Oz — Control Plane Prototype + +The interactive prototype for **Oz**, the CoCoder control plane. This is a high-fidelity working mock — not production code — meant to anchor the dev team's implementation. Open `Oz.html` in a browser to run it. + +--- + +## What this is + +- A single-page React prototype (no build step) demonstrating every screen, state, and interaction described in the design brief. +- Stable visual reference: type, color, spacing, components, and copy are committed. +- Behavioral reference: clicking through the prototype demonstrates the intended flows. +- A spec embedded in the UI itself — **turn on dev annotations** (see below). + +This is the source of truth for *what* to build. The dev team owns *how*. + +--- + +## How to read it + +Open `Oz.html` in any modern browser. Key things to try: + +1. **Talk to Oz.** Type in the terminal. Try `status`, `launch the next priority`, `promote #4`, `full`, `partial`. Click the decision callout when run-1 is blocked. +2. **Click a running priority.** The Run Detail drawer slides out next to it with the gold-edge handoff cue. +3. **Open Run history.** Top-right of the priorities panel. +4. **Switch workspaces.** Use the tabs at the top of the dashboard. Each tab is its own Oz instance. +5. **Inspect every persona.** Personas screen. Note the linked CLI + Model dropdowns and sub-agent hierarchy. +6. **Craft a new persona.** Header button on Personas — fills a priority for the team to build. +7. **First-run setup.** Open Tweaks (bottom-left toggle) → Workspace state → First run. + +### Dev annotations (the embedded spec) + +Open the **Tweaks panel** (toggle in the toolbar) → flip **Show dev annotations** on. Eighteen numbered gold pins appear on key UI surfaces. Click any pin for a short component spec — what it is, how it behaves, and the data it touches. A floating **"Dev notes"** button (bottom-right) opens the full index. + +Treat the dev-notes list as the binding implementation spec. + +--- + +## File layout + +``` +Oz.html Entry point. Loads everything below. +oz.css App styles. Extends the design system. +design-system/ CoBuilder Fusion design system tokens (drop-in). + colors_and_type.css 93 CSS variables, typography classes + fonts/ Josefin Sans (display) +data.js Seed data — workspaces, runs, priorities, + personas, CLIs, dependencies, settings. + NOT the schema spec; just demo content. +dev-notes.js Component specs for the dev team. + 18 numbered entries that overlay the UI. +components.jsx Shared primitives — Sidebar, TopBar, + WorkspaceTabs, Modal, Button, StatusChip, + DevNote, DevNotesPanel, Card. +dashboard.jsx Dashboard composition — PrioritiesPanel + (incl. AdhocPriorityRow + PriorityRow), + OzChatPanel, RunDetail drawer, + RunHistoryModal, first-run setup card. +screens.jsx Workspaces, CLIs, Personas, Settings, + Dependencies panel, NewWorkspaceModal, + CraftPersonaModal. +app.jsx Root component. State, routing, tweaks + wiring, Oz bot reply simulator. +tweaks-panel.jsx Tweaks panel host (theme, density, + workspace state, dev-mode toggle). +``` + +No bundler, no npm install. React + Babel are loaded from unpkg. + +--- + +## Hard rules baked in + +These come from the design brief and should survive the rewrite: + +- **Five top-level nav items only:** Dashboard · Workspaces · CLIs · Personas · Settings. Runs and Priorities are panels inside Dashboard, never standalone pages. +- **One Oz per workspace.** Multiple workspaces loaded = multiple independent headless Oz instances. Persist Oz state keyed on `workspace.id`. (See dev note 17.) +- **Oz is the command center.** Anything the founder can do via a button, they must be able to ask Oz to do. Keep parity. +- **Never expose JSON.** Forms, toggles, selectors. Always. +- **Workspace context is pervasive.** Switching tabs swaps Oz, priorities, runs, and conversation atomically. +- **External orchestration sessions.** Today the runs execute in iTerm. The Run Detail "Transcript" tab is Oz's read-only window into the externally-running session. v2 will embed an Electron terminal. +- **Persona roster:** Oz · Oscar · Bob · Talia · Quinn · Doc. New personas are built by the team via the Craft modal — not configured directly. +- **System dependencies separate from CLI auth.** Settings → System dependencies probes iTerm2 / cmux. CLIs screen probes Claude Code / Codex / Cursor-agent / Gemini / Grok. + +--- + +## What this prototype does NOT decide + +- The wire protocol between Oz and the orchestrator +- How the externally-running session reports back (probably a websocket per run + persisted transcript) +- The actual persona prompt scaffolding (Oscar builds these at runtime) +- Auth, identity, billing — out of scope for the control plane +- Schema / persistence model + +The dev team owns these. + +--- + +## Data model (informal, from the prototype state shape) + +``` +Workspace { + id, name, description, icon, roots: [Root], created +} +Root { + id, name, path, role: "primary" | "writable" | "readonly" +} +Priority { + id, name, summary, status, labels: [string], + runId?: string, // points to the live or last run executing this priority + spec?: PersonaSpec // present when priority is a "build new persona" +} +Run { + id, title, status: "running"|"blocked"|"complete"|"failed"|"stopped", + priorityId?: string, // null = ad-hoc + personas: [string], cli, startedAt, progress, lastEvent, attachCmd, + transcript: [TranscriptLine], evidence: [EvidenceItem] +} +Persona { + id, name, role, description, cli, model, runMode: "visible"|"headless", + subAgents: [{ id, name, cli, model }], icon, headless?: boolean +} +Cli { + id, name, vendor, version, status: "ok"|"auth-failed"|"not-installed", + lastTested, errorDetail?, models: [string] +} +Dependency { + id, name, vendor, purpose, status: "ok"|"not-installed", + version, lastChecked, installCmd, icon, note? +} +``` diff --git a/docs/oz-control-plane-design/app.jsx b/docs/oz-control-plane-design/app.jsx new file mode 100644 index 0000000..f7c39b5 --- /dev/null +++ b/docs/oz-control-plane-design/app.jsx @@ -0,0 +1,524 @@ +// Oz — App root. State, routing, Oz chat simulation, Tweaks wiring. + +const { useState, useEffect, useRef, useMemo, useCallback } = React; +const A = window; + +// ───────── Default tweaks ───────── +const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ + "theme": "dark", + "density": "comfortable", + "workspaceState": "configured", + "devMode": false, + "showInlineFlows": false +}/*EDITMODE-END*/; + +// ───────── Tiny Oz bot ───────── +function buildOzReply(userText, ctx) { + const t = userText.toLowerCase(); + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const runs = ctx.runs; + const activeCount = runs.filter(r => r.status === "running" || r.status === "blocked").length; + + if (/^status|how.*we doing|what.*going on/.test(t)) { + return { + role: "oz", + body: `**Workspace status:** ${activeCount} active run${activeCount === 1 ? '' : 's'} · ${ctx.priorities.length} priorities queued. Run-1 is blocked on your call about replay scope. Run-2 (ad-hoc audit) is also waiting on a Grok question.`, + time, + }; + } + if (/launch.*next|launch.*top|launch the next/.test(t)) { + const next = ctx.priorities[0]; + if (!next) return { role: "oz", body: "Nothing on the priority list. Want to draft one?", time }; + return { + role: "oz", + body: `Launching **${next.name}**. Spinning up Planner → Builder → Reviewer on claude-code. Visible mode (iTerm).`, + time, + attachments: [{ kind: "queued-run", priorityId: next.id, title: next.name }], + }; + } + if (/ad-?hoc|review|refactor|audit|investigate/.test(t)) { + return { + role: "oz", + body: `Drafting an ad-hoc run for that. Recommended team: Reviewer + Researcher. Approve?`, + time, + }; + } + if (/reorder|promote|move .* to (top|first)/.test(t)) { + const m = t.match(/#?(\d+)/); + return { + role: "oz", + body: m ? `Moved priority #${m[1]} to the top. Run order updated.` : "Got it — which priority would you like to promote? Send `promote #4` or drag it in the list.", + time, + }; + } + if (/decision|full plan|partial/.test(t) || t === "full" || t === "partial") { + const choice = /partial/.test(t) ? "partial" : "full"; + return { + role: "oz", + body: `Decision recorded: replay **${choice} plan** on sub-agent handoffs. Resuming run-1 — Reviewer is finishing its audit now.`, + time, + }; + } + if (/new persona|craft.*persona|build.*persona/.test(t)) { + return { + role: "oz", + body: "Filed it as a priority. Builder + Architect will scaffold prompts, sub-agents, and tests. ETA depends on the role you sketch.", + time, + }; + } + // default + return { + role: "oz", + body: "Heard. I'm thinking — give me a moment.", + time, + }; +} + +// ───────── App ───────── +const App = () => { + const [tweaks, setTweaks] = useState({ ...TWEAK_DEFAULTS }); + const [route, setRoute] = useState("dashboard"); + const [theme, setTheme] = useState(tweaks.theme); + + // Mutable data state (cloned from window.OZ_DATA) + const seed = window.OZ_DATA; + const [activeWsId, setActiveWsId] = useState(seed.activeWorkspaceId); + const [loadedWsIds, setLoadedWsIds] = useState([seed.activeWorkspaceId]); + const [workspaces, setWorkspaces] = useState(seed.workspaces); + const [prioritiesMap, setPrioritiesMap] = useState(seed.priorities); + const [runsMap, setRunsMap] = useState(seed.runs); + const [ozChatMap, setOzChatMap] = useState(seed.ozChat); + const [clis, setClis] = useState(seed.clis); + const [personas, setPersonas] = useState(seed.personas); + const [settings, setSettings] = useState(seed.settings); + const [dependencies, setDependencies] = useState(seed.dependencies || []); + const [selectedRunId, setSelectedRunId] = useState(null); + const [ozTyping, setOzTyping] = useState(false); + + // Modals + const [newWsOpen, setNewWsOpen] = useState(false); + const [craftPersonaOpen, setCraftPersonaOpen] = useState(false); + const [devNotesOpen, setDevNotesOpen] = useState(false); + const [runHistoryOpen, setRunHistoryOpen] = useState(false); + + // Theme sync (tweaks + manual toggle in topbar) + useEffect(() => { + document.documentElement.dataset.theme = theme; + }, [theme]); + useEffect(() => { setTheme(tweaks.theme); }, [tweaks.theme]); + + // Density via CSS var + useEffect(() => { + document.documentElement.style.setProperty('--oz-density-scale', tweaks.density === 'compact' ? '0.92' : '1'); + }, [tweaks.density]); + + // Active workspace ctx + const activeWs = workspaces.find(w => w.id === activeWsId); + const priorities = prioritiesMap[activeWsId] || []; + const runs = runsMap[activeWsId] || []; + const ozMessages = ozChatMap[activeWsId] || []; + + // First-run / empty workspace state + const emptyState = tweaks.workspaceState === "first-run" ? "first-run" : null; + + // ───────── Handlers ───────── + + const updateWs = (next) => { + setWorkspaces(ws => ws.map(w => w.id === next.id ? next : w)); + }; + const createWs = () => { setNewWsOpen(true); }; + + const handleCreateWs = ({ name, description, root }) => { + const id = "ws-" + Math.random().toString(36).slice(2, 7); + const newWs = { + id, name, description, + icon: "ph-thin ph-cube", + roots: [{ id: "r-" + Math.random().toString(36).slice(2, 6), name: root.name, path: root.path, role: "primary" }], + created: new Date().toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' }), + }; + setWorkspaces(ws => [...ws, newWs]); + setPrioritiesMap(m => ({ ...m, [id]: [] })); + setRunsMap(m => ({ ...m, [id]: [] })); + setOzChatMap(m => ({ ...m, [id]: [{ + id: "init", + role: "oz", + body: `Workspace **${name}** is up. Primary root is \`${root.path}\`. Tell me what we're building first.`, + time: new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }) + }] })); + setLoadedWsIds(arr => arr.includes(id) ? arr : [...arr, id]); + setActiveWsId(id); + setSelectedRunId(null); + setRoute("dashboard"); + }; + const deleteWs = (id) => { + setWorkspaces(ws => ws.filter(w => w.id !== id)); + if (activeWsId === id && workspaces.length > 1) { + setActiveWsId(workspaces.find(w => w.id !== id).id); + } + }; + + const loadWorkspaceTab = (id) => { + if (!loadedWsIds.includes(id)) setLoadedWsIds(arr => [...arr, id]); + setActiveWsId(id); + setSelectedRunId(null); + }; + const closeWorkspaceTab = (id) => { + if (loadedWsIds.length <= 1) return; + const nextLoaded = loadedWsIds.filter(x => x !== id); + setLoadedWsIds(nextLoaded); + if (activeWsId === id) { + setActiveWsId(nextLoaded[Math.max(0, nextLoaded.length - 1)]); + setSelectedRunId(null); + } + }; + const selectWorkspaceTab = (id) => { setActiveWsId(id); setSelectedRunId(null); }; + + const reorderPriorities = (from, to) => { + setPrioritiesMap(m => { + const arr = [...(m[activeWsId] || [])]; + const [moved] = arr.splice(from, 1); + arr.splice(to, 0, moved); + return { ...m, [activeWsId]: arr }; + }); + }; + + const onLaunchPriority = (priority) => { + // Launch a new run from this priority + const runId = "run-" + Math.random().toString(36).slice(2, 6); + const newRun = { + id: runId, title: priority.name, priorityId: priority.id, + status: "running", + personas: ["Planner", "Builder", "Reviewer"], + cli: "claude-code", + startedAt: "just now", progress: 0.05, + lastEvent: "Run started. Planner is decomposing the priority.", + attachCmd: `cocoder attach ${runId}`, + transcript: [ + { role: "system", body: `Run started against ${activeWs.roots.find(r => r.role === "primary")?.name || "primary root"}.` }, + { role: "Planner", body: "Reading the priority spec. Decomposing into steps." }, + ], + evidence: [], + }; + setRunsMap(m => ({ ...m, [activeWsId]: [newRun, ...(m[activeWsId] || [])] })); + setPrioritiesMap(m => { + const arr = (m[activeWsId] || []).map(p => p.id === priority.id ? { ...p, status: "in-progress", runId } : p); + return { ...m, [activeWsId]: arr }; + }); + // Oz announces the launch + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "u-" + runId, role: "user", body: `Launch "${priority.name}"`, time }, + { id: "o-" + runId, role: "oz", body: `Launching **${priority.name}**. Planner is up. I'll watch.`, time, attachments: [{ kind: "run-card", runId }] }, + ] + })); + }; + + const onAdhoc = () => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ad-" + Date.now(), role: "user", body: "Run something ad-hoc — describe the task here.", time }, + { id: "or-" + Date.now(), role: "oz", body: "Tell me what to do. Common ad-hocs: **code review** of a PR, **refactor** a module, **research** prior-art, **audit** a surface. What's the task?", time }, + ] + })); + }; + + const onAddPriority = () => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ap-" + Date.now(), role: "oz", body: "What's the priority? Sketch it in a sentence — I'll write it up and add it to the list.", time }, + ] + })); + }; + + const onSend = (text) => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const userMsg = { id: "u-" + Date.now(), role: "user", body: text, time }; + setOzChatMap(m => ({ ...m, [activeWsId]: [...(m[activeWsId] || []), userMsg] })); + setOzTyping(true); + setTimeout(() => { + const reply = buildOzReply(text, { priorities, runs }); + reply.id = "o-" + Date.now(); + setOzChatMap(m => ({ ...m, [activeWsId]: [...(m[activeWsId] || []), reply] })); + setOzTyping(false); + }, 700 + Math.random() * 500); + }; + + const onDecision = (choice) => { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "ud-" + Date.now(), role: "user", body: choice === "full" ? "Replay the full plan." : "Just replay the last 2 messages.", time }, + { id: "od-" + Date.now(), role: "oz", body: `Decision recorded — **${choice === "full" ? "full plan" : "partial"} replay** on fallback. Resuming run-1.`, time }, + ] + })); + // Unblock run-1 + setRunsMap(m => { + const arr = (m[activeWsId] || []).map(r => r.id === "run-1" ? { ...r, status: "running", lastEvent: "Decision received. Builder continuing." } : r); + return { ...m, [activeWsId]: arr }; + }); + }; + + const onRunAction = (action, runId) => { + if (action === "stop") { + setRunsMap(m => ({ ...m, [activeWsId]: (m[activeWsId] || []).map(r => r.id === runId ? { ...r, status: "stopped", lastEvent: "Stopped by founder." } : r) })); + } else if (action === "ask-oz") { + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + const run = runs.find(r => r.id === runId); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "aoz-" + Date.now(), role: "user", body: `What's going on with ${runId}?`, time }, + { id: "aor-" + Date.now(), role: "oz", body: `${runId} — ${run?.title}. Last event: ${run?.lastEvent}`, time, attachments: [{ kind: "run-card", runId }] }, + ] + })); + } else if (action === "retry") { + setRunsMap(m => ({ ...m, [activeWsId]: (m[activeWsId] || []).map(r => r.id === runId ? { ...r, status: "running", startedAt: "just now", lastEvent: "Retrying." } : r) })); + } + }; + + const onTestCli = (id) => { + setClis(cs => cs.map(c => { + if (c.id !== id) return c; + if (c.status === "auth-failed") return { ...c, status: "auth-failed", lastTested: "just now" }; + if (c.status === "not-installed") return { ...c, status: "not-installed", lastTested: "just now" }; + return { ...c, status: "ok", lastTested: "just now" }; + })); + }; + + const onChangePersona = (id, next) => { + setPersonas(ps => ps.map(p => p.id === id ? next : p)); + }; + const onAddSub = (pid) => { + const subId = "sub-" + Math.random().toString(36).slice(2, 6); + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: [...p.subAgents, { id: subId, name: "New sub-agent", cli: "claude-code", model: "Default" }] } : p)); + }; + const onRemoveSub = (pid, sid) => { + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: p.subAgents.filter(s => s.id !== sid) } : p)); + }; + const onUpdateSub = (pid, sid, next) => { + setPersonas(ps => ps.map(p => p.id === pid ? { ...p, subAgents: p.subAgents.map(s => s.id === sid ? next : s) } : p)); + }; + const onNewPersonaAsPriority = () => { + setCraftPersonaOpen(true); + }; + + const handleSubmitNewPersona = ({ name, summary, spec, placeAtTop }) => { + const newP = { + id: "np-" + Date.now(), + name, summary, + status: "ready", + labels: ["persona-build"], + spec, + }; + setPrioritiesMap(m => { + const arr = m[activeWsId] || []; + return { ...m, [activeWsId]: placeAtTop ? [newP, ...arr] : [...arr, newP] }; + }); + // Oz acknowledges + const time = new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + setOzChatMap(m => ({ + ...m, [activeWsId]: [...(m[activeWsId] || []), + { id: "cp-" + Date.now(), role: "oz", + body: `Filed **${name}** as a ${placeAtTop ? 'top' : 'queued'} priority. Oscar will scaffold the persona—prompt, sub-agents, tests included.`, + time, + }, + ] + })); + setRoute("dashboard"); + }; + + const onRecheckDep = (id) => { + setDependencies(deps => deps.map(d => d.id === id ? { ...d, lastChecked: "just now" } : d)); + }; + + // ───────── Tweaks panel ───────── + const setTweak = (key, val) => { + if (typeof key === "object") { + setTweaks(t => ({ ...t, ...key })); + window.parent.postMessage({ type: '__edit_mode_set_keys', edits: key }, '*'); + } else { + setTweaks(t => ({ ...t, [key]: val })); + window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*'); + } + }; + + // Tweaks host protocol + const [tweaksOpen, setTweaksOpen] = useState(false); + useEffect(() => { + const handler = (e) => { + if (!e?.data) return; + if (e.data.type === "__activate_edit_mode") setTweaksOpen(true); + else if (e.data.type === "__deactivate_edit_mode") setTweaksOpen(false); + }; + window.addEventListener("message", handler); + window.parent.postMessage({ type: '__edit_mode_available' }, '*'); + return () => window.removeEventListener("message", handler); + }, []); + + // ───────── Render ───────── + const screenTitle = NAV_ITEMS.find(n => n.id === route)?.label || "Oz"; + + const devCtxValue = useMemo(() => ({ on: !!tweaks.devMode }), [tweaks.devMode]); + + return ( + +
+ { setRoute(r); setSelectedRunId(null); }} + runs={runs} priorities={priorities} user={seed.user} /> +
+ +
+ {route === "dashboard" && ( + + )} + {route === "workspaces" && ( + setRoute("dashboard")} /> + )} + {route === "clis" && ( + alert("Add CLI flow (mock)")} /> + )} + {route === "personas" && ( + + )} + {route === "settings" && ( + + )} +
+
+ + {/* Modals */} + setNewWsOpen(false)} + onCreate={handleCreateWs} + /> + setCraftPersonaOpen(false)} + clis={clis} + onSubmit={handleSubmitNewPersona} + /> + + {/* Dev notes panel + floating toggle */} + setDevNotesOpen(false)} + notes={window.DEV_NOTES || []} + /> + {tweaks.devMode && !devNotesOpen && ( + + )} + + {tweaksOpen && ( + setTweaksOpen(false)}> + + setTweak("theme", v)} + /> + + + setTweak("workspaceState", v)} + /> + + + setTweak("density", v)} + /> + + + { setTweak("devMode", v); if (v) setDevNotesOpen(true); }} + /> + {tweaks.devMode && ( + setDevNotesOpen(o => !o)}> + {devNotesOpen ? "Hide notes panel" : "Show notes panel"} + + )} + + + setRoute("dashboard")}>Go to Dashboard + setSelectedRunId("run-1")}>Open run-1 (active) + setSelectedRunId("run-4")}>Open run-4 (failed) + { setRoute("dashboard"); setActiveWsId("ws-vault"); }}>Switch to quiet workspace + + + )} +
+
+ ); +}; + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/docs/oz-control-plane-design/components.jsx b/docs/oz-control-plane-design/components.jsx new file mode 100644 index 0000000..04be575 --- /dev/null +++ b/docs/oz-control-plane-design/components.jsx @@ -0,0 +1,584 @@ +// Shared primitives + app shell (Sidebar, TopBar, WorkspacePicker) + +const { useState, useEffect, useRef, useMemo, useCallback } = React; + +// ───────── Icon wrapper (Phosphor thin) ───────── +const Icon = ({ name, size, style }) => ( + +); + +// ───────── Status chips ───────── +const STATUS_LABEL = { + running: "Running", + blocked: "Needs decision", + complete: "Complete", + failed: "Failed", + stopped: "Stopped", + queued: "Queued", + ready: "Ready", + "in-progress": "In progress", + ok: "Ready", + "auth-failed": "Auth failed", + "not-installed": "Not installed", +}; +const STATUS_ICON = { + running: null, // uses pulsing dot + blocked: "warning-circle", + complete: "check-circle", + failed: "x-circle", + stopped: "stop-circle", + queued: "circle-dashed", + ready: "circle", + "in-progress": null, + ok: "check-circle", + "auth-failed": "warning-circle", + "not-installed": "minus-circle", +}; +const STATUS_VARIANT = { + running: "running", "in-progress": "running", + blocked: "blocked", + complete: "complete", ok: "complete", + failed: "failed", "auth-failed": "failed", + stopped: "stopped", "not-installed": "stopped", + queued: "queued", ready: "queued", +}; + +const StatusChip = ({ status, label }) => { + const variant = STATUS_VARIANT[status] || "queued"; + const iconName = STATUS_ICON[status]; + return ( + + {variant === "running" ? : iconName && } + {label || STATUS_LABEL[status] || status} + + ); +}; + +// ───────── Button ───────── +const Button = ({ variant = "secondary", size, icon, children, ...rest }) => ( + +); + +// ───────── Sidebar ───────── +const NAV_ITEMS = [ + { id: "dashboard", label: "Dashboard", icon: "squares-four" }, + { id: "workspaces", label: "Workspaces", icon: "folders" }, + { id: "clis", label: "CLIs", icon: "terminal-window" }, + { id: "personas", label: "Personas", icon: "users-three" }, + { id: "settings", label: "Settings", icon: "gear-six" }, +]; + +const Sidebar = ({ route, setRoute, runs, priorities, user }) => { + const activeRuns = runs.filter(r => r.status === "running" || r.status === "blocked").length; + return ( + + ); +}; + +// ───────── Workspace tabs (top bar — loaded workspaces, like browser tabs) ───────── +const WorkspaceTab = ({ ws, isActive, runs, onSelect, onClose, canClose }) => { + const activeRunCount = (runs || []).filter(r => r.status === "running" || r.status === "blocked").length; + return ( +
onSelect(ws.id)} + className="oz-ws-tab" + data-active={isActive ? "true" : "false"} + > + + {ws.name} + {activeRunCount > 0 && ( + + )} + {canClose && ( + + )} +
+ ); +}; + +const WorkspaceTabs = ({ workspaces, loadedIds, activeId, runsMap, onSelect, onClose, onLoad, onCreate }) => { + const [adderOpen, setAdderOpen] = useState(false); + const adderRef = useRef(null); + + useEffect(() => { + if (!adderOpen) return; + const handler = (e) => { if (adderRef.current && !adderRef.current.contains(e.target)) setAdderOpen(false); }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [adderOpen]); + + const loaded = loadedIds.map(id => workspaces.find(w => w.id === id)).filter(Boolean); + const unloaded = workspaces.filter(w => !loadedIds.includes(w.id)); + + return ( +
+ +
+ {loaded.map(w => ( + 1} + /> + ))} +
+ +
+ + + {adderOpen && ( +
+
+ Load workspace +
+ {unloaded.length === 0 ? ( +
+ All workspaces already loaded. +
+ ) : unloaded.map(w => ( +
{ onLoad(w.id); setAdderOpen(false); }} + style={{ + display: 'flex', alignItems: 'center', gap: 10, + padding: '9px 10px', borderRadius: 'var(--cb-radius-md)', + cursor: 'pointer', + }} + onMouseEnter={e => e.currentTarget.style.background = 'var(--cb-hover)'} + onMouseLeave={e => e.currentTarget.style.background = 'transparent'} + > +
+
+
{w.name}
+
+ {w.description || '—'} +
+
+ + {w.roots.length} root{w.roots.length === 1 ? '' : 's'} + +
+ ))} +
+
{ onCreate(); setAdderOpen(false); }} + style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '9px 10px', cursor: 'pointer', color: 'var(--cb-accent)', fontSize: 12, borderRadius: 'var(--cb-radius-md)' }} + onMouseEnter={e => e.currentTarget.style.background = 'var(--cb-hover)'} + onMouseLeave={e => e.currentTarget.style.background = 'transparent'} + > + + New workspace… + ⌘N +
+
+ )} +
+
+ ); +}; + +// ───────── Top bar ───────── +const TopBar = ({ title, workspaces, activeId, loadedIds, runsMap, onSelectWs, onCloseWs, onLoadWs, onCreateWs, theme, setTheme, route }) => ( +
+ {route === "dashboard" ? ( + + ) : ( +
{title}
+ )} + {route !== "dashboard" &&
} + +
+ + Search runs, priorities… + ⌘K +
+ + + +
+); + +// ───────── Reusable: section header inside content ───────── +const ScreenHeader = ({ title, subtitle, actions }) => ( +
+
+

+ {title} +

+ {subtitle && ( +
+ {subtitle} +
+ )} +
+ {actions &&
{actions}
} +
+); + +// ───────── Card ───────── +const Card = ({ children, style, onClick, active }) => ( +
+ {children} +
+); + +// ───────── Modal ───────── +const Modal = ({ open, onClose, title, subtitle, icon, footer, children, width = 640 }) => { + useEffect(() => { + if (!open) return; + const onKey = (e) => { if (e.key === "Escape") onClose(); }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, [open, onClose]); + + if (!open) return null; + return ( +
+
e.stopPropagation()} + style={{ + width: width, maxWidth: '100%', maxHeight: 'calc(100vh - 48px)', + background: 'var(--cb-bg-soft)', + border: '1px solid var(--cb-border-strong)', + borderRadius: 'var(--cb-radius-xl)', + boxShadow: '0 24px 60px rgba(0,0,0,0.55), inset 0 1px 0 0 var(--cb-glass-highlight)', + display: 'flex', flexDirection: 'column', + overflow: 'hidden', + position: 'relative', + animation: 'ozSlideIn 240ms ease-out', + }} + > +
+
+ +
+ {icon && ( +
+ +
+ )} +
+

{title}

+ {subtitle && ( +
+ {subtitle} +
+ )} +
+ +
+ +
+ {children} +
+ + {footer && ( +
+ {footer} +
+ )} +
+
+ ); +}; + +// ───────── Export to window ───────── +// ───────── Dev Annotations ───────── +const DevModeContext = React.createContext({ on: false, register: () => {}, unregister: () => {} }); + +const useDevMode = () => React.useContext(DevModeContext); + +const DevNote = ({ n, anchor = "top-left" }) => { + const { on } = useDevMode(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + const meta = (window.DEV_NOTES || []).find(x => x.n === n) || { title: `Note ${n}`, body: "Missing in registry." }; + const { title, body } = meta; + + useEffect(() => { + if (!open) return; + const handler = (e) => { + if (ref.current && !ref.current.contains(e.target)) setOpen(false); + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [open]); + + if (!on) return null; + const positions = { + "top-left": { top: 6, left: 6 }, + "top-right": { top: 6, right: 6 }, + "bottom-left": { bottom: 6, left: 6 }, + "bottom-right": { bottom: 6, right: 6 }, + }; + return ( +
+ + {open && ( +
+
+ {n} +
{title}
+
+
+ {body} +
+
+ )} +
+ ); +}; + +// ───────── Dev Annotations side panel ───────── +const DevNotesPanel = ({ open, onClose, notes }) => { + if (!open) return null; + return ( +
+
+ +
Dev notes
+ + {notes.length} pins + + +
+
+ Numbered pins overlay every annotated component. Click a pin in the UI for component-level docs; the list here is the index. +
+ {notes.map(note => ( +
+
{note.n}
+
+
{note.title}
+
{note.body}
+
+
+ ))} +
+
+ ); +}; + +Object.assign(window, { + Icon, StatusChip, Button, Sidebar, TopBar, WorkspaceTabs, ScreenHeader, Card, Modal, + DevModeContext, useDevMode, DevNote, DevNotesPanel, + NAV_ITEMS, STATUS_LABEL, +}); diff --git a/docs/oz-control-plane-design/dashboard.jsx b/docs/oz-control-plane-design/dashboard.jsx new file mode 100644 index 0000000..768b753 --- /dev/null +++ b/docs/oz-control-plane-design/dashboard.jsx @@ -0,0 +1,1174 @@ +// Dashboard — the operator's home. Priorities | Oz terminal | Runs (with run-detail drawer). + +const D = window; + +// ───────── Priority row (draggable, run-aware) ───────── +const PriorityRow = ({ priority, index, onLaunch, onDrag, isDragging, isDropTarget, onSelectRun, runs, showDevPin9, selectedRunId }) => { + const linkedRun = priority.runId ? runs.find(r => r.id === priority.runId) : null; + const isRunning = linkedRun && (linkedRun.status === "running" || linkedRun.status === "blocked"); + const isBlocked = linkedRun && linkedRun.status === "blocked"; + const isSelected = linkedRun && linkedRun.id === selectedRunId; + + return ( +
onDrag("start", index, e)} + onDragOver={(e) => { e.preventDefault(); onDrag("over", index, e); }} + onDragEnd={(e) => onDrag("end", index, e)} + onDrop={(e) => { e.preventDefault(); onDrag("drop", index, e); }} + onClick={() => isRunning && onSelectRun(linkedRun.id)} + style={{ + background: isSelected ? 'var(--cb-accent-muted)' : isRunning ? 'var(--cb-accent-subtle)' : 'var(--cb-surface-glass)', + border: `1px solid ${isSelected ? 'var(--cb-accent)' : isBlocked ? 'rgba(212,118,110,0.30)' : isRunning ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRight: isSelected ? '1px solid var(--cb-accent)' : undefined, + borderRadius: isSelected ? 'var(--cb-radius-md) 0 0 var(--cb-radius-md)' : 'var(--cb-radius-md)', + padding: '11px 12px 12px', + marginBottom: 8, + marginRight: isSelected ? -17 : 0, + paddingRight: isSelected ? 24 : 12, + opacity: isDragging ? 0.4 : 1, + boxShadow: isDropTarget ? '0 0 0 2px var(--cb-accent-30)' : + isSelected ? '0 4px 16px rgba(201,169,110,0.18)' : 'none', + cursor: isRunning ? 'pointer' : 'grab', + transition: 'box-shadow 120ms ease-out, background 120ms ease-out, margin-right 200ms ease-out, padding-right 200ms ease-out, border-radius 200ms ease-out', + position: 'relative', + zIndex: isSelected ? 5 : 1, + }} + > + {/* Selected: notch arrow pointing into the run drawer */} + {isSelected && ( +
+ )} + {/* Running accent bar */} + {isRunning && !isSelected && ( +
+ )} + +
+
+ + {String(index + 1).padStart(2, '0')} +
+
+
+ {priority.name} +
+
+ {priority.summary} +
+
+
+ +
+ {linkedRun ? ( + + ) : ( + + )} + {priority.labels.map(l => ( + {l} + ))} +
+ {!isRunning && ( + { e.stopPropagation(); onLaunch(priority); }}> + Launch + + )} +
+
+ + {/* Inline run summary — only when running */} + {isRunning && ( +
+ {showDevPin9 && } +
+ + {linkedRun.id} · {linkedRun.startedAt} + +
+ {linkedRun.personas.slice(0, 4).map(p => ( + {p} + ))} +
+
+
+ {isBlocked && } + {linkedRun.lastEvent} +
+ {linkedRun.progress != null && ( +
+
+
+ )} +
+ )} +
+ ); +}; + +// ───────── Ad-hoc priority row (pinned at top, behaves like any priority) ───────── +const AdhocPriorityRow = ({ adhocRuns, onLaunch, onSelectRun, selectedRunId }) => { + const activeCount = adhocRuns.filter(r => r.status === "running" || r.status === "blocked").length; + const hasSelected = adhocRuns.some(r => r.id === selectedRunId); + + return ( +
0 ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRight: hasSelected ? '1px solid var(--cb-accent)' : undefined, + borderRadius: hasSelected ? 'var(--cb-radius-md) 0 0 var(--cb-radius-md)' : 'var(--cb-radius-md)', + padding: '11px 12px 12px', + marginBottom: 8, + marginRight: hasSelected ? -17 : 0, + paddingRight: hasSelected ? 24 : 12, + position: 'relative', + transition: 'all 200ms ease-out', + boxShadow: hasSelected ? '0 4px 16px rgba(201,169,110,0.18)' : 'none', + zIndex: hasSelected ? 5 : 1, + }}> + +
+
+ + +
+
+
+ Ad-hoc + pinned +
+
+ Refactors · code reviews · research · audits — work that doesn't fit a priority. +
+
+
+ +
+ {activeCount > 0 ? ( + + {activeCount} active + + ) : ( + + )} +
+ { e.stopPropagation(); onLaunch(); }}> + Launch run + +
+
+ + {/* Inline list of ad-hoc runs */} + {adhocRuns.length > 0 && ( +
+ {adhocRuns.map((r, idx) => { + const isSel = r.id === selectedRunId; + const isBlocked = r.status === "blocked"; + const isLive = r.status === "running" || r.status === "blocked"; + return ( +
onSelectRun(r.id)} style={{ + padding: '8px 10px 8px 10px', + background: isSel ? 'var(--cb-accent-15)' : 'transparent', + border: `1px solid ${isSel ? 'var(--cb-accent-30)' : 'var(--cb-border)'}`, + borderRadius: 'var(--cb-radius-sm)', + marginBottom: idx === adhocRuns.length - 1 ? 0 : 6, + cursor: 'pointer', + transition: 'all 120ms ease-out', + position: 'relative', + overflow: 'hidden', + }} + onMouseEnter={e => { if (!isSel) e.currentTarget.style.background = 'var(--cb-hover)'; }} + onMouseLeave={e => { if (!isSel) e.currentTarget.style.background = 'transparent'; }}> + {isLive && ( +
+ )} +
+ + {r.id} + {r.startedAt} +
+
+ {r.title} +
+ {r.lastEvent && ( +
+ {r.lastEvent} +
+ )} +
+ ); + })} +
+ )} +
+ ); +}; + +// ───────── Priorities panel ───────── + +// ───────── Priorities panel (now includes ad-hoc runs section) ───────── +const PrioritiesPanel = ({ priorities, runs, onReorder, onLaunch, onAdhoc, onAddPriority, onSelectRun, onOpenRunHistory, selectedRunId }) => { + const [drag, setDrag] = useState({ from: null, over: null }); + + const handleDrag = (type, index) => { + if (type === "start") setDrag({ from: index, over: null }); + else if (type === "over") setDrag(d => ({ ...d, over: index })); + else if (type === "drop") { + if (drag.from !== null && drag.from !== index) onReorder(drag.from, index); + setDrag({ from: null, over: null }); + } else if (type === "end") setDrag({ from: null, over: null }); + }; + + const adhocRuns = runs.filter(r => !r.priorityId && (r.status === "running" || r.status === "blocked")); + const totalRuns = runs.length; + + return ( +
+ +
+ +
Priorities
+ {priorities.length} +
+ + +
+
+ +
+ {/* Ad-hoc — always-pinned priority */} + !r.priorityId && (r.status === "running" || r.status === "blocked"))} + onLaunch={onAdhoc} + onSelectRun={onSelectRun} + selectedRunId={selectedRunId} + /> + + {priorities.length === 0 ? ( +
+
+
Nothing queued
+
+ Ask Oz to draft your first priority, or add one yourself. +
+ Add priority +
+ ) : ( + <> +
+ QUEUE · ↑ TOP = NEXT UP + +
+ {priorities.map((p, i) => ( + r.id === p.runId && (r.status === "running" || r.status === "blocked"))} + /> + ))} + + )} +
+
+ ); +}; + +// ───────── Oz chat: message ───────── +const ChatMessage = ({ msg, runs, onSelectRun, onDecision }) => { + const isOz = msg.role === "oz"; + const isUser = msg.role === "user"; + const roleLabel = isOz ? "Oz" : isUser ? "You" : msg.role; + const roleColor = isOz ? 'var(--cb-accent)' : isUser ? 'var(--cb-text)' : 'var(--cb-text-secondary)'; + + return ( +
+
+ {roleLabel} + {isOz && orchestrator · headless} + + {msg.time} + +
+
$1') }} /> + + {/* Inline run cards */} + {msg.attachments && msg.attachments.map((a, i) => { + if (a.kind === "run-card") { + const run = runs.find(r => r.id === a.runId); + if (!run) return null; + return ( +
onSelectRun(run.id)} style={{ + marginTop: 10, + padding: 12, + background: 'var(--cb-bg-soft)', + border: '1px solid var(--cb-border)', + borderRadius: 'var(--cb-radius-md)', + cursor: 'pointer', + display: 'flex', alignItems: 'center', gap: 12, + transition: 'border-color 120ms ease-out', + position: 'relative', + }} onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--cb-accent-30)'} + onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--cb-border)'}> + +
+
+ + {run.id} +
+
{run.title}
+
+ {run.personas.join(' · ')} · started {run.startedAt} +
+
+ +
+ ); + } + return null; + })} + + {/* Decision callout */} + {msg.flag === "decision" && ( +
+ + +
+ Oz is waiting for your call before run-1 can continue. +
+
+ onDecision("full")}>Replay full plan + onDecision("partial")}>Partial +
+
+ )} +
+ ); +}; + +// ───────── Oz chat panel ───────── +const QUICK_PROMPTS = [ + { label: "Status check", prompt: "Status across the workspace?" }, + { label: "Launch next priority", prompt: "Launch the next priority." }, + { label: "Ad-hoc run", prompt: "Run an ad-hoc " }, + { label: "Reorder priorities", prompt: "Promote #4 to the top." }, +]; + +const OzChatPanel = ({ messages, runs, workspaceName, onSend, onSelectRun, onDecision, ozTyping }) => { + const [text, setText] = useState(""); + const bodyRef = useRef(null); + + useEffect(() => { + if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; + }, [messages.length, ozTyping]); + + const send = () => { + if (!text.trim()) return; + onSend(text); + setText(""); + }; + + return ( +
+ {/* Decorative corner accents */} +
+
+ +
+ + +
+ +
+ Oz Terminal +
+
+ headless oz · bound to {workspaceName} +
+
+ + watching + + +
+ +
+ {messages.map(m => ( + + ))} + {ozTyping && ( +
+ Oz + + {[0, 1, 2].map(i => ( + + ))} + +
+ )} +
+ + {/* Input */} +
+
+ + {QUICK_PROMPTS.map(qp => ( + + ))} +
+
+ +