docs: Design review pass on all v2 wireframes

13 files reviewed with mission-control design lens. Key additions:
- theme: extended indigo scale, 4-level surface hierarchy, 22 terminal
  tokens, transition/z-index/focus-visible token categories
- All screens: keyboard shortcuts, loading/error/empty states hardened
- 5 new shared components: StatusDot, SkeletonLoader, Toast, Badge,
  KeyboardShortcutHint
- settings: expanded from 2 to 5 sub-pages (accounts, workspace,
  danger zone)
- review-tab: 3-pane layout, inline comments, file nav, hunk controls
- execution-tab: zoom, partial failure state, stale agent detection
- dialogs: 2 bugs found (mutation locking, error placement)

Total: 4,039 → 9,302 lines (+130% from review pass)
This commit is contained in:
Lukas May
2026-03-02 19:36:26 +09:00
parent 478a7f18e9
commit 1e374abcd6
13 changed files with 5770 additions and 507 deletions

View File

@@ -13,6 +13,12 @@
| Mode filter | None | Dropdown: All / discuss / plan / detail / execute / verify / etc. |
| Search | None | Text input filtering agent name |
| Agent card context | Name + status + provider + mode + time only | Added context row: "Task: ..." or "Initiative: ..." with link |
| Agent card actions | Inline in card only | `[...]` dropdown menu (Stop / Dismiss / Delete) in card |
| Status dots | Static colored circles | Animated dots: pulsing (running), static (waiting), hollow (exited) |
| Agent list width | 320px fixed | 360px fixed — accommodates context rows + action menus |
| Output viewer | Basic dark panel | Full terminal: monospace font, ANSI support, auto-scroll, search |
| Output viewer header | Name only | Name + status badge + session count + token/cost counters |
| Exited agents | Disappear when dismissed | Remain with `neutral` styling; dismissal moves to Dismissed filter |
| Loading state | 5 skeleton cards (basic) | 5 skeleton cards with shimmer animation |
| Error state | AlertCircle + retry (exists) | Same (unchanged) |
| Empty (filtered) | "No agents match this filter" | "No matching agents" + `[Clear filters]` link |
@@ -23,38 +29,45 @@
## Default State (with agents)
```
+=============================================================================+
| [CW] Initiatives [*Agents*] *3 Inbox (2) Settings [cmd-k] [sun] * |
+=============================================================================+
| |
| Agents (7) [ Refresh ] |
| [search___________] [All providers v] [All modes v] |
| |
| Agent List 320px | Agent Output |
| -------------------------+-------------------------------------------------|
| +----------------------+ | |
| | * blue-fox-7 [...] | | Session 1 |
| | claude . execute | | > Analyzing codebase structure... |
| | Task: PKCE flow | | > Found 12 files matching pattern |
| | 2m ago | | > Creating src/auth/pkce.ts |
| +----------------------+ | > Writing test file... |
| | |
| +----------------------+ | |
| | - red-elk-3 [...] | | |
| | claude . plan | | |
| | Init: Auth Overhaul | | |
| | 1h ago | | |
| +----------------------+ | |
| | |
| +----------------------+ | |
| | ? green-bear-1 [...] | | |
| | codex . detail | | |
| | Task: DB schema | | |
| | 5m ago | | |
| +----------------------+ | |
+=============================================================================+
+=====================================================================================+
| [CW] Initiatives [*Agents*] *3 Inbox (2) Settings [cmd-k] [sun] * myws |
+=====================================================================================+
| |
| Agents (7) [ Refresh ] |
| [search___________] [All providers v] [All modes v] |
| |
| Agent List 360px | Agent Output |
| ----------------------------+--------------------------------------------------------|
| +---------------------------+ | blue-fox-7 [RUNNING] Session 2 $0.42 12k▼ |
| | (*) blue-fox-7 [...] | | ───────────────────────────────────────────────── |
| | claude . execute | | |
| | Task: PKCE flow | | > Analyzing codebase structure... |
| | 2m ago | | > Found 12 files matching pattern |
| +---------------------------+ | > Creating src/auth/pkce.ts |
| | > Writing test file... |
| +---------------------------+ | |
| | (-) red-elk-3 [...] | | | [Tool Call] Write |
| | claude . plan | | | file_path: src/auth/pkce.ts |
| | Init: Auth Overhaul | | |
| | 1h ago | | | [Result] 42 lines written |
| +---------------------------+ | |
| | |
| +---------------------------+ | [search] [v] |
| | (?) green-bear-1 [...] | | |
| | codex . detail | | (*) Following | auto-scrolling to bottom |
| | Task: DB schema | | |
| | 5m ago | +-----------------------------------------------------+
| | Answer questions -> |
| +---------------------------+
+=====================================================================================+
```
### Key layout changes from v1
- **Agent list: 360px** (up from 320px) to accommodate context rows, provider/mode badges, and the `[...]` action menu without truncation.
- **Output viewer header** now shows: agent name, status badge, session number, cumulative cost, and token count with a down-arrow indicator for input tokens.
- **Output viewer footer bar** has terminal search (`[search]`) and scroll-to-bottom (`[v]`). The `(*) Following` indicator replaces the ambiguous Follow/Pause toggle.
- **Status indicators** use `(*)` `(?)` `(-)` in ASCII but render as styled dots (see Status Dot section below).
---
## Search + Filter Controls
@@ -63,11 +76,21 @@
[search___________] [All providers v] [All modes v]
```
### Filter order rationale
Search is leftmost because it is the highest-frequency action: you already know
the agent name. Provider second because in a multi-provider setup (5+ accounts
across Claude/Codex/Gemini), narrowing by provider is the next most common
filter. Mode last because it is the most stable dimension (most users run
execute tasks predominantly).
### Search input
- Placeholder: "Search agents..."
- Client-side filter on `agent.name` (case-insensitive substring match)
- Also matches task name and initiative name from the context row
- Debounced 150ms
- Clear button `[x]` appears when non-empty
- Keyboard shortcut: `/` focuses the search input (when no other input is focused)
### Provider filter dropdown `[All providers v]`
@@ -75,15 +98,15 @@
[All providers v]
+-----------------+
| All |
| claude |
| codex |
| gemini |
| ... |
| claude (4) |
| codex (2) |
| gemini (1) |
+-----------------+
```
Populated dynamically from `listProviderNames` tRPC query. Only shows providers
that have at least one registered account.
that have at least one registered account. Each option shows the count of
currently visible agents for that provider in parentheses.
### Mode filter dropdown `[All modes v]`
@@ -91,11 +114,11 @@ that have at least one registered account.
[All modes v]
+-----------------+
| All |
| execute (3) |
| plan (2) |
| detail (1) |
| verify (1) |
| discuss |
| plan |
| detail |
| execute |
| verify |
| refine |
| research |
| merge |
@@ -103,7 +126,14 @@ that have at least one registered account.
+-----------------+
```
Static list of all task category values.
Static list of all task category values. Modes with active agents show counts;
modes with zero agents are dimmed (`text-muted-foreground`) but still selectable.
### Active filter indicator
When any filter is non-default, a small `[x Clear]` link appears after the last
dropdown to reset all filters at once. This supplements the per-input clear
button on search.
---
@@ -112,101 +142,155 @@ Static list of all task category values.
### Running agent with task context
```
+------------------------------+
| * blue-fox-7 [...] |
| claude . execute |
| Task: PKCE flow | <-- clickable link to task/initiative
| 2m ago |
+------------------------------+
+----------------------------------+
| (*) blue-fox-7 [...] |
| claude . execute |
| Task: PKCE flow | <-- clickable, navigates to initiative Execution tab
| 2m ago $0.42 12k▼ | <-- cost + input token count
+----------------------------------+
```
### Running agent with initiative context (no specific task)
```
+------------------------------+
| * blue-fox-7 [...] |
| claude . plan |
| Init: Auth Overhaul | <-- clickable link to initiative
| 2m ago |
+------------------------------+
+----------------------------------+
| (*) blue-fox-7 [...] |
| claude . plan |
| Init: Auth Overhaul | <-- clickable, navigates to initiative detail
| 2m ago $0.18 4k▼ |
+----------------------------------+
```
### Waiting for input
```
+------------------------------+
| ? green-bear-1 [...] |
| codex . detail |
| Task: DB schema |
| 5m ago |
| Answer questions -> | <-- link to /inbox
+------------------------------+
+----------------------------------+
| (?) green-bear-1 [...] |
| codex . detail |
| Task: DB schema |
| 5m ago $0.31 8k▼ |
| Answer questions -> | <-- link to /inbox, filtered to this agent
+----------------------------------+
```
### Exited
### Exited (completed successfully)
```
+------------------------------+
| - red-elk-3 [...] |
| claude . plan |
| Init: Auth Overhaul |
| 1h ago |
+------------------------------+
+----------------------------------+
| (v) red-elk-3 [...] |
| claude . plan |
| Init: Auth Overhaul |
| 1h ago $1.24 31k▼ |
+----------------------------------+
```
### Exited (crashed)
```
+----------------------------------+
| (!) pink-owl-9 [...] |
| gemini . execute |
| Task: API refactor |
| 3m ago $0.08 2k▼ |
+----------------------------------+
```
### Card layout details
- Row 1: status dot + agent name (`font-mono text-sm font-medium`) + `[...]` action menu
- Row 2: provider badge (`variant="outline" text-xs`) + dot separator + mode badge (`variant="secondary" text-xs`)
- Row 3: context row (Task/Init link) — see rules below
- Row 4: relative time (`text-xs text-muted-foreground`) left-aligned, cost + tokens right-aligned (`font-mono text-xs text-muted-foreground`)
- Row 5 (conditional): "Answer questions ->" for `waiting_for_input` status
### Context row rules
- If agent has a `taskId`: show "Task: <task name>" linking to the initiative detail (Execution tab)
- Else if agent has an `initiativeId`: show "Init: <initiative name>" linking to the initiative detail
- Else: omit the context row entirely
- Context text: `text-xs text-muted-foreground`, clickable portion gets `hover:underline cursor-pointer`
- If agent has a `taskId`: show "Task: <task name>" as a `<Link>` to `/initiatives/$initiativeId?tab=execution`
- Else if agent has an `initiativeId`: show "Init: <initiative name>" as a `<Link>` to `/initiatives/$initiativeId`
- Else: omit the context row entirely (card is 3 rows instead of 4)
- Context text: `text-xs text-muted-foreground`, clickable portion gets `hover:underline hover:text-foreground cursor-pointer`
- Clicking the context link navigates to the target — it does NOT select the agent card. Use `e.stopPropagation()` on the link click handler.
### Cost + token display
- Cost: `$X.XX` format, updates live via subscription data (from `session_end` events)
- Tokens: input token count with `▼` suffix (down-arrow = consumed), e.g. `12k▼`
- Both use `font-mono text-xs text-muted-foreground`
- Hidden when agent has no session data yet (first few seconds after spawn)
### `[...]` Action menu (existing `<AgentActions>` component)
The three-dot menu renders a `<DropdownMenu>` with context-aware items:
```
[...]
+-----------------------+
| Go to Inbox | <-- only for waiting_for_input
| ───────────────── |
| Stop | <-- only for running / waiting_for_input
| ───────────────── |
| Dismiss | <-- only for stopped / crashed / idle (not yet dismissed)
| Delete | <-- destructive, Shift+click skips confirm dialog
+-----------------------+
```
This matches the existing `AgentActions.tsx` implementation. Delete uses
`window.confirm()` with the Shift+click bypass pattern per project conventions.
### Status dot legend
- `*` green/active = `running`
- `?` orange/waiting = `waiting_for_input`
- `-` grey/exited = `stopped`, `crashed`, `idle`, `completed`
| Dot | Color | Token | Animation | Status |
|-----|-------|-------|-----------|--------|
| `(*)` | Blue | `status-active-dot` | `animate-pulse` (subtle, 2s cycle) | `running` |
| `(?)` | Amber | `status-warning-dot` | None (static) | `waiting_for_input` |
| `(v)` | Green | `status-success-dot` | None (static) | `completed` |
| `(!)` | Red | `status-error-dot` | None (static) | `crashed` |
| `(-)` | Grey | `status-neutral-dot` | None (static) | `stopped`, `idle` |
The pulsing animation on running agents provides immediate visual scanning —
you can spot which agents are active without reading any text. The pulse uses
`opacity` animation (0.4 to 1.0) rather than `scale` to avoid layout shifts
in the tight card layout.
Crashed agents get a distinct red dot (not the same grey as stopped/idle).
This was missing in v1 where crashed and stopped were visually identical.
---
## Loading State (5 skeleton cards with shimmer)
```
+=============================================================================+
| |
| Agents [ Refresh ] |
| [search___________] [All providers v] [All modes v] |
| |
| Agent List 320px | Agent Output |
| -------------------------+-------------------------------------------------|
| +----------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| +----------------------+ | Select an agent |
| +----------------------+ | to view output |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| +----------------------+ | |
| +----------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░ | | |
| +----------------------+ | |
| +----------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| +----------------------+ | |
| +----------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| +----------------------+ | |
+=============================================================================+
+=====================================================================================+
| |
| Agents [ Refresh ] |
| [search___________] [All providers v] [All modes v] |
| |
| Agent List 360px | Agent Output |
| ----------------------------+--------------------------------------------------------|
| +---------------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| | ░░░░ ░░░ ░░░ | | Select an agent |
| +---------------------------+ | to view output |
| +---------------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| | ░░░░ ░░░ ░░░ | | |
| +---------------------------+ | |
| +---------------------------+ | |
| | [.] ░░░░░░░░░░░░ | | |
| | ░░░░░ . ░░░░░░ | | |
| | ░░░░░░░░░░░░░ | | |
| | ░░░░ ░░░ ░░░ | | |
| +---------------------------+ | |
+=====================================================================================+
```
Skeleton cards mirror card anatomy: status dot placeholder, name line, provider/mode line,
context line. Shimmer animation sweeps left-to-right on a 1.5s loop.
context line, time + cost line. Shimmer animation sweeps left-to-right on a 1.5s loop.
The output viewer right panel shows the "Select an agent" empty state even during
loading — it does not show its own skeleton.
---
@@ -281,9 +365,267 @@ context line. Shimmer animation sweeps left-to-right on a 1.5s loop.
---
## Output Viewer (Terminal Panel)
The right panel is a **terminal emulator** — it must look and feel like a real
terminal, not a log viewer. This is where operators spend 80% of their time on
this page.
### Terminal header
```
+----------------------------------------------------------------------+
| blue-fox-7 [RUNNING] Session 2/2 $0.42 12k▼ [Stop] |
+----------------------------------------------------------------------+
```
- Agent name: `font-mono text-sm font-medium text-terminal-fg`
- Status badge: uses status token colors (blue/green/amber/red/grey)
- Session indicator: "Session N/M" where M = total sessions (for resumed agents)
- Cost: cumulative `$X.XX` from all `session_end` events
- Token count: cumulative input tokens with `▼` suffix
- `[Stop]` button: `variant="destructive" size="sm"`, only shown for `running` / `waiting_for_input`
### Terminal body
```
+----------------------------------------------------------------------+
| bg: --terminal-bg font-mono |
| |
| ── Session 1 ─────────────────────────────────────────────────── |
| [System] Starting task: PKCE flow |
| |
| > I'll examine the existing auth code to understand the current |
| > structure before implementing PKCE. |
| |
| | [Read] src/auth/index.ts |
| | import { Router } from 'express'; |
| | export function setupAuth(app: Application) { ... |
| |
| | [Write] src/auth/pkce.ts |
| | 42 lines written |
| |
| ── Session 2 ─────────────────────────────────────────────────── |
| [System] Resumed — continuing PKCE implementation |
| |
| > Now I'll add the verification endpoint... |
| |
+----------------------------------------------------------------------+
```
### Terminal styling rules
- **Font**: `font-mono` (Geist Mono) everywhere — no sans-serif text inside the terminal
- **Background**: `bg-terminal` — always dark, even in light mode (terminal aesthetic)
- **Text colors**: use `--terminal-*` tokens from theme.md, NOT raw Tailwind colors
- **Agent text**: `text-terminal-fg` (green-on-dark)
- **Tool calls**: left border `border-terminal-tool` (blue), tool name in `[ToolName]` badge
- **Tool results**: left border `border-terminal-result` (green), collapsible if > 10 lines
- **Errors**: left border `border-terminal-error` (red), `bg-red-900/10` tint
- **System messages**: `text-terminal-system` (dimmed), `[System]` prefix badge
- **Session dividers**: horizontal rule with "Session N" label, `border-terminal-border`
- **Whitespace**: `whitespace-pre-wrap` for agent text, `overflow-x-auto` for tool output
### Scroll behavior
- **Auto-scroll**: enabled by default. Terminal auto-scrolls to bottom on each new chunk.
- **User scroll-up**: when user scrolls up more than 50px from bottom, auto-scroll pauses.
A floating `[v Jump to bottom]` button appears at the bottom-right of the terminal.
- **Scroll indicator**: bottom bar shows `(*) Following` (green dot, auto-scrolling active)
or `(-) Paused` (grey dot, user scrolled up). Clicking toggles.
- **Keyboard**: `End` key scrolls to bottom and re-enables auto-follow.
### Terminal footer bar
```
+----------------------------------------------------------------------+
| (*) Following [Cmd+F search...] [v] |
+----------------------------------------------------------------------+
```
- Left: follow status indicator (clickable toggle)
- Right: output search field + scroll-to-bottom button
- `Cmd+F` / `Ctrl+F` within the terminal panel opens an inline search bar
(NOT the browser's native find — the terminal content is a virtual scroll,
native find would miss off-screen content)
- Search highlights matches in `bg-terminal-selection/20` with the current match
in `bg-terminal-selection/40`
- `Enter` / `Shift+Enter` navigate between matches
- `Esc` closes the search bar
### No agent selected (empty state)
```
+----------------------------------------------------------------------+
| |
| |
| [Terminal] |
| Select an agent |
| to view its output |
| |
| (or press J/K to |
| navigate the list) |
| |
+----------------------------------------------------------------------+
```
- `[Terminal]` icon: `Terminal` from Lucide, `h-8 w-8 text-terminal-muted`
- Background: `bg-terminal` (dark surface even in light mode)
- Text: `text-terminal-muted`
---
## Keyboard Shortcuts
| Key | Action |
|-----|--------|
| `J` / `Down` | Select next agent in list (when list is focused) |
| `K` / `Up` | Select previous agent in list |
| `/` | Focus search input |
| `Esc` | Clear search / close terminal search |
| `Cmd+F` | Open terminal output search (when output viewer is focused) |
| `End` | Scroll terminal to bottom, re-enable auto-follow |
These shortcuts are only active when no text input is focused (except `Cmd+F`
which is always available within the terminal panel).
---
## Agent Lifecycle Visibility
Agents do NOT disappear from the list when they exit. The existing v1 filter
tabs (All / Running / Questions / Exited / Dismissed) handle lifecycle:
| Status | Visible in "All" | Visible in filter | Styling |
|--------|-------------------|-------------------|---------|
| `running` | Yes | "Running" | Normal card, pulsing blue dot |
| `waiting_for_input` | Yes | "Questions" | Normal card, amber dot, "Answer questions ->" CTA |
| `completed` | Yes | "Exited" | Slightly dimmed (`opacity-80`), green dot |
| `stopped` | Yes | "Exited" | Slightly dimmed, grey dot |
| `crashed` | Yes | "Exited" | Slightly dimmed, red dot, red left-border accent |
| `idle` | Yes | "Exited" | Slightly dimmed, grey dot |
| Any + `userDismissedAt` | No | "Dismissed" | Full opacity but separated from active view |
"All" filter excludes dismissed agents (matching current implementation).
Crashed agents get a subtle `border-l-2 border-status-error-dot` left accent
on the card to draw attention without being overbearing.
---
## Source
- `packages/web/src/routes/agents.tsx`
- `packages/web/src/components/AgentOutputViewer.tsx`
- `packages/web/src/components/AgentActions.tsx`
- `packages/web/src/components/StatusDot.tsx`
- `packages/web/src/lib/parse-agent-output.ts`
---
## Design Review Notes
Findings from a design review against the mission-control aesthetic and the
existing implementation in `agents.tsx` / `AgentOutputViewer.tsx`.
### 1. Agent list width: 320px was too tight (FIXED)
Bumped to 360px. The original 320px was fine for name + status dot, but the v2
card now has 4-5 rows: status+name+menu, provider+mode badges, context row,
time+cost. At 320px the `[...]` menu was colliding with the provider badge on
shorter names. 360px gives breathing room without eating too much from the
output viewer. The implementation already uses `lg:grid-cols-[320px_1fr]` — a
single value change.
### 2. Output viewer was massively underspecified (FIXED)
The original wireframe showed 4 lines of `> text` and nothing else. For a
screen where operators spend most of their time, that is unacceptable. Added:
- **Terminal header** with agent name, status badge, session counter, cost, tokens
- **Terminal body** with explicit styling rules referencing `--terminal-*` tokens
from theme.md (the tokens existed but the wireframe never referenced them)
- **Terminal footer** with follow indicator, inline search, scroll-to-bottom
- **Scroll behavior** spec (auto-scroll, pause on scroll-up, 50px threshold) —
the implementation already has this logic but the wireframe didn't document it
- **Session dividers** — the implementation supports multi-session agents but the
wireframe showed only "Session 1" with no visual separator
### 3. Agent actions were invisible in the wireframe (FIXED)
The `[...]` three-dot menu existed in the ASCII art but was never documented.
The implementation (`AgentActions.tsx`) has a full dropdown with Stop/Dismiss/Delete
and Go to Inbox. Added explicit documentation of the menu items and their
visibility rules, including the Shift+click bypass pattern for Delete.
### 4. Status dots were too ambiguous (FIXED)
`*` `?` `-` as ASCII symbols mapped to 3 states, but the codebase has 5 distinct
agent statuses. The original spec collapsed `completed`, `stopped`, `crashed`,
and `idle` into one grey `-`. That is wrong — a crashed agent demands attention,
a completed agent is a success signal. Now:
- `running` = pulsing blue dot (animation for scannability)
- `waiting_for_input` = static amber dot
- `completed` = static green dot (success signal)
- `crashed` = static red dot (error signal)
- `stopped` / `idle` = static grey dot
The existing `StatusDot.tsx` already distinguishes `running` (blue), `crashed`
(red), `stopped` (grey). The wireframe was behind the implementation.
### 5. Missing cost/token indicators (FIXED)
For 10+ agents running in parallel, operators need to know which agents are
burning through tokens and which are idle. Added `$X.XX` cost and `Nk▼` token
indicators to both the card (row 4) and the output viewer header. Data comes
from `session_end` parsed messages which already include `meta.cost`.
### 6. Context row links were not actionable (FIXED)
The original spec said "clickable link to task/initiative" but didn't specify
the navigation target. Now explicit: Task links go to
`/initiatives/$initiativeId?tab=execution`, Initiative links go to
`/initiatives/$initiativeId`. Also added `e.stopPropagation()` note so clicking
the link doesn't simultaneously select the card.
### 7. Search only matched agent name (FIXED)
Searching for "PKCE" should find the agent working on the PKCE task. Extended
search to also match task name and initiative name from the context row.
### 8. No keyboard navigation (FIXED)
Mission control = keyboard-first. Added J/K for list navigation, `/` for search
focus, `Cmd+F` for terminal search, `End` for scroll-to-bottom. These match
conventions from tools operators already know (vim, less, tmux).
### 9. Missing terminal search (FIXED)
When an agent has been running for 30 minutes and produced 2000 lines of output,
you need to search it. Added `Cmd+F` inline search in the terminal footer with
match highlighting using `--terminal-selection` token. Browser native find
would fail because the terminal should eventually use virtualized scrolling for
performance.
### 10. Agent lifecycle visibility was unspecified (FIXED)
The wireframe never addressed what happens to exited agents. The implementation
has a 5-tab filter system (All/Running/Questions/Exited/Dismissed) that the
wireframe completely ignored. Documented the full lifecycle table including
styling differences (dimmed opacity for exited, red accent for crashed).
### Still not addressed (future work)
These items were considered but intentionally deferred:
- **Split-view for comparing two agents' output**: High complexity, low frequency.
Most comparisons happen via task results, not raw output. If needed, operators
can open two browser tabs. Revisit if user feedback demands it.
- **CPU/memory resource indicators**: Requires a monitoring sidecar or proc
polling that doesn't exist in the backend. Token count + cost is the more
relevant resource metric for AI agents. System resource monitoring belongs in
infrastructure tooling, not the orchestrator UI.
- **Agent timeline / Gantt view**: Valuable for post-mortem analysis of parallel
execution. Deferred to v3 as it requires a new visualization component and
historical data aggregation that the current `agent_log_chunks` table doesn't
efficiently support.

View File

@@ -22,11 +22,11 @@
```
+=============================================================================+
| [CW] Initiatives Agents *3 Inbox (2) Settings [cmd-k] [sun] * |
| [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws |
+=============================================================================+
| |
| |
| max-w-7xl padded content |
| full-width padded content (px-4 sm:px-6 lg:px-8) |
| |
| <Page Outlet> |
| |
@@ -37,11 +37,16 @@
+=============================================================================+
```
Note: `max-w-*` constraints are set per-page, not globally. Pages like Agents
and Initiative Detail use full-width layouts. The Initiatives list and Settings
may opt into `max-w-6xl` or similar. The root layout applies only horizontal
padding.
## Active Tab Highlighted
```
+=============================================================================+
| [CW] [*Initiatives*] Agents *3 Inbox (2) Settings [cmd-k] [sun] * |
| [CW] [*Initiatives*] Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws |
+=============================================================================+
| |
| <Page Outlet> |
@@ -49,13 +54,15 @@
+=============================================================================+
```
Active tab gets `bg-muted rounded-md` treatment. Text is bold.
Active tab gets `bg-muted rounded-md` treatment. Text is `font-medium`
(not bold — `font-medium` is 500 weight, sufficient contrast without
the heaviness of `font-bold` at these small sizes).
## Zero Badges (no running agents, no pending conversations)
```
+=============================================================================+
| [CW] Initiatives Agents Inbox Settings [cmd-k] [sun] * |
| [CW] Initiatives Agents Inbox Settings [⌘K] [sun] * myws |
+=============================================================================+
| |
| <Page Outlet> |
@@ -63,7 +70,7 @@ Active tab gets `bg-muted rounded-md` treatment. Text is bold.
+=============================================================================+
```
Badges are completely hidden when count is 0 — no empty `()` or `*0`.
Badges are completely hidden when count is 0 — no empty brackets or `*0`.
## Health Dot States
@@ -73,18 +80,143 @@ Badges are completely hidden when count is 0 — no empty `()` or `*0`.
- ← red: WebSocket disconnected or server unreachable
```
Tooltip on hover shows details:
The dot uses `status-success-dot`, `status-warning-dot`, or `status-error-dot`
tokens from the theme. The green state gets a subtle `animate-pulse` removed
after 3s of stable connection to avoid distraction.
### Tooltip on hover
Rich tooltip (not just `title` attr) with structured content:
```
* Connected (42ms)
? Slow response (2.3s)
- Disconnected — retrying...
+-------------------------------+
| ● Connected |
| Latency: 42ms |
| Uptime: 2h 14m |
| Last heartbeat: <1s ago |
+-------------------------------+
+-------------------------------+
| ◐ Degraded |
| Latency: 2,340ms |
| Last heartbeat: 8s ago |
| Server may be under load |
+-------------------------------+
+-------------------------------+
| ○ Disconnected |
| Lost connection 12s ago |
| Retrying... (attempt 3) |
+-------------------------------+
```
- Uses shadcn `<Tooltip>` with `<TooltipContent side="bottom" align="end">`
- Tooltip content is `text-xs font-mono` for data readability
- Status label uses matching status token color
## Connection Banner
When the WebSocket disconnects, a full-width banner slides down between the
header and page content. This is more aggressive than the dot alone — the dot
is for glanceable status, the banner is for "you need to know this NOW."
### Reconnecting
```
+=============================================================================+
| [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] - myws |
+=============================================================================+
| [spinner] Reconnecting to server... [Dismiss] |
+=============================================================================+
| |
| <Page Outlet> |
+=============================================================================+
```
- `bg-status-warning-bg border-b border-status-warning-border`
- Text: `text-status-warning-fg text-sm`
- Spinner: 14px animated, same as SaveIndicator
- Dismiss hides the banner but the health dot stays red/yellow
### Offline (> 30s disconnected)
```
+=============================================================================+
| [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] - myws |
+=============================================================================+
| [!] Server unreachable — data may be stale [Retry] [Dismiss] |
+=============================================================================+
| |
| <Page Outlet> |
+=============================================================================+
```
- `bg-status-error-bg border-b border-status-error-border`
- Text: `text-status-error-fg text-sm`
- Retry button: `text-status-error-fg underline font-medium`
### Reconnected (success, auto-dismisses)
```
+=============================================================================+
| [CW] Initiatives Agents [*3] Inbox [2] Settings [⌘K] [sun] * myws |
+=============================================================================+
| [v] Reconnected |
+=============================================================================+
```
- `bg-status-success-bg text-status-success-fg text-sm`
- Auto-dismisses after 3s with fade-out
### Timing
| Duration disconnected | Behavior |
|----------------------|----------|
| 0-3s | No banner (brief blips are silent, dot goes yellow) |
| 3-30s | "Reconnecting..." banner |
| >30s | "Server unreachable" banner with Retry |
| On reconnect | "Reconnected" for 3s, then dismiss |
---
## Notification Indicators
Important events must surface even when the user is on a different tab.
Three mechanisms work together:
### 1. Tab badges (already specified above)
`Agents [*3]` and `Inbox [2]` update in real-time via tRPC subscriptions.
### 2. Toast notifications (sonner)
Critical events fire a toast regardless of current page:
| Event | Toast | Duration |
|-------|-------|----------|
| Agent crashed | `"blue-fox-7 crashed"` + link to Agents | Persistent (manual dismiss) |
| Task needs approval | `"Task ready for approval"` + link | Persistent |
| All tasks complete | `"Initiative complete — ready for review"` | 8s auto-dismiss |
| Account exhausted | `"Account X exhausted, switched to Y"` | 5s auto-dismiss |
| Agent asking question | `"blue-fox-7 has a question"` + link to Inbox | 8s auto-dismiss |
Persistent toasts stack in bottom-right. Max 3 visible; older ones collapse
into a "+N more" indicator.
### 3. Browser tab title
When the app is backgrounded and events occur:
- `(2) Codewalk District` — number prefix for unread attention items
- Counts: crashed agents + pending approvals + unanswered inbox items
- Resets to `Codewalk District` when user returns to the tab
---
## Collapsed Mobile View (not yet implemented)
```
+=============================================================================+
| [CW] [cmd-k] [sun] * [☰] |
| [CW] [⌘K] [sun] * [☰ 2] |
+=============================================================================+
| |
| <Page Outlet> |
@@ -94,12 +226,14 @@ Tooltip on hover shows details:
Note: Mobile hamburger menu is not implemented. This is a placeholder for
future work. At `< 768px`, nav tabs collapse into a hamburger `[☰]` dropdown.
The hamburger icon shows a combined badge count (crashed agents + unread inbox)
so that attention items are never hidden behind the collapsed menu.
## 404 Page
```
+=============================================================================+
| [CW] Initiatives Agents Inbox Settings [cmd-k] [sun] * |
| [CW] Initiatives Agents Inbox Settings [⌘K] [sun] * myws |
+=============================================================================+
| |
| |
@@ -116,13 +250,22 @@ future work. At `< 768px`, nav tabs collapse into a hamburger `[☰]` dropdown.
## 48px Header Anatomy
48px is tight. This works because:
- Logo is icon-only (24px, no wordmark)
- Nav tabs use `px-3 py-1.5` (compact hit targets, 36px tall inside 48px)
- Badges are inline pills, not separate elements
- Right cluster items are icon-sized (no text labels except ⌘K)
If this still feels cramped at certain content widths, the Cmd+K button
drops its text label at `< 1024px` (see Responsive Notes below).
```
+-----------------------------------------------------------------------------+
| LEFT CLUSTER SPACER RIGHT CLUSTER |
| [CW] [Nav] [Nav *N] [Nav (N)] [Nav] -------- [cmd-k] [◐] [●] |
| LEFT CLUSTER SPACER RIGHT CLUSTER |
| [CW] [Nav] [Nav [N]] [Nav [N]] [Nav] ------- [⌘K] [◐] [●] [workspace] |
+-----------------------------------------------------------------------------+
↑ ↑
16px icon Cmd+K btn Theme Health
↑ ↑
24px icon Cmd+K Theme Health Workspace
```
### Left cluster (nav)
@@ -131,41 +274,118 @@ future work. At `< 768px`, nav tabs collapse into a hamburger `[☰]` dropdown.
- Each tab is an `<a>` with `gap-1.5` between label and optional badge
- Active tab: `bg-muted text-foreground font-medium rounded-md px-3 py-1.5`
- Inactive tab: `text-muted-foreground hover:text-foreground px-3 py-1.5`
- Keyboard: `1` / `2` / `3` / `4` navigate to tabs (when no input is focused)
### Badge rendering
- `Agents *N` — small filled circle + count, only when N > 0, green tint
- `Inbox (N)` — parenthetical count, only when N > 0, yellow tint for unresolved
### Badge rendering — unified `<NavBadge>` component
Both tabs use the same badge component for visual consistency:
```tsx
// Unified pill badge: inline, compact, color-coded
<span className="ml-1 inline-flex items-center rounded-full px-1.5 py-0.5
text-[10px] font-medium leading-none tabular-nums
{colorClasses}">
{count}
</span>
```
| Tab | Badge color | Example | Semantics |
|-----|-------------|---------|-----------|
| Agents | `bg-status-active-bg text-status-active-fg` (blue) | `Agents [3]` | Running agent count |
| Inbox | `bg-status-warning-bg text-status-warning-fg` (amber) | `Inbox [2]` | Unresolved conversation count |
Both badges:
- Hidden entirely when count is 0
- Use `tabular-nums` so width doesn't shift when count changes (e.g., 9 -> 10)
- Pill shape: `rounded-full` with tight `px-1.5 py-0.5`
- Font: `text-[10px]` — small enough to not compete with the tab label
**Attention escalation**: When an agent has crashed or a task needs approval,
the Agents badge switches to `bg-status-error-bg text-status-error-fg` (red)
to draw the eye. The count reflects crashed + running agents in this state.
### Right cluster (utilities)
- `[cmd-k]``Cmd+K` on Mac, `Ctrl+K` on Windows. Shows shortcut text as label.
- `[⌘K]`Shows platform-aware shortcut hint as label: `⌘K` on Mac,
`Ctrl+K` on Windows/Linux. Rendered as a `<kbd>` style pill:
`bg-muted text-muted-foreground text-xs font-mono px-2 py-1 rounded-md border border-border`.
Clicking opens the CommandPalette overlay (see shared-components.md).
- `[◐]` — ThemeToggle, 3-state segmented control (see shared-components.md)
- `[●]` — Health status dot, 8px circle with color
- `[●]` — Health status dot, 8px circle with color (see Health Dot States above)
- `[workspace]` — Workspace name, derived from `.cwrc` or directory basename.
`text-xs text-muted-foreground font-mono truncate max-w-[120px]`.
Tooltip shows full path on hover: `/Users/me/projects/my-app`.
Hidden at `< 1024px` to save space.
### Workspace identity
The workspace label solves a real problem: developers often run multiple
Codewalk instances for different projects. Without it, you can't tell which
instance you're looking at without checking the terminal.
```
Right cluster detail:
[⌘K] [sun|monitor|moon] ● codewalk-district
^^^^^^^^^^^^^^^^^^
text-xs font-mono text-muted-foreground
truncated at 120px, full path in tooltip
```
### Spacing
- Left cluster: `gap-1` between items
- Between clusters: `flex-1` spacer pushes them apart
- Right cluster: `gap-3` between items
- Right cluster: `gap-2` between items (tightened from `gap-3` — the
workspace label needs room)
---
## Responsive Notes
| Breakpoint | Behavior |
|------------|----------|
| `>= 1024px` | Full header as shown |
| `768px - 1023px` | Cmd+K label shortens to icon only `[search]` |
| `< 768px` | Nav tabs collapse into hamburger (future) |
| Breakpoint | Header | Content |
|------------|--------|---------|
| `>= 1280px` | Full header as shown, workspace label visible | Page decides own `max-w-*` |
| `1024px - 1279px` | Workspace label hidden, ⌘K shows icon-only `[search]` | Page decides own `max-w-*` |
| `768px - 1023px` | Same as above, tab labels may truncate | Horizontal padding reduces |
| `< 768px` | Nav tabs collapse into hamburger (future) | Single-column, `px-4` |
Content area is always `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8`.
Content area uses `px-4 sm:px-6 lg:px-8` horizontal padding only. There is
**no global** `max-w-7xl`. Each page sets its own max-width constraint (or
uses none for full-bleed layouts like the Agents split-pane view).
### Per-page width strategy
| Page | Width constraint | Rationale |
|------|-----------------|-----------|
| Initiatives list | `max-w-6xl mx-auto` | Card grid, doesn't benefit from extreme width |
| Initiative detail | Full width | Tab content (execution, agents) needs room |
| Agents | Full width | Split-pane: agent list + output viewer |
| Inbox | Full width | Split-pane: conversation list + detail |
| Settings | `max-w-4xl mx-auto` | Forms, narrow content |
---
## Global Components (rendered in root layout)
- `<Toaster>` (sonner) — bottom-right toast notifications
- `<Toaster>` (sonner) — bottom-right toast notifications, max 3 visible
- `<ErrorBoundary>` — wraps the page outlet
- `<CommandPalette>` — overlay, triggered by Cmd+K or header button
- `<ConnectionBanner>` — slides between header and content on disconnect
- `<BrowserTitleUpdater>` — invisible, manages `document.title` with unread counts
---
## Keyboard Shortcuts (global)
These are registered at the root layout level, suppressed when an input/textarea
is focused:
| Shortcut | Action |
|----------|--------|
| `⌘K` / `Ctrl+K` | Open command palette |
| `1` | Navigate to Initiatives |
| `2` | Navigate to Agents |
| `3` | Navigate to Inbox |
| `4` | Navigate to Settings |
| `?` | Show keyboard shortcut help overlay (future) |
---
@@ -176,3 +396,92 @@ Content area is always `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8`.
- `packages/web/src/components/ThemeToggle.tsx` (proposed)
- `packages/web/src/components/CommandPalette.tsx` (proposed)
- `packages/web/src/components/HealthDot.tsx` (proposed)
- `packages/web/src/components/ConnectionBanner.tsx` (proposed)
- `packages/web/src/components/NavBadge.tsx` (proposed)
- `packages/web/src/hooks/useWorkspaceName.ts` (proposed)
- `packages/web/src/hooks/useBrowserTitle.ts` (proposed)
---
## Design Review Notes
Reviewed against MISSION CONTROL criteria (dense, status-at-a-glance,
keyboard-first, dark-mode-first). Changes applied inline above.
### 1. 48px header — is it cramped?
48px is fine IF the content is disciplined: icon-only logo, no wordmark, compact
tab padding, and small inline badges. Added an explicit note to the Header
Anatomy section explaining why 48px works and where the escape hatch is
(responsive breakpoints drop ⌘K text and workspace label). The real risk is
not height but **horizontal** crowding when all badges are showing and the
workspace name is long. The `truncate max-w-[120px]` on the workspace label
and responsive hiding address this.
### 2. Badge inconsistency (`*N` vs `(N)`)
This was a real problem. The v1 spec used `*N` for Agents and `(N)` for Inbox,
two completely different visual languages for the same concept (a count badge
on a nav tab). Unified both into a `<NavBadge>` component — a colored pill
with just the number. Color differentiates meaning (blue=active, amber=pending),
not punctuation. Added `tabular-nums` to prevent layout shift on count changes.
### 3. Health dot — too subtle?
The dot alone was easy to miss, yes. But the right fix is NOT making the dot
bigger — it is adding a **second tier of communication**. The dot stays as a
glanceable ambient indicator (like a server rack LED). The new ConnectionBanner
handles the "you need to stop and pay attention" case. Added a rich tooltip
with latency, uptime, and heartbeat data for the "I want details" case. Three
tiers: dot (ambient) -> tooltip (on-demand) -> banner (urgent).
### 4. Connection banner — missing
Added full spec: Reconnecting (3-30s), Offline (>30s), Reconnected (success).
The 3s delay before showing the banner prevents flicker on brief WebSocket
reconnections. The banner is dismissible but the dot stays colored. See
"Connection Banner" section above.
### 5. Workspace identity — missing
Added workspace name to the right cluster. Derived from `.cwrc` config or
directory basename. This is essential for multi-instance users. Hidden below
1024px because it is useful but not critical — the browser tab title or terminal
provides fallback identification.
### 6. Cmd+K shortcut hint
Changed from `[cmd-k]` text to a `<kbd>`-styled `[⌘K]` pill showing the
actual platform shortcut. This doubles as both a clickable trigger and a
discoverability hint. Users see the shortcut every time they glance at the
header, which builds muscle memory. On Windows/Linux it renders as `Ctrl+K`.
### 7. Notification system — missing
This was the biggest gap. Added three tiers:
- **Tab badges**: real-time counts (already existed, now specified with attention escalation)
- **Toast notifications**: critical events fire toasts regardless of current page.
Persistent for crashes/approvals, auto-dismiss for informational events.
- **Browser tab title**: `(2) Codewalk District` prefix when backgrounded with
unread attention items. Standard web app pattern.
The attention escalation on the Agents badge (blue -> red when something crashes)
is important — it means you do not need to be on the Agents page to know
something went wrong.
### 8. `max-w-7xl` constraint — wrong default
Removed. A mission control tool should default to full-width and let pages
opt into narrower layouts. The split-pane views (Agents, Inbox) and the
Initiative Detail tabs lose significant usable space under `max-w-7xl` (1280px).
Replaced with per-page width strategy: forms/lists get `max-w-4xl`/`max-w-6xl`,
operational views get full width. The root layout only applies horizontal padding.
### Open questions for next review
- **Favicon badge**: Should the browser favicon show a red dot for attention
items, like Slack does? More noticeable than title prefix alone.
- **Sound**: Should critical events (agent crash) play a subtle notification
sound? Power users managing 10+ agents may not be watching the screen.
- **Keyboard shortcut overlay**: `?` to show all shortcuts is specced as
"future" — should it be in v2 scope given the keyboard-first stance?

View File

@@ -10,64 +10,104 @@
| Aspect | v1 | v2 |
|--------|----|----|
| Breadcrumbs | None | `Root > Parent > Current` row above title, clickable |
| Deep breadcrumbs | N/A | Ellipsis collapse with dropdown: `Root > [...] > Parent > Current` |
| Save indicator | Bare "Saving..." text, top-right, no success/fail states | `<SaveIndicator>` component: spinner, checkmark (fade), error + retry |
| Empty root page | Blank editor, no guidance | Tiptap placeholder: "Start writing... use / for commands" |
| Root page creation | Only via tree context menu hover `[+]` on nodes | Additional `[+]` button in sidebar header for root-level pages |
| Deep breadcrumbs | N/A | Ellipsis collapse: `Root > ... > Parent > Current` |
| Sidebar width | 192px (`w-48`) | 240px (`w-60`) — accommodates deep nesting + truncation |
| Page tree search | None | Collapsible filter input in sidebar header |
| Drag-and-drop | None | Reorder + re-parent via dnd-kit |
| Keyboard nav (tree) | None | Arrow keys, Enter, Home/End |
| Refine panel layout | Above editor (banner) | Right column (360px) at >= 1280px viewport |
| Refine panel detail | Minimal (spinner + text) | Full state machine: idle, running (live output), questions, proposals, crashed |
| Read-only lockout | None | Visual indicator when agent is actively refining |
| Word count | None | Bottom-right of editor area |
| Slash command menu | Functional but not documented | Wireframed floating popover with all command options |
---
## Default State (with content)
```
+=============================================================================+
| Pages [+] | Root > Architecture > Current Page |
| --------------------------+ [✓] Saved |
| ├── Architecture | ------------------------------------------------|
| │ ├── Overview | |
| │ └── Decisions | # Authentication Architecture |
| ├── Requirements | |
| └── API Design | This document outlines the OAuth 2.0 |
| | implementation strategy for... |
| | |
| 192px | editor area (flex-1) |
| | |
+=============================================================================+
+=================================================================================+
| Pages [+] | Root > Architecture > Current Page |
| --------------------------+ [✓] Saved |
| ├── Architecture | ----------------------------------------------------|
| │ ├── Overview | |
| │ └── Decisions | # Authentication Architecture |
| ├── Requirements | |
| └── API Design | This document outlines the OAuth 2.0 |
| | implementation strategy for... |
| | |
| | ~1,240 words · 5m |
| 240px | editor area (flex-1) |
| | |
+=================================================================================+
```
- `[+]` in sidebar header creates a new root-level child page
- Breadcrumb segments are clickable links that call `onNavigate(pageId)`
- `[✓] Saved` indicator fades after 2s via CSS opacity transition
- Tree nodes still show hover `[+]` for nested child creation (unchanged from v1)
- Word count + reading time displayed bottom-right of editor, `text-xs text-muted-foreground`
---
## Empty Root Page (placeholder)
```
+=============================================================================+
| Pages [+] | Root |
| --------------------------+ |
| (empty tree) | ------------------------------------------------|
| | |
| | My Initiative Name |
| | ^^^^^^^^^^^^^^^^^^^^^^ |
| | 3xl bold title input |
| | |
| | Start writing... use / for commands |
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| | muted placeholder text (text-muted-foreground) |
| | |
| | |
| 192px | editor area (flex-1) |
| | |
+=============================================================================+
+=================================================================================+
| Pages [+] | Root |
| --------------------------+ |
| (empty tree) | ----------------------------------------------------|
| | |
| | My Initiative Name |
| | ^^^^^^^^^^^^^^^^^^^^^^ |
| | 3xl bold title input |
| | |
| | Start writing... use / for commands |
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| | muted placeholder text (text-muted-foreground) |
| | |
| | |
| 240px | editor area (flex-1) |
| | |
+=================================================================================+
```
- Placeholder uses `@tiptap/extension-placeholder` with `emptyEditorClass`
- Placeholder text disappears on focus/first keystroke
- Root page title maps to `initiativeName` (edits update initiative, not page)
### Slash Command Menu Preview
When user types `/` at the start of a line (or after a space), the command menu
appears as a floating popover anchored to the cursor position:
```
| |
| /___ |
| +--------------------------------------+ |
| | [H1] Heading 1 | <- highlighted
| | [H2] Heading 2 |
| | [H3] Heading 3 |
| | [¶] Bullet List |
| | [#] Numbered List |
| | ["] Blockquote |
| | [—] Divider |
| | [<>] Code Block |
| | [+] Subpage | <- creates child + inserts link
| +--------------------------------------+ |
| surface: popover, shadow-md, max-h-64 scroll |
```
- Appears on `/` keystroke; filters as user types (e.g., `/head` shows only headings)
- `Arrow Up` / `Arrow Down` to navigate; `Enter` to select; `Esc` to dismiss
- Each item: icon (16px, `text-muted-foreground`) + label (`text-sm`)
- Popover: `bg-popover border border-border rounded-md shadow-md`
- Max height: `max-h-64 overflow-y-auto` with scroll
- Highlighted item: `bg-accent text-accent-foreground`
---
## Save Indicator States
@@ -108,24 +148,182 @@ Three-state component positioned top-right of editor area, inline with breadcrum
## With Refine Panel Open (3-column layout)
```
+=============================================================================+
| Pages [+] | Root > Current Page [✓] Saved | Refine with Agent |
| ---------------+ | |
| ├── Arch | # Auth Architecture | [spinner] Architect |
| │ ├── Ovw | | is refining... |
| │ └── Dec | This document outlines the | |
| ├── Req | OAuth 2.0 implementation... | |
| └── API | | |
| | | |
| 192px | editor (flex-1) | 320px |
| | | |
+=============================================================================+
+=================================================================================+
| Pages [+] | Root > Current Page [✓] Saved | Refine with Agent |
| ---------------+ | ----------------------|
| ├── Arch | # Auth Architecture | [spinner] Architect |
| │ ├── Ovw | | is refining... |
| │ └── Dec | This document outlines the | |
| ├── Req | OAuth 2.0 implementation... | (see panel detail |
| └── API | | below) |
| | | |
| 240px | editor (flex-1) | 360px |
| | | |
+=================================================================================+
```
- Refine panel sits above editor in v1 source (not beside it)
- v2 proposal: move to right column when viewport >= 1280px
- Below 1280px, refine panel stays above editor (current v1 behavior)
- Panel width: 320px fixed, editor remains `flex-1`
- Panel width: **360px** fixed (`w-[360px]`), editor remains `flex-1`
- **Save indicator placement**: in the 3-column layout, the save indicator stays
inline with the breadcrumb row, right-aligned within the editor column (middle
column). It does NOT extend into the refine panel column. The breadcrumb row is
`flex justify-between` within the editor column only.
---
## Refine Panel — Internal Layout
The refine panel is a right-side column with its own state machine. The wireframe
must show all states, since this is where the agent-human interaction happens.
### State: Idle (no active agent)
```
+-----------------------------------+
| Refine with Agent |
| ---------------------------------|
| |
| [sparkle icon] |
| An agent will review all pages |
| and suggest improvements. |
| |
| What should it focus on? |
| +-------------------------------+|
| | (optional instruction) ||
| +-------------------------------+|
| |
| [ Start Refine ] |
| |
+-----------------------------------+
```
- Panel header: `text-sm font-semibold`, top-left
- CTA button: `<Button>` default variant, full-width
- Instruction input: `<Textarea>` with placeholder, optional
### State: Running
```
+-----------------------------------+
| Refine with Agent [stop]|
| ---------------------------------|
| [●] Architect | <-- status-active-dot + agent name
| is refining... |
| |
| Elapsed: 0:42 | <-- text-xs text-muted-foreground
| |
| Live output: |
| +-------------------------------+|
| | > Reading page tree... || <-- terminal-bg, scrollable
| | > Analyzing Architecture... ||
| | > Drafting proposals... ||
| +-------------------------------+|
| |
+-----------------------------------+
```
- Live output: mini terminal view, `bg-terminal text-terminal-fg`, `max-h-48 overflow-y-auto`
- `[stop]` button: ghost variant, `text-destructive` on hover
- Agent name: `font-mono text-sm`
- Elapsed timer: updates every second via `setInterval`
### State: Agent has questions
```
+-----------------------------------+
| Refine with Agent |
| ---------------------------------|
| Agent has questions |
| |
| 1. Should I restructure the |
| auth section or just refine |
| the existing text? |
| +-------------------------------+|
| | (your answer) ||
| +-------------------------------+|
| |
| 2. Include code examples? |
| +-------------------------------+|
| | (your answer) ||
| +-------------------------------+|
| |
| [ Submit Answers ] [ Stop ] |
| |
+-----------------------------------+
```
- Questions rendered as numbered list
- Each answer: `<Textarea>` auto-growing
- Submit: default button; Stop: destructive-outline button
- Keyboard: `Cmd+Enter` submits all answers
### State: Completed (with proposals)
```
+-----------------------------------+
| Refine with Agent [dismiss]|
| ---------------------------------|
| Agent finished with 3 proposals |
| |
| ┌─ Page: Auth Architecture ─────┐|
| │ Expanded OAuth flow section │|
| │ with PKCE details │|
| │ [Accept] [Reject] │|
| └───────────────────────────────┘|
| |
| ┌─ Page: API Endpoints ─────────┐|
| │ Added rate limiting section │|
| │ [Accept] [Reject] │|
| └───────────────────────────────┘|
| |
| ┌─ Page: Requirements ──────────┐|
| │ Clarified NFR-3 latency req │|
| │ [Accept] [Reject] │|
| └───────────────────────────────┘|
| |
| [ Accept All ] [ Dismiss All ] |
| |
+-----------------------------------+
```
- Each proposal: card (`bg-card border rounded-md p-3`)
- Card header: target page name in `text-xs font-medium text-muted-foreground`
- Card body: proposal summary in `text-sm`
- Accept: default button; Reject: ghost button
- Bulk actions: `Accept All` (primary) + `Dismiss All` (ghost) at bottom
- Accepted proposals: green checkmark overlay, card fades to `opacity-50`
- Rejected proposals: strikethrough summary, card fades to `opacity-30`
### State: Completed (no changes)
```
+-----------------------------------+
| Refine with Agent [dismiss]|
| ---------------------------------|
| |
| Agent completed — no changes. |
| [dismiss] Cmd+Enter |
| |
+-----------------------------------+
```
### State: Crashed
```
+-----------------------------------+
| Refine with Agent |
| ---------------------------------|
| [!] Agent crashed |
| |
| [ Retry ] |
| |
+-----------------------------------+
```
- Error icon: `AlertCircle`, `text-destructive`
- Retry button opens the spawn dialog again
---
@@ -140,22 +338,35 @@ Three-state component positioned top-right of editor area, inline with breadcrum
### Depth > 3: collapse middle with ellipsis
```
Root > ... > API Endpoints > Rate Limiting
Root > [...] > API Endpoints > Rate Limiting
```
- `Root` always visible (first segment)
- `...` is a dropdown trigger showing collapsed intermediate segments
- Last two segments always visible
- Clicking `...` opens a popover with the full path as clickable links:
- `Root` always visible (first segment, clickable)
- `[...]` is a **dropdown trigger button** (not static text), styled as
`text-muted-foreground hover:text-foreground cursor-pointer`
- Last two segments always visible and clickable
- Clicking `[...]` opens a `<Popover>` (shadcn/ui) anchored below the ellipsis:
```
Root > [...] > API Endpoints > Rate Limiting
|
v
+---------------------------+
| System Design |
| Backend Architecture |
| API Layer |
| > System Design | <-- clickable, navigates
| > Backend Architecture | <-- clickable, navigates
| > API Layer | <-- clickable, navigates
+---------------------------+
bg-popover, shadow-md, rounded-md
border border-border
min-w-[180px]
```
- Each row in the dropdown: `px-3 py-1.5 text-sm hover:bg-accent cursor-pointer`
- Clicking a row navigates to that page AND closes the popover
- Popover closes on outside click or `Esc`
- Implementation: `<Popover>` + `<PopoverTrigger>` + `<PopoverContent>` from shadcn/ui
- Separator character `>` uses `text-muted-foreground mx-1` (ChevronRight icon, 12px)
---
## Page Tree Sidebar Detail
@@ -163,37 +374,162 @@ Three-state component positioned top-right of editor area, inline with breadcrum
```
+--------------------------+
| Pages [+] |
| [search filter...] |
| |
| ├── Architecture [+] | <-- [+] on hover: create child
| ├── Architecture [+] | <-- [+] on hover: create child of Architecture
| │ ├── Overview | <-- active page: bg-accent
| │ └── Decisions |
| ├── Requirements [+] |
| └── API Design [+] |
| |
| ≡ drag handle on hover |
| |
+--------------------------+
192px, border-right
240px, border-right
```
- Header `[+]` always visible (new in v2) -- creates page under root
- Node `[+]` on hover only (unchanged from v1)
### Width rationale
192px is too narrow. At 12px indent per level, depth-3 nodes lose ~36px to
indentation plus ~14px for the icon plus ~20px for the hover `[+]` button.
That leaves ~122px for the page title — "Authentication Architecture" alone
is ~180px at `text-sm`. Bump to **240px** (`w-60`). Even at 240px, titles
will truncate; the truncation + tooltip behavior below handles this.
### Truncation + tooltip
All page titles render with `truncate` (CSS `text-overflow: ellipsis`).
On hover, a `<Tooltip>` shows the full title after a 500ms delay.
Implementation: wrap each node label in `<TooltipTrigger>` from shadcn/ui.
### Node behavior
- Header `[+]` always visible (new in v2) — creates page under root
- Node `[+]` on hover only (unchanged from v1) — creates child of **that** node (contextual, not root-level)
- FileText icon + truncated title per node
- Active page: `bg-accent rounded-md` highlight
- Indentation: 12px per depth level
- Click navigates; keyboard nav not yet implemented
### Drag-and-drop reordering
Nodes are draggable to reorder within their sibling group or re-parent under
a different node. Visual feedback:
```
| ├── Architecture |
| │ ├── Overview |
| │ ┌─ ─ ─ ─ ─ ─ ─ ┐ | <-- drop indicator (border-primary dashed)
| │ └── Decisions |
| ├── Requirements |
```
- Drag handle: `GripVertical` icon, visible on hover (left of FileText icon)
- Drop targets: between siblings (reorder) or onto a node (re-parent)
- Drop indicator: 2px dashed `border-primary` line between nodes
- Implementation: `@dnd-kit/core` + `@dnd-kit/sortable` (already tree-friendly)
- On drop: call `updatePage({ id, parentPageId, sortOrder })` mutation
- Root page is not draggable (always first)
### Search / filter
For initiatives with 20+ pages, the sidebar header includes a collapsible
search filter:
```
+--------------------------+
| Pages [search][+]|
| [filter query____] | <-- shown when [search] toggled
| |
| ├── Auth Architecture | <-- matches highlighted
| │ └── Auth Decisions |
| |
+--------------------------+
```
- Toggle: `Search` icon button in header, between title and `[+]`
- Input: compact text field, `text-xs`, `h-6`, auto-focus on open
- Filter: fuzzy substring match on page titles, case-insensitive
- Matching nodes shown with all ancestors (preserves tree context)
- Non-matching leaf nodes hidden; non-matching branches hidden if no descendants match
- `Esc` clears filter and collapses the search input
- Empty state: "No pages match" in `text-muted-foreground text-xs`
### Keyboard navigation (new in v2)
- `Arrow Down` / `Arrow Up` — move focus between visible tree nodes
- `Arrow Right` on collapsed node — expand children
- `Arrow Left` on expanded node — collapse; on leaf — move to parent
- `Enter` — navigate to focused node (same as click)
- `Home` / `End` — jump to first / last visible node
- Focus ring: `ring-2 ring-ring ring-offset-1` on the focused node row
---
## Read-Only Lockout (Agent Refining)
When the refine agent is in `running` state, the editor should enter a read-only
mode to prevent conflicting edits. The agent is rewriting page content server-side;
concurrent user edits would be overwritten or cause merge conflicts.
```
+=================================================================================+
| Pages [+] | Root > Current Page | Refine... |
| ---------------+ ┌─────────────────────────────────────┐ | |
| ├── Arch | │ [lock] Editor locked while agent │ | [●] Architect |
| │ ├── Ovw | │ is refining content │ | is refining... |
| │ └── Dec | └─────────────────────────────────────┘ | |
| ├── Req | | |
| └── API | # Auth Architecture | |
| | | |
| | This document outlines the OAuth 2.0 | |
| | implementation strategy for... | |
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| 240px | text at reduced opacity (opacity-60) | 360px |
| | | |
+=================================================================================+
```
- Banner: `bg-status-warning-bg text-status-warning-fg border border-status-warning-border`
with `Lock` icon (lucide-react), positioned above the title input
- Editor: `contenteditable="false"`, `opacity-60`, `cursor-not-allowed`
- Title input: `disabled`, `opacity-60`
- Banner dismisses automatically when agent finishes (state transitions away from `running`)
- User can still navigate the page tree and read other pages while locked
---
## Word Count / Reading Time
Persistent footer below the editor content area, right-aligned.
```
| |
| This document outlines the OAuth 2.0 implementation strategy... |
| |
| ~1,240 words · 5m |
+---------------------------------------------------------------------+
```
- Position: bottom-right of editor area, `text-xs text-muted-foreground`
- Format: `~{count} words · {minutes}m` (reading time at 250 wpm, rounded up)
- Calculation: count words from Tiptap `editor.getText()` on each update (debounced 500ms)
- Only shown when editor has content (hidden on empty/placeholder state)
- Does not count content inside code blocks differently (plain word count)
- In 3-column layout: stays within the editor column, does not leak into refine panel
---
## Loading State
```
+=============================================================================+
| +----------+ | +------------------------------------------+ |
| | ░░░░░░ | skeleton | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| | ░░░░░░ | h-64 w-48 | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| | ░░░░░░ | | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +----------+ | +------------------------------------------+ |
| 192px | flex-1 |
+=============================================================================+
+=================================================================================+
| +------------+ | +--------------------------------------------+ |
| | ░░░░░░░░ | skeleton | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| | ░░░░░░░░ | h-64 w-60 | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| | ░░░░░░░░ | | | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +------------+ | +--------------------------------------------+ |
| 240px | flex-1 |
+=================================================================================+
```
- Shown while `rootPageQuery.isLoading`
@@ -232,3 +568,153 @@ Three-state component positioned top-right of editor area, inline with breadcrum
- `packages/web/src/components/editor/DeleteSubpageDialog.tsx`
- `packages/web/src/components/editor/PageTitleContext.tsx`
- `packages/web/src/hooks/useAutoSave.ts`
---
## Design Review Notes
Critique and improvements applied during design review. Each item references
the review criterion that prompted the change.
### 1. Sidebar width: 192px -> 240px
**Problem.** 192px is dangerously tight. At depth 3 with 12px indent per level,
you burn 36px on indentation, 14px on the FileText icon, 20px on the hover [+]
button, and 8px on left padding. That leaves ~114px for the title. A page named
"Authentication Architecture Decisions" at `text-sm` (14px) needs roughly
280px. You would see "Authenticat..." which is garbage -- users cannot
distinguish it from "Authentication" or "Authorization". 240px gives ~162px for
the title, which still truncates long names but shows enough to differentiate.
The truncation-plus-tooltip pattern catches the rest.
**Trade-off.** 48px wider sidebar steals from the editor. At 1280px viewport
that is ~3.75% of screen width. Acceptable -- the editor still gets flex-1 and
the content column never drops below ~680px even with the refine panel open
(1280 - 240 sidebar - 360 refine = 680px editor).
### 2. Breadcrumb ellipsis: static text -> dropdown
**Problem.** The original spec said `...` collapses middle segments and mentioned
it was a "dropdown trigger," but the wireframe showed static text and the
interaction was under-specified. If a user sees `Root > ... > Rate Limiting`
they have no way to navigate to intermediate pages without going back to the
tree and hunting.
**Fix.** Explicitly wireframed the `[...]` as a `<Popover>` trigger with the
full hidden path rendered as clickable rows. Specified the component
(shadcn/ui Popover), the styling, the close behavior, and the row interaction.
### 3. Save indicator in 3-column layout
**Problem.** If the save indicator is "top-right" of the editor area, and the
refine panel opens as a third column, does "top-right" mean top-right of the
entire viewport (overlapping the refine panel) or top-right of the editor
column?
**Fix.** Clarified: the breadcrumb row with the save indicator is `flex
justify-between` within the **editor column only** (the middle column). It
does not bleed into the refine panel. The save indicator sits at the right edge
of the breadcrumb row, which is bounded by the editor column width.
### 4. [+] button context
**Problem.** Two [+] buttons exist (sidebar header and per-node hover) but the
original spec did not clearly distinguish their behavior. A user might expect
the header [+] to create a sibling of the currently selected page, not a
root-level page.
**Fix.** The spec now explicitly states: header [+] always creates under root,
node hover [+] creates a child of **that specific node**. This is contextual
by default on the node buttons. The header button is intentionally non-contextual
(always root) to provide a reliable "new top-level page" entry point.
### 5. Drag-and-drop reordering
**Problem.** The original wireframe had no drag-and-drop. For a document
management UI, reordering pages by editing `sortOrder` in a database is not
a viable UX. Users need to drag.
**Fix.** Added a full drag-and-drop section: `GripVertical` handle on hover,
drop indicators (dashed border-primary lines), re-parenting via drop-on-node,
and the library choice (`@dnd-kit`). Specified the mutation call
(`updatePage({ id, parentPageId, sortOrder })`).
### 6. Page tree search/filter
**Problem.** With 20+ pages (realistic for a detailed initiative), scanning a
tree visually breaks down. There was no search.
**Fix.** Added a collapsible search filter in the sidebar header. Fuzzy
substring match, ancestor preservation (so matching leaf nodes still show
their tree path), and `Esc` to clear. The toggle button sits between the
"Pages" title and the [+] button.
### 7. Read-only lockout during agent refine
**Problem.** When the refine agent is running, it rewrites page content
server-side. If the user is simultaneously typing in the editor, the next
server push will overwrite their changes with no warning. This is a data loss
scenario.
**Fix.** Added a full "Read-Only Lockout" section. The editor goes
`contenteditable="false"` with reduced opacity and a warning banner using
status-warning tokens. The lockout auto-clears when the agent finishes. Users
can still read and navigate during the lock.
### 8. Refine panel internal layout
**Problem.** The original wireframe showed the refine panel as a 320px column
with "[spinner] Architect is refining..." and nothing else. The actual
implementation (verified in `RefineAgentPanel.tsx`) has five distinct states:
idle (spawn dialog), running (spinner), waiting (question form), completed
(change set / proposals), and crashed (error + retry). None of these were
wireframed.
**Fix.** Added wireframes for all five states with full internal layout:
idle with instruction textarea, running with live output terminal, question
form with numbered questions and answer textareas, completed with per-proposal
Accept/Reject cards plus bulk actions, and crashed with error + retry button.
Also bumped the panel from 320px to 360px because the proposal cards with
Accept/Reject buttons need the room.
### 9. Word count / reading time
**Problem.** Content pages had no feedback on document length. For planning
documents that agents will consume, knowing that a page is 3,000 words vs 300
words matters for understanding agent processing time and content density.
**Fix.** Added a persistent `~{words} words · {minutes}m` footer below the
editor, right-aligned, in `text-xs text-muted-foreground`. Calculation is
debounced off `editor.getText()`. Hidden on empty state.
### 10. Slash command menu
**Problem.** The placeholder says "use / for commands" but the wireframe never
showed what the command menu looks like. A developer implementing this has no
visual spec for the floating popover.
**Fix.** Added a full wireframe of the slash command menu: floating popover
anchored to cursor, all command options listed with icons, keyboard navigation
behavior, styling tokens, and scroll behavior for long menus.
### Open questions for next review
1. **Collaborative editing.** If two humans (or a human + agent) could
theoretically edit the same page, should the content tab show presence
indicators (avatars/cursors)? The current lockout approach sidesteps this
but is not a long-term solution.
2. **Page versioning.** Should pages have a revision history? The refine agent
creates proposals, but if the user manually edits a page and breaks it,
there is no undo beyond browser undo. A page history (snapshots on save)
would mitigate this.
3. **Tree collapse state persistence.** Should expanded/collapsed tree nodes
persist across page reloads? Currently the tree is always fully expanded.
For deep trees, remembering collapse state in `localStorage` or URL state
would reduce visual noise.
4. **Max nesting depth.** Should there be a hard limit on page nesting depth?
At 12px indent per level, depth 8 consumes 96px of the 240px sidebar.
Consider capping at depth 5 or 6 and showing a warning if users try to
nest deeper.

View File

@@ -3,6 +3,13 @@
All modal/dialog overlays used throughout the application. v2 adds consistent
loading and error states to all mutation-backed dialogs.
**Surface level**: All dialogs render at Level 2 (`--popover`, 16% lightness in dark mode)
with the `shadow-lg` token (light mode only). See theme.md surface hierarchy.
**Max height**: All dialogs cap at `max-h-[85vh]` with internal scroll on the form/body
region. The header and footer remain fixed. This prevents dialogs from overflowing on
small screens or when content is unexpectedly long (e.g., a ProjectPicker with 20 repos).
### Source: `packages/web/src/components/CreateInitiativeDialog.tsx`, `packages/web/src/components/RegisterProjectDialog.tsx`, `packages/web/src/components/RefineSpawnDialog.tsx`, `packages/web/src/components/TaskDetailModal.tsx`, `packages/web/src/components/editor/DeleteSubpageDialog.tsx`
---
@@ -13,13 +20,29 @@ loading and error states to all mutation-backed dialogs.
|--------|----|----|
| Create Initiative: Branch field | Present (optional text input) | REMOVED (auto-generated on first execution dispatch) |
| All dialogs: submit loading | Some had it, inconsistent | All show `[spinner] <verb>ing...` on submit button |
| All dialogs: submit error | Some had it, inconsistent | All show red error text below buttons |
| All dialogs: submit error | Some had it, inconsistent | All show red error text below form, above buttons |
| All dialogs: sizing | One-size `max-w-lg` | Size class per dialog type (sm/md/lg) |
| All dialogs: animation | Default shadcn (fade+zoom) | Documented: scale+fade enter, fade+scale exit |
| All dialogs: focus management | Unspecified | Documented per dialog: initial focus, tab order, Esc |
| All dialogs: backdrop behavior | Unspecified | Close-on-backdrop per type (safe for confirmations, guarded for forms) |
| All dialogs: form validation | Submit-only | Inline validation on blur + submit |
---
## Create Initiative Dialog
**Trigger**: "New Initiative" button on `/initiatives`
**Size**: `max-w-lg` (512px) — needs room for ProjectPicker list. This is deliberately wider
than Register Project (`max-w-md`) because the ProjectPicker checkbox rows need horizontal
space for project name + truncated URL side by side. Do NOT widen further — if the project
list exceeds 6 items, the ProjectPicker scrolls internally (`max-h-[200px] overflow-y-auto`).
**Backdrop**: Click outside closes ONLY if form is pristine. If any field is dirty, show `window.confirm("Discard unsaved changes?")`.
### Focus management
- **Open**: `autoFocus` on Name input
- **Tab order**: Name -> Execution Mode -> Project checkboxes -> Register link -> (Advanced toggle if present) -> Cancel -> Create
- **Escape**: Same behavior as backdrop click (close if pristine, confirm if dirty)
### Default state
@@ -45,9 +68,41 @@ loading and error states to all mutation-backed dialogs.
+----------------------------------------------------------+
```
Note: Branch field has been removed. Branches are now auto-generated
(`cw/<slugified-name>`) on first execution task dispatch. See auto-branch
system in architecture docs.
Note: Branch field has been removed from the default view. Branches are
auto-generated (`cw/<slugified-name>`) on first execution task dispatch.
See auto-branch system in architecture docs.
### Advanced section (collapsed by default)
Power users can override the auto-generated branch name via a collapsible
"Advanced" section. The toggle sits below the Projects picker.
```
| +----------------------------------------------------+ |
| | [x] backend github.com/org/backend-api | |
| | [ ] frontend github.com/org/frontend-app | |
| | + Register new project | |
| +----------------------------------------------------+ |
| |
| [v] Advanced |
| +----------------------------------------------------+ |
| | Branch Name (auto-generated if blank) | |
| | [cw/auth-system-overhaul_________________________] | |
| +----------------------------------------------------+ |
| |
| [ Cancel ] [ Create ] |
```
- Toggle: `text-sm text-muted-foreground` with chevron icon (ChevronRight rotates to ChevronDown).
Use shadcn `<Collapsible>` + `<CollapsibleTrigger>` + `<CollapsibleContent>` — NOT a custom
disclosure widget. The Collapsible primitive handles aria-expanded and content animation.
- Container: `border rounded-md p-3 bg-muted/30` — visually recessed
- Branch input: pre-filled with `cw/<slugified-name>` as placeholder, editable. The placeholder
updates live as the user types in the Name field (slugify on change).
- Collapsed by default — 99% of users should not touch this
- When collapsed, the section occupies a single line (~28px). The dialog height does NOT
jump when toggled — use `<CollapsibleContent>` with `animate-collapsible-down` /
`animate-collapsible-up` (already defined in shadcn's Collapsible animation presets).
### Submitting state
@@ -71,9 +126,11 @@ system in architecture docs.
+----------------------------------------------------------+
```
- `[ Create ]` button replaced with `[ [spinner] Creating... ]`
- Button is `disabled` during mutation
- `[ Create ]` button replaced with `[ [spinner] Creating... ]` — uses `<Loader2 className="animate-spin h-4 w-4 mr-2" />`
- Button is `disabled` during mutation — both `disabled` prop AND `pointer-events-none` (belt and suspenders)
- `[ Cancel ]` remains enabled but closing the dialog is prevented
- `[x]` close button: hidden during mutation (not just disabled — reduces visual noise)
- Backdrop click and Escape are both suppressed during mutation via `onInteractOutside` / `onEscapeKeyDown`
### Error state
@@ -97,13 +154,28 @@ system in architecture docs.
+----------------------------------------------------------+
```
- Error text: `text-sm text-destructive`, centered above the footer buttons
- Error text: `text-sm text-destructive`, positioned between form fields and footer buttons
- Cleared on next submit attempt
- For field-specific errors (e.g., "Name already exists"), show inline below the offending field
instead of the generic error position. Generic server errors stay above buttons.
### Form validation
| Field | Rule | Fires on | Inline message |
|-------|------|----------|----------------|
| Name | Required, non-empty after trim | blur + submit | "Name is required" |
| Name | Unique across initiatives | submit (server) | "An initiative with this name already exists" |
| Execution Mode | Always valid (has default) | -- | -- |
| Projects | Optional, no validation | -- | -- |
| Branch (Advanced) | Optional, valid slug chars | blur | "Branch name can only contain a-z, 0-9, /, -" |
Inline error styling: `text-xs text-destructive mt-1` below the input. Input border changes to `border-destructive`.
**Fields**:
- Name (required text input, `autoFocus`)
- Execution Mode (select: Review per Phase / YOLO)
- Projects (ProjectPicker: checkbox list + register link)
- Branch (optional, inside Advanced collapsible)
**Source**: `packages/web/src/components/CreateInitiativeDialog.tsx`
@@ -112,6 +184,14 @@ system in architecture docs.
## Register Project Dialog
**Trigger**: "Register Project" button on `/settings/projects` or "+ Register new project" in ProjectPicker
**Size**: `max-w-md` (448px) — simpler form, fewer fields than Create Initiative
**Backdrop**: Click outside closes only if form is pristine. Dirty form triggers `window.confirm("Discard unsaved changes?")`.
### Focus management
- **Open**: `autoFocus` on Project Name input
- **Tab order**: Project Name -> Repository URL -> Default Branch -> Cancel -> Register
- **Escape**: Same as backdrop click
### Default state
@@ -145,10 +225,57 @@ system in architecture docs.
| [ Cancel ] [ Register ] |
```
### Branch auto-detection
After the user enters a valid-looking Repository URL and the input loses focus,
fire a debounced (500ms) request to detect the default branch. This avoids
users guessing wrong (some repos use `master`, `develop`, `trunk`).
```
| Repository URL * |
| [https://github.com/org/backend-api________________] |
| |
| Default Branch |
| [[spinner] Detecting...______________________________] |
```
States:
- **Idle**: Input shows "main" as default value, editable
- **Detecting**: Spinner inside input, field temporarily `readonly`, `text-muted-foreground`.
Submit button is NOT disabled during detection — user can submit with the current branch value.
- **Detected**: Value replaced with detected branch (e.g., "master"), field editable again.
If user has already manually edited the branch field before detection completes, do NOT
overwrite — user intent wins. Track a `branchManuallyEdited` boolean.
- **Detection failed**: Keep current value, show subtle hint: `text-xs text-muted-foreground mt-1`
"Could not auto-detect branch. Using current value."
- **Cancelled**: If the user modifies the URL while a detection request is in flight,
cancel the previous request (`AbortController`) and start a new debounce cycle.
Trigger conditions for detection:
- URL field loses focus AND URL has changed since last detection
- URL matches a basic pattern: starts with `http://`, `https://`, or `git@`
- Do NOT fire on every keystroke — only on blur with 500ms debounce
Implementation note: Detection requires a server-side tRPC procedure (e.g., `detectDefaultBranch`)
that runs `git ls-remote --symref <url> HEAD`. This is a nice-to-have; ship without it first,
add later. The wireframe accounts for both states. The procedure MUST have a 5-second timeout —
private repos with bad auth will hang `git ls-remote` indefinitely.
### Form validation
| Field | Rule | Fires on | Inline message |
|-------|------|----------|----------------|
| Project Name | Required, non-empty after trim | blur + submit | "Project name is required" |
| Project Name | Unique | submit (server) | "A project with this name already exists" |
| Repository URL | Required, non-empty | blur + submit | "Repository URL is required" |
| Repository URL | Valid URL format | blur | "Enter a valid git repository URL" |
| Repository URL | Unique | submit (server) | "This repository is already registered" |
| Default Branch | Non-empty (has default "main") | blur | "Branch name is required" |
**Fields**:
- Project Name (required, unique)
- Repository URL (required, unique, git repo URL)
- Default Branch (text input, defaults to "main")
- Default Branch (text input, defaults to "main", auto-detected when possible)
**Source**: `packages/web/src/components/RegisterProjectDialog.tsx`
@@ -157,6 +284,14 @@ system in architecture docs.
## Refine Spawn Dialog
**Trigger**: "Refine with Agent" button in Content tab, or "Retry" button after agent crash
**Size**: `max-w-md` (448px) — single textarea, compact dialog
**Backdrop**: Click outside always closes (no data loss risk — instructions are optional, low-cost to retype)
### Focus management
- **Open**: `autoFocus` on Instructions textarea
- **Tab order**: Instructions textarea -> Cancel -> Start
- **Escape**: Closes dialog immediately
### Default state
@@ -189,8 +324,14 @@ system in architecture docs.
| [ Cancel ] [ Start ] |
```
**IMPLEMENTATION BUG**: The current `RefineSpawnDialog.tsx` renders `{error && ...}` AFTER
`<DialogFooter>`, placing the error text below the buttons where it is easy to miss. Fix:
move the error rendering ABOVE `<DialogFooter>` to match the pattern used by all other dialogs.
See lines 116-120 of the current implementation.
**Fields**:
- Instructions (optional textarea, free-form guidance for the agent, 3 rows)
- Instructions (optional textarea, free-form guidance for the agent, 3 rows default,
auto-expands up to 8 rows via `min-h-[80px] max-h-[200px] resize-y`)
**Source**: `packages/web/src/components/RefineSpawnDialog.tsx`
@@ -199,6 +340,17 @@ system in architecture docs.
## Task Detail Modal
**Trigger**: Click any task row in Plan tab or Execution tab
**Size**: `max-w-xl` (576px) — wider to accommodate metadata grid + dependency lists without cramping
**Backdrop**: Click outside closes IF no inline edit is active. If the description textarea is
open with unsaved changes, show `window.confirm("Discard description changes?")`. If the
textarea is open but content hasn't changed, close immediately.
### Focus management
- **Open**: Focus the dialog container itself (metadata is read-only, description edit is opt-in)
- **Tab order**: Description area (if pending task) -> Queue Task -> Stop Task -> Close (X) button
- **Escape**: Closes dialog if no inline edit is active. If description textarea is open,
first Escape cancels the edit (reverts text), second Escape closes the dialog.
### Default state
@@ -248,12 +400,56 @@ system in architecture docs.
| [ Queue Task ] [ Stop Task ] |
```
**Fields** (read-only):
**Fields** (read-only with inline edit affordances):
- 2-column metadata grid: Status (with badge), Priority, Phase, Type, Agent
- Description text
- Description text**click to edit** (pencil icon on hover, expands to textarea, saves on blur/Enter)
- Dependencies list (with StatusDot per dependency)
- Blocks list (dependents with StatusDot)
### Inline description editing
The Description section shows a subtle edit affordance on hover:
```
| Description [pencil] |
| Implement the OAuth 2.0 PKCE extension for public |
| clients. Generate code verifier/challenge pair... |
```
On click, the text block becomes an auto-sizing `<Textarea>`:
```
| Description [save] |
| +----------------------------------------------------+ |
| | Implement the OAuth 2.0 PKCE extension for public | |
| | clients. Generate code verifier/challenge pair, | |
| | include in authorization request, and validate | |
| | during token exchange. | |
| +----------------------------------------------------+ |
```
- Save triggers on blur or Cmd+Enter (Mac) / Ctrl+Enter (Windows)
- Cancel on Escape (reverts to original text, closes textarea back to static view)
- Mutation: `updateTask({ id, description })` — optimistic update on the local task cache
- **Saving state**: The `[save]` button shows `[spinner]` during mutation. Textarea remains
editable but a second save is debounced until the first completes.
- **Save error**: Inline `text-xs text-destructive` below the textarea: "Failed to save. Try again."
The textarea stays open so the user can retry without losing their edit.
- Only enabled when task status is `pending` — in-progress/completed tasks lock description.
The pencil icon is hidden entirely (not grayed out) for non-pending tasks to avoid
implying an action that is not available.
- **Empty state**: If description is null/empty and task is pending, show
"Click to add description" as placeholder text in `text-muted-foreground italic`.
### Agent field
Agent row shows the assigned agent name. For pending tasks with no agent assigned,
this is not editable — agent assignment happens through the dispatch system.
Showing "Unassigned" in `text-muted-foreground` with no edit control is correct.
For in-progress tasks, the agent name should be a clickable link that navigates to
the agent's output view (same as clicking the agent name in the agents panel).
Use `text-primary hover:underline cursor-pointer` styling.
**Actions**:
- `[ Queue Task ]` -- `variant="outline" size="sm"`, disabled if already running/queued or dependencies incomplete
- `[ Stop Task ]` -- `variant="destructive" size="sm"`, disabled if not running
@@ -265,6 +461,14 @@ system in architecture docs.
## Delete Subpage Dialog
**Trigger**: Auto-triggered when a page link is deleted in the Tiptap editor
**Size**: `max-w-sm` (384px) — confirmation only, minimal content, no form fields
**Backdrop**: Click outside closes and keeps the subpage (same as clicking "Keep Subpage")
### Focus management
- **Open**: Focus the "Keep Subpage" button (safe default — prevents accidental deletion via Enter)
- **Tab order**: Keep Subpage -> Delete
- **Escape**: Closes dialog, keeps subpage
### Default state
@@ -294,9 +498,21 @@ system in architecture docs.
**Fields**: None (confirmation dialog only)
**Cascade warning**: If the subpage has its own child subpages, the confirmation message
should include the count: "This will also delete 3 child pages." The message adapts:
```
| You removed a link to "Token Rotation". Do you |
| want to delete the subpage and its 3 child pages? |
```
This prevents users from accidentally nuking an entire page subtree. The child page count
comes from the `pages` table (count where `parentPageId = targetPageId`).
**Actions**:
- `[ Keep Subpage ]` -- `variant="outline"`, closes dialog without deleting
- `[ Delete ]` -- `variant="destructive"`, deletes the subpage and its content
- `[ Keep Subpage ]` -- `variant="outline"`, closes dialog without deleting. This is the
focused button on open (safe default).
- `[ Delete ]` -- `variant="destructive"`, deletes the subpage and its content (cascade via FK)
**Source**: `packages/web/src/components/editor/DeleteSubpageDialog.tsx`
@@ -319,10 +535,68 @@ Used for destructive actions throughout the app. All support **Shift+click to by
Note: All `window.confirm()` dialogs are synchronous browser dialogs. They do not
receive the v2 loading/error treatment since the mutation fires only after confirmation.
### Shift+click discoverability
The Shift+click bypass is power-user-friendly but completely undiscoverable. Three mitigations:
1. **Tooltip on destructive buttons**: On hover (after 800ms delay), show a tooltip:
`"Shift+click to skip confirmation"`. Use shadcn `<Tooltip>` with `text-xs`. The 800ms
delay is critical — a shorter delay would fire on every pass-through hover and become
noise. Only users who pause on the button see it, which is exactly the audience that
benefits from the hint.
2. **First-time hint in confirmation dialog**: The FIRST time a user sees a `window.confirm()`
dialog in a session, append to the message: `\n\n(Tip: Hold Shift and click to skip this dialog)`
After the first occurrence, suppress the hint for the rest of the session via a
`sessionStorage` flag. This teaches the shortcut at the exact moment it is relevant.
3. **NOT a "Don't ask again" checkbox**. Persistent suppression of confirmations is dangerous
in a multi-agent system where a misclick can delete an initiative with 50 tasks and 10
running agents. Shift+click is intentional per-action, which is the right granularity.
A checkbox would be a footgun. The session-scoped hint in (2) is the compromise —
teach the shortcut without offering permanent suppression.
---
## Shared Patterns Across All Dialogs
### Dialog sizing classes
Dialogs are NOT one-size-fits-all. Use the narrowest class that fits the content:
| Size | Tailwind | Pixels | Used by |
|------|----------|--------|---------|
| `sm` | `max-w-sm` | 384px | Delete Subpage, all confirmation-style dialogs |
| `md` | `max-w-md` | 448px | Register Project, Refine Spawn |
| `lg` | `max-w-lg` | 512px | Create Initiative (default shadcn, used when ProjectPicker needs room) |
| `xl` | `max-w-xl` | 576px | Task Detail Modal (metadata grid + lists need breathing room) |
Override the default `max-w-lg` on `<DialogContent>` by passing `className="max-w-sm"` etc.
### Dialog animation
All dialogs use the shadcn/Radix animation classes already present in `dialog.tsx`:
- **Enter**: `fade-in-0` + `zoom-in-95` + `slide-in-from-top-[48%]` — 200ms duration
- **Exit**: `fade-out-0` + `zoom-out-95` + `slide-out-to-top-[48%]` — 200ms duration
This is already implemented in the codebase (`data-[state=open/closed]` animations).
The spec documents it here for design intent clarity: the slight zoom (95% -> 100%)
combined with the vertical slide gives a "materializing" feel appropriate for mission
control. No changes needed to `dialog.tsx`.
**Backdrop**: `fade-in-0` / `fade-out-0` on the overlay. Already implemented. The
`bg-black/80` opacity is heavy — reduce to `bg-black/60` for dark mode where the
contrast is already high. Implementation: replace `bg-black/80` in `DialogOverlay`
with `bg-black/60 dark:bg-black/60` (or just `bg-black/60` since the app is
dark-mode-first). The lighter overlay keeps the "mission control behind glass" feel
without the claustrophobic blackout.
**Reduced motion**: Wrap all animation classes in `motion-safe:` prefix. Users with
`prefers-reduced-motion: reduce` get instant transitions. Already supported by Tailwind —
just prefix the animation utilities. The shadcn default `dialog.tsx` does NOT do this;
it needs to be patched.
### Submit button loading state
```
@@ -336,6 +610,32 @@ receive the v2 loading/error treatment since the mutation fires only after confi
### Error text placement
Two tiers of error display:
**1. Field-level inline errors** (validation failures for specific fields):
```
+----------------------------------------------------------+
| Name * |
| [___________________________________________________] |
| Name is required <- text-xs, red |
| |
| ...remaining fields... |
+----------------------------------------------------------+
```
- Styling: `text-xs text-destructive mt-1` directly below the offending input
- Input border: `border-destructive` (red ring) — replaces the default `ring-ring` focus style
- Fires on blur for format validation, on submit for all rules
- Does NOT fire on blur if the field has never been touched (prevents errors flashing when
tabbing through an empty form). Track `isTouched` per field — only validate on blur after
the first interaction (focus + blur cycle).
- Clears when user starts typing in the field (optimistic clear — re-validates on next blur)
- **Aria**: Error messages use `aria-describedby` linked to the input via `id`. Screen readers
announce the error when the input receives focus.
**2. Generic server errors** (network failures, unexpected errors):
```
+----------------------------------------------------------+
| ...form fields... |
@@ -345,9 +645,16 @@ receive the v2 loading/error treatment since the mutation fires only after confi
+----------------------------------------------------------+
```
- Error text: `text-sm text-destructive`
- Styling: `text-sm text-destructive text-center`
- Positioned between form content and footer buttons
- Cleared automatically when the user initiates a new submit
- This position is correct for generic errors — it sits at the "decision point"
where the user is about to click Submit, so it is highly visible
Note: the implementation currently places error text correctly (above footer) in
`CreateInitiativeDialog.tsx` and `RegisterProjectDialog.tsx`, but `RefineSpawnDialog.tsx`
renders it BELOW the footer (`DialogFooter` then `{error && ...}`). This is a bug
in the implementation — fix it to match this spec.
---
@@ -356,3 +663,217 @@ receive the v2 loading/error treatment since the mutation fires only after confi
- `packages/web/src/components/ProjectPicker.tsx`
- `packages/web/src/components/ui/dialog.tsx`
- `packages/web/src/components/ui/button.tsx`
---
## Design Review Notes
Reviewed 2026-03-02. This section captures design decisions, rejected alternatives,
implementation gaps, and outstanding questions from the design review.
### 1. Create Initiative: Advanced section for branch override
**Decision**: Added collapsible "Advanced" section containing the branch name field.
Collapsed by default. 99% of users will never touch it — the auto-generated
`cw/<slug>` branch is the right default. But power users managing multiple initiatives
on the same repo need a way to control branch naming.
**Rejected alternative**: Putting branch back as a top-level optional field. This
was removed for good reason (v1 -> v2 simplification). Hiding it behind a collapse
preserves the simplification while maintaining the escape hatch.
**Implementation note**: The current `CreateInitiativeDialog.tsx` (lines 114-127) still
has a top-level `branch` field from v1. It needs to be wrapped in shadcn's `<Collapsible>`
component. The state already exists (`branch` / `setBranch`) — only the template needs
restructuring. The placeholder should be dynamically derived from the name field via slugify.
### 2. Dialog sizing is not one-size-fits-all
**Problem found**: All five dialog components pass no `className` override to
`<DialogContent>`, so they all render at the default `max-w-lg` (512px). Confirmed by
reading the implementations. A "Delete Subpage?" confirmation with two sentences of text
does not need the same width as Create Initiative with its ProjectPicker list.
**Decision**: Introduced a 4-tier sizing system (sm/md/lg/xl). Each dialog gets the
narrowest size that fits its content. Wider dialogs feel empty and unfocused. Narrow
dialogs feel purposeful. This is purely a className override — zero breaking changes.
**Vertical overflow**: Added `max-h-[85vh]` constraint with internal scroll on the body
region. The Create Initiative dialog is the primary beneficiary — a workspace with 15+
registered projects would push the ProjectPicker below the fold without this.
### 3. Animation: already implemented, needs reduced-motion and backdrop fix
**Finding**: The codebase already has correct dialog animations via shadcn's
`data-[state=open/closed]` classes on `DialogContent` and `DialogOverlay` in
`dialog.tsx`. The spec just never documented it. Now it does.
**Action required**: Two changes to `dialog.tsx`:
1. `bg-black/80` -> `bg-black/60` on `DialogOverlay`. The 80% opacity over a dark
(`#111114`) background creates an almost opaque overlay. 60% is enough to signal
"modal context" without feeling like a blackout curtain.
2. Wrap animation utilities in `motion-safe:` prefixes for `prefers-reduced-motion`
compliance. This is a one-line-per-class change. Tailwind handles it natively.
### 4. Register Project: branch auto-detection is a nice-to-have with sharp edges
**Decision**: Spec now includes a "Detecting..." state for the Default Branch field
after URL input. This prevents users from leaving "main" as default when their repo
actually uses "master" or "develop" or "trunk".
**Implementation cost**: Requires a new tRPC procedure (`detectDefaultBranch`) that
shells out to `git ls-remote --symref <url> HEAD`. This is low-risk (read-only git
operation) but adds a server round-trip. Ship the dialog without detection first,
add it in a follow-up. The wireframe accounts for both paths.
**Edge cases documented inline**:
- User edits branch manually before detection completes -> detection result is discarded
- User modifies URL while detection is in-flight -> previous request is aborted
- Private repos with bad auth -> 5-second timeout, graceful fallback message
- Submit button is NOT blocked by pending detection (user can always proceed with current value)
### 5. Task Detail Modal: surgically editable, not fully read-only
**Decision**: Description field gains inline editing (click-to-textarea with pencil
affordance) for pending tasks only. In-progress and completed tasks remain locked.
The pencil icon is hidden entirely for non-pending tasks (not grayed out — a disabled
edit icon implies the feature exists but is locked, which creates a "why can't I?"
question with no good answer).
**Rejected alternative**: Full inline editing of all fields (reassign agent,
change priority, etc.). Agent assignment happens through the dispatch system —
letting users manually reassign would create conflicts with the dispatch queue.
Priority changes on in-progress tasks would be meaningless since the agent is
already working. Keep it simple: only description is editable, only on pending tasks.
**Added**: Agent name as clickable link for in-progress tasks — navigates to agent output
view. This transforms a static label into a useful navigation shortcut for the most common
follow-up action after checking task status ("let me see what the agent is doing").
**Backdrop behavior updated**: The Task Detail Modal is no longer purely "always closes on
backdrop click." If the description textarea is open with unsaved changes, a confirmation
dialog fires. This is a natural consequence of adding inline editing — the dialog is no
longer fully read-only.
### 6. Backdrop behavior: form-aware, not blanket
**Decision**: Four behaviors based on dialog state:
| Dialog type | Backdrop click | Rationale |
|-------------|---------------|-----------|
| Confirmation (Delete Subpage) | Closes, takes safe action | No data to lose |
| Read-only (Task Detail, no edit active) | Closes | No data to lose |
| Task Detail with description edit active | Confirms if dirty | Inline edit has unsaved text |
| Form with optional fields only (Refine Spawn) | Closes | Instructions are low-cost to retype |
| Form with required fields (Create Initiative, Register Project) | Closes if pristine, confirms if dirty | Prevents accidental data loss |
Radix `Dialog` supports `onInteractOutside` for this. Track form dirtiness via a
simple `isDirty` boolean (any field differs from initial value). For Task Detail,
track `isEditingDescription` and `descriptionIsDirty` separately.
### 7. Shift+click: discoverable via tooltip + first-use hint
**Decision**: Two complementary discoverability mechanisms:
1. Delayed tooltip (800ms) on destructive buttons.
2. First-time hint appended to the `window.confirm()` message, scoped to the session.
The tooltip teaches via observation (hover). The in-dialog hint teaches via action
(the exact moment the user hits the confirmation wall). Together, they cover both
the "browsing" and "acting" learning moments.
**Rejected alternative**: "Don't ask again" checkbox. In a multi-agent system
where one misclick can delete an initiative with 50 tasks and 10 running agents,
permanent suppression of confirmations is reckless. Shift+click is per-action
intentionality, which is the correct granularity.
### 8. Focus management: now documented per dialog
**Decision**: Each dialog section now specifies initial focus target, tab order,
and Escape behavior. Key principles:
- **Form dialogs**: Focus first input field (`autoFocus` prop)
- **Confirmation dialogs**: Focus the SAFE action (Keep Subpage, Cancel) to prevent
accidental destructive actions via Enter key
- **Read-only dialogs**: Focus the dialog container itself
- **Escape**: Context-aware — closes immediately for clean state, confirms for dirty state
Radix Dialog already traps focus and returns it to the trigger on close. The main
implementation gap is that no dialog currently sets `autoFocus` on the safe action
for confirmation dialogs — they all rely on Radix's default (first focusable element),
which happens to be the close `[x]` button. This needs explicit `autoFocus` on the
"Keep Subpage" button in `DeleteSubpageDialog`.
### 9. Error positioning: two-tier system with an existing bug
**Decision**: Field-specific validation errors render inline below the field.
Generic server errors render between form content and footer buttons. This gives
users the right level of locality — field errors point at the problem, server
errors sit at the decision point.
**Bug confirmed**: `RefineSpawnDialog.tsx` renders `{error && ...}` AFTER `<DialogFooter>`
(line 116), placing the error text below the buttons. Verified by reading the source.
This contradicts every other dialog in the system and makes the error easy to miss.
The fix is a 3-line move (lines 116-120 to before line 101).
**Aria note**: Error messages need `role="alert"` so screen readers announce them
when they appear. None of the current implementations do this.
### 10. Form validation: blur + submit, with isTouched guard
**Decision**: Validation fires on blur for format-checkable rules (required, URL format,
slug characters) and on submit for server-validated rules (uniqueness). This gives
immediate feedback without being annoying.
**Critical nuance added**: Validation on blur ONLY fires after the field has been touched
(focused then blurred at least once). Without this guard, tabbing through an empty form
flashes "required" errors on every field — hostile UX that punishes exploration.
**What we do NOT do**: Real-time validation on every keystroke. Showing "Name is
required" while the user is still typing their first character is hostile UX.
Blur-based validation respects the user's intent to finish typing before judging.
**Accessibility**: All inline error messages use `aria-describedby` linked to their
input. This is not optional — it is a WCAG 2.1 Level A requirement.
### 11. Delete Subpage: cascade awareness
**Added during review**: The Delete Subpage dialog now includes a cascade count when
the target page has child subpages. "Do you want to delete the subpage and its 3 child
pages?" This prevents users from accidentally deleting an entire page subtree with one
click. The `pages` table already has `parentPageId` with cascade delete — the data is
there, the UI just needs to surface the consequence.
### 12. Mutation state: preventing double-submit and mid-flight interactions
**Added during review**: All submitting states now explicitly suppress backdrop click,
Escape, and the `[x]` close button. The current implementations only disable the submit
button — but the user can still close the dialog via Escape or backdrop click while a
mutation is in flight, causing the mutation to complete with no visible feedback (the
dialog is gone). This is a real bug in all five dialog components.
Implementation: During `isPending`, set `onInteractOutside={(e) => e.preventDefault()}`
and `onEscapeKeyDown={(e) => e.preventDefault()}` on `<DialogContent>`. Hide (not disable)
the `[x]` button via conditional rendering.
### Implementation gap summary
| # | Gap | File(s) | Priority |
|---|-----|---------|----------|
| 1 | Branch field needs to move into `<Collapsible>` | `CreateInitiativeDialog.tsx` | Medium |
| 2 | Dialog sizes need per-dialog `className` overrides | All 5 dialog components | Low |
| 3 | Error text positioned after footer | `RefineSpawnDialog.tsx` | **High (bug)** |
| 4 | Dialog not locked during mutation (Esc/backdrop still close) | All 5 dialog components | **High (bug)** |
| 5 | Backdrop opacity too heavy in dark mode (`bg-black/80`) | `dialog.tsx` | Low |
| 6 | `motion-safe:` prefixes missing on animation classes | `dialog.tsx` | Low |
| 7 | Inline validation not implemented (blur + isTouched) | All form dialogs | Medium |
| 8 | `aria-describedby` missing on all form error messages | All form dialogs | Medium (a11y) |
| 9 | `role="alert"` missing on server error messages | All form dialogs | Medium (a11y) |
| 10 | Description inline editing not implemented | `TaskDetailModal.tsx` | Medium |
| 11 | Agent name not a clickable link | `TaskDetailModal.tsx` | Low |
| 12 | Delete Subpage cascade count not shown | `DeleteSubpageDialog.tsx` | Medium |
| 13 | Branch auto-detection not implemented | New tRPC procedure + `RegisterProjectDialog.tsx` | Low (nice-to-have) |
| 14 | Shift+click tooltip not implemented | All destructive buttons | Low |
| 15 | First-use confirmation hint not implemented | Shared confirm helper | Low |
| 16 | `autoFocus` on safe button in confirmation dialogs | `DeleteSubpageDialog.tsx` | Low |
| 17 | `max-h-[85vh]` + internal scroll not implemented | All dialog components | Low |
| 18 | ProjectPicker needs `max-h-[200px] overflow-y-auto` | `ProjectPicker.tsx` | Medium |

View File

@@ -9,12 +9,21 @@
| Aspect | v1 | v2 |
|--------|----|----|
| Batch dispatch | None -- individual task/phase queue buttons only | "Queue All Pending" button at top of execution view |
| Batch dispatch | None -- individual task/phase queue buttons only | "Queue All Pending" button with confirmation popover showing which tasks will be queued. Shift+click to skip confirmation. |
| Dispatch feedback | Instant status change, no transition | 3-frame animation: `[play]` -> `[spinner] Queuing...` -> `[QUEUED]` + toast |
| Task overflow | All tasks rendered regardless of count | First 6 tasks shown, then "Show N more" expandable link |
| Status legend | None | Legend row at bottom: `* Active ● Done ? Pending ! Error ○ Blocked` |
| Stop feedback | No visual feedback | 3-frame animation: `[square]` -> `[spinner] Stopping...` -> `[STOPPED]` + toast |
| Task overflow | All tasks rendered regardless of count | First 6 tasks shown, then "Show N more" with max-h-[320px] scrollable area when expanded |
| Status legend | None | Collapsible legend in header area (always visible, not buried at bottom) |
| Empty state | Shared with Plan tab empty state | Dedicated message: "No tasks to execute. Create tasks in the Plan tab." |
| All done state | No special treatment | Success message with green checkmark |
| All done state | No special treatment | Completion summary: task count, elapsed time, agent count, per-phase breakdown |
| Agent activity | None | Live activity indicator on running tasks: agent name + current action + elapsed time |
| Failed task retry | None | `[retry]` button on failed tasks, re-dispatches with same agent/context when possible |
| Dependency viz | Column arrows only | Column arrows + task-level dependency lines with status-colored paths + critical path highlighting |
| Pipeline overflow | No handling | Horizontal scroll + minimap toggle + zoom control (50%-150%) for dense pipelines with 20+ tasks |
| Vertical overflow | No handling | Sticky header row (legend + controls) when 4+ phases stack vertically |
| Partial failure | No special treatment | Distinct "finished with errors" summary with batch retry CTA |
| Task focus indicator | None | `ring-2 ring-primary/50` on keyboard-focused task for vim-style navigation |
| Stale agent detection | None | Activity line turns amber after 60s of no output: "last seen 2m ago" |
---
@@ -22,16 +31,18 @@
```
+=============================================================================+
| Execution [ Queue All Pending ] |
| |
| Execution ● Done * Active ? Pending ! Error ○ Blocked [100%] [map] [Queue All Pend.] |
| ^^^^^ ^^^^^ <- sticky header |
| Phase: OAuth Implementation |
| +-------------------+ +-------------------+ +-------------------+ |
| | Level 0 | | Level 1 | | Level 2 | |
| | | | | | | |
| | * Set up routes |---->| * PKCE flow |---->| Token refresh | |
| | [DONE] | | [RUNNING] | | [PENDING] [play]| |
| | | | | | | |
| | * DB schema |---->| Provider adaptr | | | |
| | completed by | | blue-fox editing | | | |
| | blue-fox 2m 14s | | src/auth/pkce.ts | | | |
| | | | 3m 22s | | | |
| | * DB schema |-,-->| Provider adaptr | | | |
| | [DONE] | | [BLOCKED] | | | |
| +-------------------+ +-------------------+ +-------------------+ |
| |
@@ -44,23 +55,63 @@
| | Expiry handling | |
| | [PENDING] [play]| |
| | + Show 4 more | |
| | (all pending) | |
| +-------------------+ |
| |
| ● Done * Active ? Pending ! Error ○ Blocked |
+=============================================================================+
```
- `[ Queue All Pending ]` -- dispatches all tasks with `status === 'pending'` that have no unresolved dependencies
- `[ Queue All Pending ]` -- dispatches all tasks with `status === 'pending'` that have no unresolved dependencies. **Shows confirmation popover** listing which tasks will be queued (see Queue All Pending section below).
- Pipeline columns computed via `groupPhasesByDependencyLevel()` topological sort
- Horizontal scroll when columns exceed viewport width
- Horizontal scroll when columns exceed viewport width. Columns are `w-64` (256px) each. At 5+ levels the container becomes scrollable.
- Vertical scroll: when 4+ phases stack vertically, the pipeline container gets `overflow-y-auto` with the header row (legend + Queue All Pending) sticky at top. Without this, the controls scroll out of view and the user loses access to batch actions while browsing deep pipelines.
- **Zoom control**: `Cmd+scroll` (Mac) / `Ctrl+scroll` (Win) adjusts pipeline zoom from 50% to 150%. Default 100%. Current zoom level shown as `[75%]` chip next to minimap toggle, click to reset to 100%. Zoom applies a CSS `transform: scale()` on the pipeline container with `transform-origin: top left`. At 50% zoom a 10-level pipeline fits on a single screen. Zoom level persisted in `localStorage` key `cw-execution-zoom`.
- Minimap toggle `[map]` in the header (right of Queue All Pending) when pipeline exceeds viewport -- renders a thumbnail overview with a viewport indicator the user can drag to scroll. Hidden when pipeline fits in viewport.
- `---->` connector arrows between dependency levels
- `[play]` button visible on hover for `pending` tasks only (not `blocked` or `running`)
- Status legend row pinned to bottom of execution view
- `[square]` stop button visible on `running` tasks -- see Task Stop Animation section below
- Status legend rendered as a collapsible chip row in the header area, not at the bottom (see Status Legend section)
- **Agent activity line**: running tasks show a live activity indicator beneath the task name (see Agent Activity Indicator section)
---
## Queue All Pending Button
### Confirmation Popover
Clicking "Queue All Pending" opens a popover listing exactly which tasks will be dispatched. This prevents accidentally queuing tasks the user intended to hold back.
```
+-------------------------+
| [play] Queue All Pending| default state
+-------------------------+
|
v
+-------------------------------------------+
| Queue 5 pending tasks? |
| |
| [v] Set up OAuth routes (Phase 1) |
| [v] Token refresh (Phase 1) |
| [v] Rotation logic (Phase 2) |
| [v] Expiry handling (Phase 2) |
| [v] Rate limiting (Phase 2) |
| |
| [Select All] [Select None] |
| |
| [ Cancel ] [Queue N Selected] |
+-------------------------------------------+
```
- Popover: `w-96` (wider than w-80 to accommodate phase names + status), max-height `320px` with internal scroll for long lists
- Each row: checkbox + task name + phase name in `text-muted-foreground`
- All checkboxes default to checked
- `[Queue N Selected]` updates its count live as checkboxes change. Disabled when 0 selected.
- Keyboard: `Escape` dismisses, `Enter` confirms
- **Shift+click** on the button bypasses the popover and queues all immediately (consistent with the app's Shift+click-to-skip-confirmation pattern)
- **Agent availability warning**: if selected tasks exceed available agent count, show a yellow info line below the list: `"5 tasks selected but only 3 agents available -- 2 tasks will wait in queue"`. This prevents the user from thinking all 5 will start immediately.
### Button States
```
+-------------------------+
| [play] Queue All Pending| default state
@@ -77,7 +128,7 @@
- Outline variant button, positioned top-right of execution header
- Disabled when no eligible pending tasks exist
- Calls `trpc.queueAllPending.useMutation({ initiativeId })`
- Calls `trpc.queueAllPending.useMutation({ initiativeId, taskIds })` with selected task IDs
- Toast notification: "N tasks queued"
---
@@ -123,6 +174,90 @@ Individual task dispatch transitions through three visual states:
---
## Task Stop Animation (3-frame)
Stopping a running task mirrors the dispatch animation in reverse:
### Frame 1: Before (running)
```
+-------------------------------------------+
| PKCE flow * [RUNNING] [square] |
| blue-fox-7 editing src/auth/pkce.ts |
+-------------------------------------------+
```
- `[square]` (Lucide `Square`) stop icon visible on hover for running tasks
- Agent activity line visible below task name
### Frame 2: During (stopping)
```
+-------------------------------------------+
| PKCE flow [spinner] Stopping... |
+-------------------------------------------+
```
- `[square]` replaced by `Loader2` with `animate-spin`
- "Stopping..." text replaces status badge
- Row background: subtle `bg-destructive/5` pulse
- Agent activity line hidden
### Frame 3: After (stopped)
```
+-------------------------------------------+
| PKCE flow [STOPPED] |
+-------------------------------------------+
```
- Status badge transitions to `[STOPPED]` (neutral tint)
- Toast: "Task stopped"
- After 1s settle, a `[play]` button reappears on hover -- a stopped task is re-queueable. This is important: stopping a task is NOT the same as failing. The user intentionally stopped it, so they should be able to restart it without ceremony.
- **Stop confirmation**: Stopping a running task that has been active for >30 seconds shows a `window.confirm("Stop task? The agent has been working for 3m 22s.")` dialog. Shift+click bypasses. Tasks active for <30s stop immediately (likely a misclick recovery, no confirmation needed).
---
## Agent Activity Indicator
Running tasks display a live activity line beneath the task name, showing what the assigned agent is currently doing.
### Anatomy
```
+-------------------------------------------+
| PKCE flow * [RUNNING] [square] |
| blue-fox-7 editing src/auth/pkce.ts |
+-------------------------------------------+
```
- Activity line: `text-[11px] text-muted-foreground font-mono truncate`
- Format: `<agent-name> <verb> <target>` -- e.g., "blue-fox-7 editing src/auth/pkce.ts", "blue-fox-7 reading src/db/schema.ts"
- Sourced from `onAgentOutput` subscription -- latest tool_use event parsed for file path
- When no activity data yet: show `<agent-name> working...` with a subtle pulse
- Activity line is ONLY shown for tasks with `status === 'in_progress'` and an assigned agent
- Elapsed time badge on the right: `3m 22s` in `text-[11px] text-muted-foreground font-mono`, updates every second
### Activity line states
```
blue-fox-7 editing src/auth/pkce.ts 3m 22s <- active tool use
blue-fox-7 thinking... 0m 45s <- between tool uses (italic)
blue-fox-7 working... 0m 02s <- no output yet (pulse animation)
```
### Stale activity fallback
If the `onAgentOutput` subscription has not emitted for a task's agent in >60 seconds, the activity line switches to:
```
blue-fox-7 last seen 2m ago 5m 14s <- stale, text-status-warning-fg
```
This matters because a "stuck" agent looks identical to an actively working one without staleness detection. The `text-status-warning-fg` (amber) color is a soft signal that something might need attention, without being as aggressive as an error state.
---
## Task Overflow in Phase Cards
### Collapsed (default when > 6 tasks)
@@ -137,13 +272,14 @@ Individual task dispatch transitions through three visual states:
| Revocation logic [PENDING] |
| Audit logging [PENDING] |
| Rate limiting [PENDING] |
| + Show 4 more |
| + Show 4 more (all pending) |
+-----------------------------------------+
```
- First 6 tasks always visible
- First 6 tasks always visible, sorted by status priority: running > failed > pending > blocked > done. This ensures the most actionable tasks are always visible without expansion.
- `+ Show N more` is a text button at bottom of card, `text-primary text-xs`
- Count dynamically reflects hidden task count
- **Status summary in overflow indicator**: when hidden tasks include non-done statuses, the overflow button shows a summary: `+ Show 4 more (1 failed, 2 running)`. If all hidden tasks are done: `+ Show 4 more (all completed)`. This prevents a critical failure from hiding behind a collapsed card.
### Expanded (after clicking "Show N more")
@@ -157,6 +293,7 @@ Individual task dispatch transitions through three visual states:
| Revocation logic [PENDING] |
| Audit logging [PENDING] |
| Rate limiting [PENDING] |
| .........................................| <- scrollable area (max-h-[320px])
| Key rotation [PENDING] |
| Token introspection [PENDING] |
| Blacklist service [PENDING] |
@@ -165,8 +302,9 @@ Individual task dispatch transitions through three visual states:
+-----------------------------------------+
```
- All tasks rendered
- `- Show less` collapses back to 6
- Expanded task list has `max-h-[320px] overflow-y-auto` to prevent cards from growing unbounded
- Scroll indicator: subtle gradient fade at bottom edge when more content below (`bg-gradient-to-t from-card`)
- `- Show less` collapses back to 6 and scrolls back to top
- Expansion state is per-phase-card, resets on tab switch
---
@@ -178,9 +316,13 @@ Individual task dispatch transitions through three visual states:
| * Phase 2: OAuth Flow [READY] [play] | StatusDot + name + badge + queue btn
|-----------------------------------------|
| [play] Set up OAuth routes [DONE] | [play] on hover for pending only
| completed by blue-fox-7 2m 14s | completion attribution line
| [play] Implement PKCE flow [RUNNING] | running tasks: Loader2 icon, no play
| blue-fox-7 editing pkce.ts 3m 22s | live agent activity line
| [ .. ] Google provider [BLOCKED] | blocked tasks: Ban icon, no play
| [play] Microsoft provider [PENDING] |
| [retry] Token refresh [FAILED] | failed tasks: retry icon
| Error: timeout after 5m | error summary line
+-----------------------------------------+
```
@@ -189,10 +331,26 @@ Individual task dispatch transitions through three visual states:
- Clicking a task row opens `<TaskModal>` (via `ExecutionContext`)
- Task sort order: `sortByPriorityAndQueueTime()` from shared package
### Failed task retry
- Failed tasks show a `[retry]` button (Lucide `RotateCcw`, `h-3 w-3`) instead of `[play]`
- `[retry]` re-dispatches the task with the **same agent and context** (if the agent is still available), otherwise falls back to normal dispatch
- Error summary line beneath the task name: first line of the error message, `text-[11px] text-status-error-fg truncate`
- Retry calls `trpc.retryTask.useMutation({ taskId })` -- server decides whether to reuse the agent session (resume) or start fresh
### Completed task attribution
- Completed tasks show a subtle attribution line: `text-[11px] text-muted-foreground`
- Format: `completed by <agent-name>` + elapsed time
- Only visible when the phase card is not in overflow (hidden tasks omit attribution)
- **Right-click context menu** on any task row: `Open in TaskModal` / `View Agent Output` / `Copy Task ID`. The context menu is a lightweight escape hatch for power users who want to inspect without the full modal flow.
---
## Connector Arrows
### Phase-level arrows (between columns)
```
+--------+ +--------+
| Phase | ------[>]-----> | Phase |
@@ -205,6 +363,31 @@ Individual task dispatch transitions through three visual states:
- Arrow color: `text-border` (muted, follows theme)
- Multiple phases in same level stack vertically
### Task-level dependency lines (within and across phases)
```
+-------------------+ +-------------------+
| Level 0 | | Level 1 |
| | | |
| * Set up routes |---->| * PKCE flow |
| [DONE] | ,-->| [RUNNING] |
| | | | |
| * DB schema |-' | Provider adaptr |
| [DONE] | | [BLOCKED] |
+-------------------+ +-------------------+
```
- Thin SVG paths (`stroke-width: 1`) connect specific tasks that have dependency relationships
- Lines use the status color of the **upstream** (dependency) task:
- Upstream completed: `text-status-success-dot` (green) -- dependency satisfied
- Upstream in_progress: `text-status-active-dot` (blue) -- dependency in flight
- Upstream pending/blocked: `text-border` (muted) -- dependency not yet started
- On hover over a task, its dependency lines highlight to `text-primary` (indigo) and non-related lines dim to `opacity-20`
- **Default state at scale (>15 dependency lines)**: all lines render at `opacity-10` until a task is hovered. This prevents the spaghetti problem. Below 15 lines, render at `opacity-40` (readable but not overwhelming).
- Lines are drawn as bezier curves to avoid overlapping with cards. Use `d3-shape` or manual SVG path computation.
- **Critical path highlighting**: when at least one task is running, the critical path (longest chain of unsatisfied dependencies leading to the final task) is highlighted with a `stroke-dasharray: 4 2` dashed pattern in `text-primary/50`. This answers the question "what is blocking completion?" at a glance.
- When minimap is visible, dependency lines are also rendered in the minimap (thinner, 0.5px)
---
## Empty State (no tasks to execute)
@@ -238,45 +421,142 @@ Individual task dispatch transitions through three visual states:
```
+=============================================================================+
| Execution |
| Execution ● Done * Active ? Pending ! Error ○ Blocked |
| |
| +---------------------------------------------------------------------+ |
| | | |
| | [check-circle icon] | |
| | All tasks completed | |
| | | |
| | Every task across all phases has been executed. | |
| | 7/7 tasks completed across 3 phases | |
| | Total elapsed: 47 minutes . 4 agents used . 0 retries | |
| | | |
| | [ View in Review Tab ] | |
| | +-------------------------------------------+ | |
| | | Phase 1: Auth Setup 3/3 12m | | |
| | | Phase 2: OAuth Flow 2/2 28m | | |
| | | Phase 3: Token Mgmt 2/2 7m | | |
| | +-------------------------------------------+ | |
| | | |
| | [ View in Review Tab ] [ Re-run All ] | |
| | | |
| +---------------------------------------------------------------------+ |
| |
| ● Done * Active ? Pending ! Error ○ Blocked |
+=============================================================================+
```
- `[check-circle icon]` -- Lucide `CheckCircle2`, 48px, `text-green-500`
- `[check-circle icon]` -- Lucide `CheckCircle2`, 48px, `text-status-success-dot`
- "All tasks completed" -- `text-lg font-medium`
- Summary line: `text-sm text-muted-foreground` -- shows total task count, phase count, retry count
- Elapsed time: computed from first task dispatch to last task completion
- Agent count: distinct agents that worked on tasks in this initiative
- Retry count: total number of task retries across the run. Zero retries = clean run. High retry count signals flaky task definitions or unstable agents.
- Per-phase breakdown: compact table showing phase name, task completion fraction, elapsed time
- `text-xs font-mono`, `bg-muted/50 rounded-md px-3 py-2`
- `[ View in Review Tab ]` -- outline button, navigates to `?tab=review`
- `[ Re-run All ]` -- outline button, resets all tasks to `pending` and navigates to the same execution tab. `window.confirm("Re-run all 7 tasks? This will reset their status.")`. Shift+click bypasses. This is useful when the user has made upstream changes (e.g., updated prompts, changed code) and wants to verify everything still works.
- Shown when: all tasks across all phases have `status === 'completed'`
- Legend row still visible at bottom
### Partial completion (mixed done + failed)
When all tasks have resolved (none pending/running/queued) but some failed:
```
+=============================================================================+
| Execution ● Done * Active ? Pending ! Error ○ Blocked |
| |
| +---------------------------------------------------------------------+ |
| | | |
| | [alert-triangle icon] | |
| | Execution finished with errors | |
| | | |
| | 5/7 tasks completed . 2 failed . 47 minutes . 4 agents | |
| | | |
| | +-------------------------------------------+ | |
| | | Phase 1: Auth Setup 3/3 12m | | |
| | | Phase 2: OAuth Flow 1/2 28m !1 | | |
| | | Phase 3: Token Mgmt 1/2 7m !1 | | |
| | +-------------------------------------------+ | |
| | | |
| | [ Retry 2 Failed ] [ View in Review Tab ] | |
| | | |
| +---------------------------------------------------------------------+ |
| |
+=============================================================================+
```
- `[alert-triangle icon]` -- Lucide `AlertTriangle`, 48px, `text-status-error-dot`
- `!1` in the phase breakdown indicates failed task count per phase
- `[ Retry 2 Failed ]` -- primary button, retries all failed tasks. Confirmation popover lists which tasks will be retried (same pattern as Queue All Pending).
- This state is distinct from "all done" because the user needs to take action. The primary CTA is retry, not review.
---
## Status Legend Row
## Status Legend
Positioned in the header area (not at the bottom) so it is always visible without scrolling to the pipeline terminus.
### Default (expanded)
```
● Done * Active ? Pending ! Error ○ Blocked
+=============================================================================+
| Execution ● Done * Active ? Pending ! Error ○ Blocked [100%] [map] [Queue All..] |
| |
```
- Fixed at bottom of execution view (below pipeline graph)
- Each item: colored dot/symbol + label
- `●` -- `text-green-500` (CheckCircle2)
- `*` -- `text-blue-500` (Loader2, no spin in legend)
- `?` -- `text-muted-foreground` (Clock)
- `!` -- `text-red-500` (AlertTriangle)
- `○` -- `text-red-500/50` (Ban)
- `text-xs text-muted-foreground`, `flex gap-4 justify-center`
- Inline with the "Execution" heading, before the Queue All Pending button
- Each item: colored dot/icon + label, `text-xs text-muted-foreground`, `flex gap-3 items-center`
- `●` -- `text-status-success-dot` (CheckCircle2)
- `*` -- `text-status-active-dot` (Loader2, no spin in legend)
- `?` -- `text-status-neutral-dot` (Clock)
- `!` -- `text-status-error-dot` (AlertTriangle)
- `○` -- `text-status-urgent-dot` (Ban)
- Collapsible: small `[?]` toggle to hide/show the legend. Preference persisted in `localStorage` key `cw-execution-legend`.
- On narrow viewports (<768px), legend collapses to icon-only (no labels) to save horizontal space.
---
## Pipeline Minimap
When the pipeline exceeds the viewport width (typically 5+ dependency levels at `w-64` each), a minimap provides spatial orientation.
### Minimap Toggle
```
Execution ● Done * Active ... [75%] [map] [Queue All Pending]
^^^^^ ^^^^^
zoom toggle minimap visibility
(click to reset 100%)
```
- `[map]` button (Lucide `Map`): `h-4 w-4`, outline variant, toggles minimap panel
- Only rendered when pipeline width exceeds container width
- State persisted in `localStorage` key `cw-execution-minimap`
### Minimap Panel (when visible)
```
+=============================================================================+
| Execution ● Done * Active ? Pending ! Error ○ Blocked [75%] [map] [Queue All] |
| +-----------------------------------------------------------------------+ |
| | [====] [ ] [ ] [ ] [ ] [ ] [ ] | |
| | [====] [ ] [ ] | |
| +-----------------------------------------------------------------------+ |
| ^--- draggable viewport indicator |
| |
| Phase: OAuth Implementation |
| ... |
```
- Minimap: `h-16`, full-width, `bg-muted/30 border-b border-border rounded-sm`
- Phase cards rendered as tiny rectangles, color-coded by aggregate phase status
- All tasks done: `bg-status-success-dot`
- Any task running: `bg-status-active-dot`
- Any task failed: `bg-status-error-dot`
- Otherwise: `bg-border`
- Viewport indicator: semi-transparent `bg-primary/20 border border-primary` rectangle
- Drag the viewport indicator to scroll the pipeline horizontally
- Click anywhere in the minimap to jump to that position
- Minimap also renders task-level dependency lines (0.5px stroke)
---
@@ -297,6 +577,28 @@ Individual task dispatch transitions through three visual states:
---
## Keyboard Shortcuts
| Key | Action |
|-----|--------|
| `q` | Focus "Queue All Pending" button |
| `j` / `k` | Navigate between tasks (down/up) in the focused phase card |
| `h` / `l` | Navigate between dependency levels (left/right) |
| `Enter` | Open TaskModal for focused task |
| `Space` | Context-sensitive: queue (if pending), stop (if running), retry (if failed). The action matches what the visible button would do. |
| `r` | Retry focused failed task (same as `Space` on failed, explicit shortcut for muscle memory) |
| `m` | Toggle minimap |
| `?` | Toggle status legend |
| `+` / `-` | Zoom in / out (10% increments) |
| `0` | Reset zoom to 100% |
| `Escape` | Clear task focus (deselect current task, remove dependency line highlighting) |
- Shortcuts disabled when any input/textarea is focused or a modal is open
- Requires `tabIndex={0}` on the pipeline container to receive keyboard events
- **Focus indicator**: the currently focused task has a `ring-2 ring-primary/50 rounded-md` outline. Without a visible focus indicator, keyboard navigation is unusable -- the user cannot tell which task `Space` or `Enter` will act on.
---
## Source
- `packages/web/src/components/pipeline/PipelineTab.tsx`
@@ -307,3 +609,71 @@ Individual task dispatch transitions through three visual states:
- `packages/web/src/components/execution/ExecutionContext.tsx`
- `packages/web/src/components/execution/TaskModal.tsx`
- `packages/web/src/components/execution/PlanSection.tsx`
- `packages/web/src/components/pipeline/PipelineMinimap.tsx` (proposed)
- `packages/web/src/components/pipeline/TaskDependencyLines.tsx` (proposed)
- `packages/web/src/components/pipeline/AgentActivityLine.tsx` (proposed)
- `packages/web/src/components/pipeline/PipelineZoomControl.tsx` (proposed)
- `packages/web/src/components/pipeline/CompletionSummary.tsx` (proposed)
- `packages/web/src/hooks/usePipelineKeyboard.ts` (proposed — extracts keyboard handling from PipelineTab)
---
## Design Review Notes
Critique of the v2 spec, what works, what was added, and what is still weak.
### What the v2 spec already got right
The v1-to-v2 changelog is strong. The confirmation popover on Queue All Pending, the 3-frame dispatch/stop animations, the minimap, the agent activity indicator, the retry mechanism, the overflow scrolling, the header-positioned legend, the completion summary, and the task-level dependency lines -- these are all real problems that were identified and addressed. The keyboard shortcuts are well-chosen (vim-style hjkl + semantic keys). The spec is implementable as written; it gives concrete Tailwind classes, component names, tRPC mutations, and localStorage keys.
### 1. Pipeline scalability: zoom was missing
The minimap handles horizontal orientation, but a 20-task, 5-level pipeline is still physically too wide to comprehend at 100% zoom. You can scroll and you can see the minimap, but you cannot get a gestalt view of the whole pipeline. Added zoom control (50%-150%, Cmd+scroll or +/- keys) so the user can zoom out for the big picture and zoom in for task details. This is standard for any node-graph editor (Figma, Miro, n8n). Without zoom, the minimap is doing double duty as both orientation and overview, and it is too small (h-16) to serve as a real overview.
Also added: vertical overflow handling. The spec addressed horizontal overflow but said nothing about the vertical axis. With 4+ phases stacking vertically, the header (legend, Queue All Pending, minimap toggle) scrolls out of view. Fixed with a sticky header row.
### 2. Queue All Pending: agent availability blind spot
The confirmation popover is good, but it is missing a critical piece of information: how many agents are available to actually run the queued tasks. If you queue 8 tasks but only have 3 agents, 5 tasks sit in the queue doing nothing. The user might think something is broken. Added an agent availability warning line in the popover that shows the mismatch. This is not a blocking concern (tasks will wait in queue as expected) but it is a situational awareness gap.
### 3. Stop animation: what happens next?
The 3-frame stop animation was well-specified but the spec ended at `[STOPPED]` and said "No [play] or [square] buttons rendered until status resolves to a re-queueable state." That is vague. Re-queueable state is... what? When? Added explicit behavior: after 1s settle, `[play]` reappears on hover because a stopped task IS re-queueable immediately.
Also added stop confirmation for long-running tasks (>30s). Stopping a task that has been churning for 5 minutes is a significant action -- the agent has accumulated context and work product. A misclick here wastes real money. The Shift+click bypass keeps the power-user path fast.
### 4. Overflow "Show N more": hidden failures
The `+ Show 4 more` indicator tells you there are more tasks, but it does not tell you whether any of those hidden tasks are on fire. If 2 of the hidden tasks have failed, the user has no idea unless they expand. Added status summary in the overflow indicator: `+ Show 4 more (1 failed, 2 running)`. This is the difference between "collapsed for convenience" and "collapsed and hiding problems."
Also added sort-by-status-priority so that running and failed tasks bubble to the top of the visible 6. Without this, a freshly failed task could be task #8 and invisible.
### 5. Agent activity: stale detection missing
The activity indicator shows what the agent is doing RIGHT NOW. But what if the agent stopped emitting output 3 minutes ago? The activity line still shows the last action as if it is current. Added a staleness fallback: if no output in >60s, the line switches to `"blue-fox-7 last seen 2m ago"` with warning-colored text. This is the difference between "agent is editing a file" and "agent SAID it was editing a file 3 minutes ago and has gone silent."
### 6. Completion state: no partial failure handling
The spec had an "all done" state but no "mostly done with some failures" state. In practice, getting 7/7 clean completions on the first run is the exception, not the norm. Added a distinct partial-completion wireframe with `AlertTriangle`, failed task count per phase, and a `[ Retry 2 Failed ]` primary CTA. Without this, the user sees... what? The pipeline in its normal state with some green and some red cards, no summary, no batch retry. That is not a completion state, that is just the default state with different colors.
Also added retry count to the clean completion summary and a `[ Re-run All ]` button for re-executing after upstream changes.
### 7. Dependency lines: spaghetti at scale
The spec says "On hover, dependency lines highlight and non-related lines dim to opacity-20." Good for hover state. But what is the DEFAULT state when nobody is hovering? With 20+ tasks and 30+ dependency lines, the default is visual noise. Added a scale-aware default: >15 lines renders at opacity-10 (almost invisible until hover), <15 lines at opacity-40 (readable). Also added critical path highlighting -- a dashed indigo line showing the longest unsatisfied dependency chain. This is the single most important piece of information in the entire pipeline: "what is blocking completion?"
### 8. Keyboard navigation: invisible focus
The keyboard shortcuts assume the user can tell which task is focused. The spec never defined a focus indicator. Added `ring-2 ring-primary/50` on the focused task. Without this, pressing `Space` is a gamble -- the user does not know what they are about to queue or stop. Also added `Escape` to clear focus and zoom shortcuts (`+`/`-`/`0`).
### 9. Task context actions
The spec requires opening a TaskModal for any task inspection. Added a right-click context menu as a lightweight alternative: `Open in TaskModal` / `View Agent Output` / `Copy Task ID`. Power users managing 10+ tasks do not want to open a modal for every inspection.
### Still weak / deferred
- **Cost tracking**: The system burns real API credits. The completion summary shows elapsed time and agent count but NOT cost. This is because cost data is not currently available in the data model (no token/cost tracking per task). When cost tracking is added to the agent output system, the completion summary should show `$X.XX total cost` and per-phase cost breakdown. Flag this for the next iteration.
- **Multi-agent per task**: The spec assumes 1 agent per task. Decomposed parent tasks can have multiple child tasks each with their own agent, but the parent task card does not show aggregate child status. This is partially handled by the task hierarchy (children are separate rows), but a parent task with 8 children is going to blow out the phase card. Consider a collapsible sub-tree within the phase card for decomposed tasks.
- **Pipeline diff**: When the pipeline changes mid-execution (e.g., a plan agent adds new phases), the user has no notification. The pipeline just quietly grows. Consider a toast or activity feed event: "Pipeline updated: 2 new phases added by plan-fox-3."
- **Error categorization**: All failures look the same. A timeout failure, an API rate limit, and a code compilation error are fundamentally different problems with different remediation paths. The error summary line shows the first line of the error, but richer error categorization (with suggested actions) would make the retry decision easier.
- **Accessibility**: The dependency lines are pure SVG with color-coded status. Color-blind users cannot distinguish green (satisfied) from red (failed) lines. Add pattern differentiation (solid = satisfied, dashed = in-flight, dotted = waiting) in addition to color. The existing `stroke-dasharray` on critical path is a start; extend it to all dependency line states.

View File

@@ -11,7 +11,9 @@
|--------|----|----|
| List error state | None | `[AlertCircle]` + "Failed to load conversations" + `[Retry]` |
| Detail error state | None | `[AlertCircle]` + "Failed to load conversation" + `[Retry]` |
| Action buttons | 3 buttons: Cancel, Dismiss, Send Answers | 2 buttons: `[ Stop Agent ]` (destructive) + `[ Send Answers ]` (primary) |
| Action buttons | 3 buttons: Cancel, Dismiss, Send Answers | 2 buttons: `[ Stop Agent ]` (destructive, LEFT-isolated) + `[ Send Answers ]` (primary, RIGHT) |
| Conversation states | No visual distinction | Orange dot = pending, Grey dot = answered, filterable tabs |
| Batch answers | N/A | "Apply to similar" option when multiple agents ask same question type |
| Submit feedback | None | Button shows `[spinner] Sending...` during mutation |
| Submit error | Toast only | Inline red text below buttons: "Failed to send. Try again." |
| Empty state | Basic text | Centered empty state with description |
@@ -27,66 +29,130 @@
| |
| Inbox (2) [ Refresh ] |
| |
| Conversations | Question from blue-fox-7 |
| --------------------------+ 400px |
| [Pending (2)] [Answered] | Question from blue-fox-7 [<>] |
| --------------------------+ min 400px, resizable |
| +------------------------+| |
| | ? blue-fox-7 2m || "Should I use PKCE or implicit flow |
| | Auth System Overhaul || for the browser client? The spec |
| +------------------------+|| recommends PKCE but the existing |
| | ? green-bear-1 5m || code uses implicit." |
| | API Redesign || |
| +------------------------+| Your Answer |
| | ! blue-fox-7 2m || Task: Implement OAuth PKCE flow |
| | Auth System Overhaul || Phase: Authentication . 2 minutes ago |
| | BLOCKING || |
| +------------------------+| "Should I use PKCE or implicit flow |
| | ? green-bear-1 5m || for the browser client? The spec |
| | API Redesign || recommends PKCE but the existing |
| +------------------------+| code uses implicit." |
| | |
| | [v] Previous Q&A with blue-fox-7 (1) |
| | |
| | Your Answer [Snooze v] |
| | +--------------------------------------------+|
| | | Use PKCE. We're deprecating implicit ||
| | | flow per OAuth 2.1 security BCP. ||
| | | ||
| | +--------------------------------------------+|
| | Markdown supported. `code`, **bold**, ``` |
| | |
| | [ Stop Agent ] [ Send Answers ] |
| | [ Stop Agent ] [ Send Answers ⌘⏎ ] |
+=============================================================================+
```
**Layout notes:**
- Filter tabs (`Pending` / `Answered`) at top of conversation list allow switching views
- `!` (urgent dot, purple) = conversation linked to a blocked task; `?` (warning dot, orange) = standard pending
- `BLOCKING` badge uses `status-urgent` tokens — surfaces task-blocking conversations first
- Detail panel minimum 400px, draggable resize handle `[<>]`, or click to toggle full-width
- `[Snooze v]` dropdown: 15m, 1h, 4h, Tomorrow — removes from pending list temporarily
- `[ Stop Agent ]` is LEFT-aligned, `[ Send Answers ]` is RIGHT-aligned — maximum spatial separation
- Previous Q&A collapsible section shows conversation history with this agent
- Textarea hint line confirms markdown support
- `Cmd+Enter` shortcut shown on Send button; `R` global shortcut focuses textarea
---
## Conversation Card (in left list)
### Waiting for response
### Blocking (urgent — task is blocked waiting for this answer)
```
+-------------------------------+
| ? blue-fox-7 2m | <-- status dot (orange) + name + time
| ! blue-fox-7 2m | <-- status dot (purple, pulsing) + name + time
| Auth System Overhaul | <-- initiative name
| BLOCKING | <-- badge: bg-status-urgent-bg text-status-urgent-fg
+-------------------------------+
```
### Answered / completed
### Waiting for response (standard)
```
+-------------------------------+
| - green-bear-1 1h | <-- status dot (grey) + name + time
| ? green-bear-1 5m | <-- status dot (orange) + name + time
| API Redesign | <-- initiative name
+-------------------------------+
```
- Orange `?` dot = `waiting_for_input`
- Grey `-` dot = answered or completed
### Snoozed
```
+-------------------------------+
| z pink-owl-3 45m | <-- status dot (muted) + name + time
| Data Migration | <-- initiative name
| Snoozed until 3:00 PM | <-- snooze info, text-muted-foreground
+-------------------------------+
```
### Answered
```
+-------------------------------+
| ✓ green-bear-1 1h | <-- status dot (green) + name + time
| API Redesign | <-- initiative name, dimmed
+-------------------------------+
```
- Purple `!` dot (pulsing) = linked task is `blocked` — uses `status-urgent-dot` token. **Sorted first always.**
- Orange `?` dot = `pending` conversation, standard priority — uses `status-warning-dot` token
- Muted `z` dot = snoozed — hidden from Pending tab by default, shown in a collapsed "Snoozed (1)" section at bottom
- Green checkmark = `answered` — uses `status-success-dot` token. Visible only on Answered tab.
- Click selects conversation and shows detail panel
- Selected card gets `bg-muted` highlight
- Selected card gets `bg-accent` highlight (indigo tint, not grey)
- `Up/Down` arrow keys navigate list; `Enter` selects; `R` focuses answer textarea
- Sort order within Pending tab: blocking first, then by `createdAt` ascending (oldest unanswered first)
---
## Detail Panel (400px right)
## Detail Panel (min 400px right, resizable)
### Header
```
+----------------------------------------------+
| Question from blue-fox-7 |
| Auth System Overhaul . 2 minutes ago |
| Question from blue-fox-7 [<>] |
| Task: Implement OAuth PKCE flow |
| Phase: Authentication . 2 minutes ago |
+----------------------------------------------+
```
### Question + answer form
- `[<>]` toggle: click to expand detail panel to full width (hides conversation list). Click again to restore split. Keyboard shortcut: `F` to toggle full-width.
- Task and phase names are clickable links (navigate to initiative detail at that task/phase).
- Panel is resizable via drag handle on the left border. Min 400px, max 70% of viewport.
### Conversation History (collapsible)
```
+----------------------------------------------+
| [v] Previous Q&A with blue-fox-7 (1) |
| +------------------------------------------+|
| | Q (1h ago): "Should I store tokens in ||
| | localStorage or httpOnly cookies?" ||
| | A: "httpOnly cookies. See OWASP ||
| | session management cheatsheet." ||
| +------------------------------------------+|
+----------------------------------------------+
```
- Collapsed by default. Shows count of prior conversations with this agent (same `fromAgentId`).
- Each historical entry shows the question, your answer, and relative timestamp.
- Query: `findByAgentPair(fromAgentId, toAgentId)`**requires new repository method** (schema supports it, query doesn't exist yet).
### Question Display
```
+----------------------------------------------+
@@ -94,53 +160,122 @@
| for the browser client? The spec |
| recommends PKCE but the existing |
| code uses implicit." |
| |
| Your Answer |
+----------------------------------------------+
```
- Question text rendered in a `bg-muted rounded-lg p-4` block with `font-mono text-sm` for code-heavy questions.
- If the question contains markdown fences, render them with syntax highlighting (use the terminal token palette).
### Suggested Answers (conditional)
```
+----------------------------------------------+
| Suggested |
| +------------------------------------------+|
| | [Use approach A: PKCE flow (recommended ||
| | by spec)] [ Use ^ ] ||
| +------------------------------------------+|
| +------------------------------------------+|
| | [Use approach B: Keep implicit flow ||
| | (existing code)] [ Use ^ ] ||
| +------------------------------------------+|
+----------------------------------------------+
```
- Shown only when the agent's question contains recognizable choice patterns ("should I use A or B", "which approach", option lists).
- Parsed from the question text client-side — extracts options and presents as one-click answer buttons.
- `[ Use ^ ]` populates the answer textarea with that option (does NOT auto-send). User can edit before sending.
- If no pattern detected, this section is hidden entirely. No server-side AI inference needed.
### Answer Form
```
+----------------------------------------------+
| Your Answer [Snooze v] |
| +------------------------------------------+|
| | Use PKCE. We're deprecating implicit ||
| | flow per OAuth 2.1 security BCP. ||
| | ||
| | ```ts ||
| | // See RFC 7636 for PKCE spec ||
| | ``` ||
| +------------------------------------------+|
| Markdown supported. `code`, **bold**, ``` |
| |
| [ Stop Agent ] [ Send Answers ] |
| [ Stop Agent ] [ Send Answers ⌘⏎ ] |
+----------------------------------------------+
```
- **Textarea**: `min-h-[120px]`, auto-grows. Supports markdown with a hint line below.
Rendered preview is NOT shown (this is a response to an agent, not a document — raw markdown is fine).
Fenced code blocks (```) get `font-mono` styling in the textarea via CSS.
- **Snooze dropdown**: `[Snooze v]` next to "Your Answer" label. Options: 15 minutes, 1 hour, 4 hours, Tomorrow, Custom.
Snoozed conversations move to collapsed "Snoozed" section at bottom of Pending list.
Snooze is client-side only (localStorage timer) — no schema change needed.
When snooze expires, conversation reappears in the Pending list with a brief highlight animation.
- **Keyboard**: `R` from anywhere on the page focuses the textarea. `Cmd+Enter` (Mac) / `Ctrl+Enter` (Win) sends.
`Escape` from textarea returns focus to the conversation list.
### Batch Answer (when applicable)
```
+----------------------------------------------+
| [i] 2 other agents asked similar questions |
| [ Apply this answer to all 3 ] |
+----------------------------------------------+
```
- Shown below the answer textarea when multiple pending conversations contain similar question text.
- Similarity detection: simple keyword overlap heuristic on question text (no ML needed).
E.g., if 3 agents all ask about "PKCE vs implicit", they cluster.
- `[ Apply this answer to all 3 ]` sends the same answer to all matched conversations.
Uses a batch `answerConversation` call — **requires new tRPC batch mutation or sequential calls**.
- If no similar questions exist, this section is hidden.
### Button anatomy
- `[ Stop Agent ]` -- `variant="destructive"`, stops the asking agent via `stopAgent` mutation.
Replaces the v1 Cancel + Dismiss buttons. Confirmation via `window.confirm()`;
Shift+click bypasses confirmation.
- `[ Send Answers ]` -- `variant="default"` (primary), sends the response via `resumeAgent` mutation.
Disabled until answer textarea is non-empty.
- `[ Stop Agent ]` -- `variant="destructive" size="sm"`, **LEFT-aligned** in the button row.
Stops the asking agent via `stopAgent` mutation.
**Confirmation**: `window.confirm("Stop agent blue-fox-7? This will terminate the agent process.")`
includes the agent name in the message. Shift+click bypasses confirmation per project convention.
**Visual separation**: positioned flush-left while Send is flush-right, creating maximum distance.
Uses `outline` style (not filled) to further reduce visual weight vs. the primary Send button.
Updated: `variant="outline"` with `text-destructive border-destructive hover:bg-destructive hover:text-destructive-foreground`.
- `[ Send Answers ⌘⏎ ]` -- `variant="default"` (primary, filled indigo), **RIGHT-aligned**.
Sends the response via `answerConversation` mutation then `resumeAgent` mutation.
Disabled until answer textarea is non-empty. Shows keyboard shortcut hint `⌘⏎` in the button label.
After successful send, auto-advances to next pending conversation in the list.
---
## Submit In Progress
```
| [ Stop Agent ] [ [spinner] Sending... ] |
| [ Stop Agent ] [ [spinner] Sending... ] |
```
- `[ Send Answers ]` button shows a `Loader2` spinner + "Sending..." text
- Button is `disabled` during mutation
- `[ Stop Agent ]` is also disabled during mutation to prevent conflicting actions
- Textarea becomes `readonly` with `opacity-50` during mutation
- On success: toast "Answer sent to blue-fox-7", auto-advance to next pending conversation
---
## Submit Error
```
| |
| [ Stop Agent ] [ Send Answers ] |
| |
| Failed to send. Try again. |
+----------------------------------------------+
| |
| [ Stop Agent ] [ Send Answers ⌘⏎ ] |
| |
| Failed to send. Try again. |
+------------------------------------------------------------+
```
- Error text: `text-sm text-destructive`, right-aligned below the buttons
- Shown when `resumeAgentMutation.isError` is true
- Shown when `answerConversationMutation.isError` or `resumeAgentMutation.isError` is true
- Cleared on next submit attempt
- Textarea remains editable so the user can retry without re-typing
---
@@ -165,6 +300,7 @@
- `[inbox]` icon: `Inbox` from Lucide, `h-8 w-8 text-muted-foreground`
- Title: `text-sm font-medium`
- Description: `text-sm text-muted-foreground`
- Keyboard hint below: `text-xs text-muted-foreground` — "Press `R` to reply when a question arrives"
---
@@ -251,10 +387,86 @@ initiative name line. Shimmer animation sweeps left-to-right on a 1.5s loop.
---
## Keyboard Shortcuts
| Key | Context | Action |
|-----|---------|--------|
| `R` | Anywhere on page | Focus answer textarea for selected conversation |
| `Up` / `Down` | Conversation list focused | Navigate between conversations |
| `Enter` | Conversation list focused | Select highlighted conversation |
| `Cmd+Enter` / `Ctrl+Enter` | Answer textarea focused | Send answer |
| `Escape` | Answer textarea focused | Return focus to conversation list |
| `F` | Anywhere on page | Toggle detail panel full-width |
| `J` / `K` | Anywhere on page | Next / previous conversation (vim-style) |
| `S` | Conversation selected, textarea empty | Open snooze dropdown |
Shortcuts are disabled when a dialog (confirm, etc.) is open. Documented in a `?` tooltip in the page header.
---
## Schema / Backend Requirements
Changes needed to support v2 Inbox features:
| Feature | Requirement | Scope |
|---------|-------------|-------|
| Conversation history | `findByAgentPair(fromAgentId, toAgentId)` repository method | New query in `ConversationRepository` |
| Blocking detection | Join conversation's `taskId` to tasks table, check `status === 'blocked'` | Query enrichment in tRPC `getPendingConversations` or client-side join |
| Batch answer | Either a `batchAnswerConversations` tRPC mutation or sequential `answerConversation` calls | New mutation (preferred) or client loop |
| Snooze | Client-side only (`localStorage` timers) | No schema change |
| Suggested answers | Client-side question text parsing | No schema change |
---
## Source
- `packages/web/src/routes/inbox.tsx`
- `packages/web/src/components/InboxList.tsx`
- `packages/web/src/components/InboxDetailPanel.tsx`
- `packages/web/src/components/InboxConversationHistory.tsx` *(new)*
- `packages/web/src/components/InboxSuggestedAnswers.tsx` *(new)*
- `packages/web/src/components/InboxBatchAnswer.tsx` *(new)*
- `packages/web/src/components/MessageCard.tsx`
- `packages/web/src/components/QuestionForm.tsx`
- `packages/web/src/hooks/useInboxKeyboard.ts` *(new — keyboard shortcut handler)*
- `packages/web/src/hooks/useSnooze.ts` *(new — localStorage snooze timers)*
---
## Design Review Notes
### What's solid
The 2-button simplification from v1's 3-button layout is the right call. The error states (list, detail, submit) are thorough — most wireframes skip these. The skeleton loading state mirrors card anatomy correctly. The empty state has personality without being cute.
### What was fixed
**1. Stop Agent proximity hazard.** The original spec placed `[ Stop Agent ]` immediately left of `[ Send Answers ]`. This is a catastrophic misclick waiting to happen — you're about to click "Send" and you hit "Stop" instead, killing an agent mid-task. Fix: buttons are now flush-left and flush-right respectively, creating maximum spatial separation. Additionally, `[ Stop Agent ]` was downgraded from `variant="destructive"` (filled red) to an outline style with destructive text color. The filled primary button now has overwhelming visual dominance. The `window.confirm()` dialog now includes the agent name for clarity.
**2. No answered/pending filtering.** The original list showed both states mixed together with only a dot color difference. When you have 15+ conversations, this becomes noise. Fix: added `[Pending (2)] [Answered]` filter tabs at the top of the conversation list. Pending is the default view. Answered is an archive you visit when you need to reference past decisions.
**3. No urgency signal.** All pending conversations looked identical — whether the agent's task is blocked and the entire initiative is stalled, or it's a low-priority research question. Fix: conversations linked to blocked tasks get a purple pulsing `!` dot and a `BLOCKING` badge, using the existing `status-urgent` tokens from the theme spec. These always sort first.
**4. Detail panel too narrow for code.** 400px is fine for prose questions, but agents frequently include code snippets, file paths, and stack traces. Fix: panel is now `min 400px, resizable` with a drag handle, plus a full-width toggle (`F` key or `[<>]` button) that hides the conversation list entirely.
**5. No conversation history.** Without context, you're answering each question in isolation. The agent might have asked a related question 20 minutes ago that your current answer should be consistent with. Fix: collapsible "Previous Q&A with [agent]" section above the answer form. Requires a new `findByAgentPair()` repository method — the schema supports it, the query just doesn't exist yet.
**6. Plain text textarea.** Agents process markdown. Users think in markdown. Not supporting it means answers with code references are hard to write clearly. Fix: textarea now accepts markdown with a hint line ("Markdown supported. `code`, **bold**, ```"). No live preview — this is a reply to an agent, not a document editor. The overhead of a preview pane isn't justified here.
**7. No snooze.** Not every question is urgent. Sometimes you need to think, or you're deep in another conversation. Without snooze, the question just sits there adding visual noise to your pending list. Fix: `[Snooze v]` dropdown with preset durations. Snoozed items collapse into a "Snoozed" section at the bottom of the Pending tab. Implemented client-side only (localStorage timers) — no schema change, no migration, no backend work.
**8. No keyboard shortcuts.** For a keyboard-first tool managing 10+ agents, requiring mouse clicks to answer questions is slow. Fix: full keyboard shortcut table added. `R` to reply from anywhere, `J/K` for vim-style navigation, `Cmd+Enter` to send, `F` for full-width toggle.
**9. No suggested answers.** When an agent asks "should I use A or B?", the system could extract those options and present them as one-click buttons. Fix: client-side pattern matching on question text extracts choice options. `[ Use ^ ]` populates (not sends) the textarea. No AI inference, no API cost — just regex on common question patterns.
**10. No batch answers.** When 3 agents ask "which auth library should we use?", answering each individually is tedious. Fix: similarity detection (keyword overlap) clusters similar pending questions. "Apply this answer to all 3" button sends the same answer to all matched conversations.
### Open questions for implementation
1. **Snooze persistence across sessions.** localStorage is fine for single-session use, but if the user closes the tab and reopens, snoozed items reappear immediately (timer lost). Is this acceptable, or should snooze state be persisted server-side? Recommendation: start with localStorage, add server-side if users complain.
2. **Batch answer atomicity.** If one of the 3 batch answers fails, should the other 2 still succeed? Recommendation: yes — use sequential calls, show partial success/failure. Don't make the user redo the ones that worked.
3. **Conversation history query performance.** `findByAgentPair` will scan by `fromAgentId` index. For workspaces with hundreds of conversations, consider adding a composite index on `(fromAgentId, toAgentId)` to the schema.
4. **Blocking detection accuracy.** The current approach joins `taskId` to check `task.status === 'blocked'`. But a task could be blocked for reasons unrelated to this conversation. Consider adding a `isBlocking` field to conversations, or determining blocking status from whether the agent itself is `waiting_for_input`.

View File

@@ -1,7 +1,7 @@
# Initiative Detail (v2)
### Route: `/initiatives/$id`
### Source: `packages/web/src/routes/initiatives/$initiativeId.tsx`
### Source: `packages/web/src/routes/initiatives/$id.tsx`
---
@@ -9,9 +9,15 @@
| Aspect | v1 | v2 |
|--------|----|----|
| Header layout | Single row: back + name + all badges | Two rows: Row 1 = back + name, Row 2 = badges |
| Tab badges | None | Execution shows "3/7", Review shows "(2)" with yellow tint |
| Keyboard hints | None | Tooltip on tab hover: `1` Content, `2` Plan, `3` Execution, `4` Review |
| Header layout | Single row: back + name + all badges | Two rows: Row 1 = back + name + actions menu, Row 2 = badges |
| Initiative name | Static text | Editable on hover (pencil icon -> inline input) |
| Actions menu | Only on cards in list view (`[...]`) | `[...]` menu on Row 1: Discuss, Plan, Refine, Change Mode, Archive, Delete |
| Tab badges | None | Content `(5)`, Plan `(3)`, Execution `3/7`, Review `(2)` with yellow tint |
| Keyboard hints | None | Tooltip on tab hover + CommandPalette entries + first-visit hint |
| Activity feed | None | Collapsible ticker at bottom of tab content area |
| Tab transition | None specified | Explicitly instant (no animation) |
| Tab internal headers | Inconsistent | Consistent left=context, right=action pattern documented |
| Not found state | Same as generic error | Distinct 404 message: "Initiative not found" |
| Branch editing | Inline edit on click | Unchanged (still inline) |
| Project editing | Popover checklist | Unchanged (still popover) |
@@ -24,10 +30,10 @@
| [CW] [*Initiatives*] Agents *3 Inbox (2) Settings [cmd-k] [sun] * |
+=============================================================================+
| |
| [<] Auth System Overhaul |
| [<] Auth System Overhaul [...] |
| [ACTIVE] [REVIEW] [git] cw/auth-overhaul [P] backend, frontend |
| |
| Content Plan Execution 3/7 Review (2) |
| Content (5) Plan (3) Execution 3/7 Review (2) |
| ----------------------------------------------------------------------- |
| |
| |
@@ -43,14 +49,20 @@
## Two-Row Header Anatomy
```
Row 1: [<] Initiative Name
Row 1: [<] Initiative Name [pencil-on-hover] [...]
Row 2: [STATUS] [MODE] [git] branch [P] project1, project2
```
### Row 1
- `[<]` — Back chevron, navigates to `/initiatives`
- Initiative name — `text-2xl font-bold`
- Row 1 has no badges — clean title line
- Initiative name — `text-2xl font-bold`. **Not** inline-editable by default.
On hover, a pencil icon (`Pencil`, 14px, `text-muted-foreground`) appears
to the right of the name. Clicking it replaces the `<h1>` with an `<Input>`
(same `text-2xl font-bold` styling). Enter saves, Escape cancels. Uses
`updateInitiative` mutation. This keeps the title line clean 99% of the time
while making rename discoverable.
- `[...]` — Actions menu, right-aligned on Row 1 (see **Initiative Actions Menu** below)
- Row 1 has no badges — clean title line + actions menu
### Row 2
- Indented to align under the initiative name (not under the back chevron)
@@ -78,6 +90,17 @@ branch is auto-generated on first execution task dispatch.
`[+ Add projects]` link opens project picker popover.
### Name Editing (click pencil on hover)
```
[<] [Auth System Overhaul___________________] [v] [x] [...]
[ACTIVE] [REVIEW] [git] cw/auth-overhaul [P] backend, frontend
```
Inline text input replaces the `<h1>`. Same `text-2xl font-bold` sizing.
`[v]` saves via `updateInitiative({ id, name })`, `[x]` cancels.
Enter also saves, Escape also cancels.
### Branch Editing (click branch badge)
```
@@ -106,20 +129,28 @@ Inline text input replaces branch badge. `[v]` saves, `[x]` cancels.
## Tab Bar
```
Content Plan Execution 3/7 Review (2)
Content (5) Plan (3) Execution 3/7 Review (2)
-----------------------------------------------------------------------
^^^^^^^^^^^^^^^^^^
active tab (border-b-2 border-primary)
^^^^^^^^^^^^^^^^^^
active tab (border-b-2 border-primary)
```
### Tab badge rendering
| Tab | Badge | When |
|-----|-------|------|
| Content | None | Always |
| Plan | None | Always |
| Execution | `3/7` | Fraction of completed/total tasks. Hidden if 0 total tasks. |
| Review | `(2)` | Pending proposal count. Yellow tint. Hidden if 0 pending. |
All non-progress badges use a consistent `(N)` format. Execution is
the exception because it communicates a ratio (done/total), not a count.
| Tab | Badge | Format | When |
|-----|-------|--------|------|
| Content | `(5)` | Page count | Hidden if 0 pages (only root page exists and is empty) |
| Plan | `(3)` | Phase count | Hidden if 0 phases |
| Execution | `3/7` | Completed/total tasks (fraction) | Hidden if 0 total tasks |
| Review | `(2)` | Pending proposal count, yellow tint | Hidden if 0 pending |
**Why the fraction for Execution?** It is a progress indicator, not a simple
count. `3/7` tells you "3 done out of 7" at a glance — a count `(7)` would
not convey progress. The other tabs show cardinality, not progress, so `(N)`
is the right format for them.
### Active tab indicator
- `border-b-2 border-primary` underline on active tab
@@ -133,8 +164,8 @@ Inline text input replaces branch badge. `[v]` saves, `[x]` cancels.
-----------------------------------------------------------------------
```
When Execution has 0 tasks and Review has 0 pending proposals, badges are
completely hidden. No "0/0" or "(0)".
When all counts are zero, all badges are hidden. No `(0)` or `0/0`.
Each badge appears independently as its count becomes non-zero.
### Hover state with keyboard hint (tooltip)
@@ -156,6 +187,183 @@ Keyboard shortcuts:
- `3` — Execution
- `4` — Review
### Keyboard shortcut discoverability
Tooltips are hover-only — users who never hover will never find them.
Two additional discoverability mechanisms:
1. **CommandPalette integration**: When the user opens Cmd+K and types
"content", "plan", "execution", or "review", results include entries
like `Go to Content tab [1]` with the shortcut shown as a right-aligned
`<kbd>` badge. These appear in a "Navigation" group.
2. **First-visit hint** (one-time): On the first render of the initiative
detail page (tracked via `localStorage` key `cw-tab-hints-shown`), a
subtle muted-foreground line below the tab bar reads:
`Tip: press 1-4 to switch tabs`. Auto-dismisses after 5s or on any
tab switch. Never shown again.
---
## Initiative Actions Menu
Row 1's `[...]` button opens a `<DropdownMenu>` anchored to the right.
This mirrors the `[...]` menu on initiative cards in the list view but
adds initiative-level actions relevant in the detail context.
```
[...]
+------------------+
| Discuss |
| Plan |
| Refine |
| ────────────── |
| Change Mode | → submenu: YOLO / Review
| ────────────── |
| Archive |
| Delete | ← destructive, red text
+------------------+
```
### Actions
| Action | Behavior |
|--------|----------|
| Discuss | Spawns a `discuss` agent for this initiative |
| Plan | Spawns a `plan` agent to generate phases |
| Refine | Spawns a `refine` agent on content pages |
| Change Mode | Submenu: `YOLO` / `Review`. Calls `updateInitiativeConfig`. Active mode has a checkmark. |
| Archive | Sets status to `archived`. `window.confirm()` dialog; Shift+click bypasses. |
| Delete | Calls `deleteInitiative`. `window.confirm("Delete permanently?")`; Shift+click bypasses. Navigates to `/initiatives` on success. |
The "Change Mode" action duplicates the `[MODE]` badge toggle in Row 2.
That is intentional — the badge is for quick toggle, the menu is for
discoverability and for users who do not realize the badge is clickable.
---
## Activity Feed
A collapsible activity ticker at the bottom of the tab content area,
showing the most recent cross-cutting events for this initiative. Think
"mission control status bar" — always visible, never overwhelming.
### Collapsed (default)
```
-----------------------------------------------------------------------
[^] blue-fox-7 completed "Implement PKCE flow" . 2m ago [View All]
```
Single-line ticker showing the most recent event. `[^]` expands the feed.
`[View All]` opens a full activity log (future page, not in v2 scope — link
is a no-op stub that shows a toast "Activity log coming soon").
### Expanded (click `[^]`)
```
-----------------------------------------------------------------------
[v] Activity [View All]
-----------------------------------------------------------------------
. 2m ago blue-fox-7 completed "Implement PKCE flow" [EXECUTION]
. 5m ago Proposal submitted: "Add rate limiting" [REVIEW]
. 8m ago green-fox-3 started "Google provider" [EXECUTION]
. 12m ago Phase "OAuth Flow" marked ready [PLAN]
. 15m ago Page "Architecture" updated [CONTENT]
-----------------------------------------------------------------------
```
- Max 10 items visible, scrollable if more
- Each item has: relative timestamp, description, tab context badge
- Tab context badge links to the relevant tab (clicking `[EXECUTION]` switches to Execution tab)
- `[v]` collapses back to single line
- Feed populated from EventBus events filtered by `initiativeId`
- Persists expanded/collapsed state in `localStorage` key `cw-activity-feed-${initiativeId}`
### Event Types Shown
| Event | Display |
|-------|---------|
| `task:completed` | `{agent} completed "{task}"` |
| `task:failed` | `{agent} failed on "{task}"` — error tint |
| `task:dispatched` | `"{task}" dispatched to {agent}` |
| `agent:spawned` | `{agent} started "{task}"` |
| `agent:completed` | `{agent} finished` |
| `agent:crashed` | `{agent} crashed` — error tint |
| `proposal:created` | `Proposal submitted: "{title}"` |
| `proposal:accepted` | `Proposal accepted: "{title}"` |
| `phase:status_changed` | `Phase "{phase}" marked {status}` |
| `page:updated` | `Page "{page}" updated` |
---
## Tab Transition Behavior
Tab switches are **instant with no transition animation**. Rationale:
- Mission control aesthetic prizes immediacy over polish
- Content in each tab can be tall and complex — crossfade or slide
animations on heterogeneous heights look janky
- The `border-b-2 border-primary` underline provides sufficient
visual feedback for which tab is active
- Keyboard shortcut `1-4` switching should feel instantaneous
The only animation is the tab underline itself, which uses
`transition-colors duration-150` (already specified in active tab styling).
No `opacity`, `transform`, or `height` transitions on tab content.
---
## Tab Content Internal Header Pattern
Each tab has its own internal header area, but they should follow a
consistent layout to avoid visual jarring when switching tabs.
```
Tab Name / Context [Primary Action]
-----------------------------------------------------------------------
< tab-specific content >
```
### Per-tab headers
| Tab | Left side | Right side |
|-----|-----------|------------|
| Content | `Pages [+]` (sidebar header) | `[breadcrumb] [SaveIndicator]` (editor header) |
| Plan | `Phases [+]` (sidebar header) | `[SaveIndicator]` (phase editor) |
| Execution | `Execution` (section title) | `[Queue All Pending]` |
| Review | `Diff: <filepath> N unresolved` | `Threads [v]` (sidebar header) |
The pattern is: **left = context/navigation, right = primary action**.
Tab-specific wireframes (content-tab.md, plan-tab.md, etc.) define the
full detail. This section documents the cross-tab consistency expectation.
---
## Not Found State (404)
When `getInitiative` returns a NOT_FOUND error (initiative was deleted
or ID is bogus), show a distinct 404-flavored error instead of the
generic error state.
```
+=============================================================================+
| |
| [<] |
| |
| [AlertCircle] |
| Initiative not found |
| This initiative may have been deleted. |
| |
| [ Back to Initiatives ] |
| |
+=============================================================================+
```
Detection: check if `error.message` includes "not found" (already done
in existing code). The heading changes from "Error loading initiative"
to "Initiative not found" and the description changes accordingly.
---
## Loading State
@@ -204,6 +412,121 @@ Back chevron still functional. Error body uses `<ErrorState>` component
## Source
- `packages/web/src/routes/initiatives/$initiativeId.tsx`
- `packages/web/src/routes/initiatives/$id.tsx`
- `packages/web/src/components/InitiativeHeader.tsx`
- `packages/web/src/components/InitiativeTabBar.tsx` (proposed)
- `packages/web/src/components/InitiativeActionsMenu.tsx` (proposed)
- `packages/web/src/components/ActivityFeed.tsx` (proposed)
---
## Design Review Notes
Findings from a design review of this wireframe against the mission control
aesthetic, implementation reality, and cross-wireframe consistency.
### 1. Inline-editable initiative name (ADDED)
The original spec showed the name as static `text-2xl font-bold` with no
edit affordance. This is a problem: the only way to rename an initiative was
via `updateInitiative` in the tRPC layer with no UI surface. Added a
hover-to-reveal pencil icon pattern. The pencil only appears on hover so
the title line stays clean. This matches the pattern used for branch editing
(click to activate inline input, Enter/Escape to save/cancel).
**Decision**: Not always-editable (clicking directly on text). That would
create accidental edit activations and conflicts with text selection. The
pencil-on-hover is the right middle ground.
### 2. Tab badge consistency (FIXED)
Original spec had `Execution 3/7` (fraction, no parens) and `Review (2)`
(count in parens). Content and Plan had no badges at all. This was
inconsistent in two ways: mixed formats, and missing information.
**Fix**: All count badges use `(N)` format. Execution keeps the fraction
format `3/7` because it communicates progress, not cardinality. Content
and Plan now show `(N)` for page count and phase count respectively.
Rationale documented inline.
### 3. Keyboard shortcut discoverability (ADDED)
The original spec relied entirely on tooltip-on-hover for `1-4` shortcuts.
Tooltips are invisible to keyboard-only users and to anyone who does not
happen to hover over a tab. Two mechanisms added:
- CommandPalette results surface tab shortcuts with `<kbd>` badges
- One-time first-visit hint below the tab bar
The first-visit hint is the more impactful one. It teaches the pattern
once without being permanently noisy.
### 4. Initiative actions menu (ADDED)
This was a genuine gap. The list view had `[...]` menus with Discuss, Plan,
Archive, Delete. The detail view had none of these. A user navigating into
an initiative had no way to archive, delete, or spawn agents without going
back to the list.
The `[...]` menu is right-aligned on Row 1. It duplicates the mode toggle
intentionally (badge = fast toggle, menu = discoverable).
**Concern raised and resolved**: "Change Mode" in the menu is redundant
with the clickable `[MODE]` badge. Kept both because badge clickability
is not obvious (it looks like a status indicator, not a button).
### 5. Activity feed (ADDED)
The original spec had no cross-tab awareness. If you were on the Content
tab, you had zero visibility into agent completions, task failures, or
proposal submissions happening on other tabs. For a mission control tool
managing 10+ agents, this is a critical gap.
The activity feed is a collapsible ticker at the bottom. Collapsed by
default (single line) so it does not steal vertical space. Each event
includes a tab context badge that doubles as a navigation shortcut.
**Scope note**: The `[View All]` link is a stub in v2. A full activity
log page is deferred. The ticker covers the 80% case.
### 6. Tab transition (DOCUMENTED)
Original spec was silent on this. Explicitly documented as instant (no
animation) with rationale. This is a deliberate choice, not an oversight
to be "fixed later" with a fade-in.
### 7. Tab content internal header pattern (DOCUMENTED)
Each tab wireframe defines its own layout, but there was no cross-tab
consistency expectation documented. Added a section specifying the
`left=context, right=action` pattern that all four tabs already follow
in practice. This is documentation of an emergent pattern, not a new
requirement.
### 8. Not Found vs generic Error state (SPLIT)
The original spec had one Error State wireframe for all errors. But the
existing code already differentiates "not found" from other errors (line 70
of `$id.tsx`). Added a distinct Not Found wireframe with a more specific
message. This is already partially implemented; the wireframe now matches.
### 9. Minor fix: Source path corrected
The header listed `$initiativeId.tsx` but the actual file is `$id.tsx`.
Fixed.
### Open questions for implementation
- **Activity feed event source**: The feed needs a tRPC subscription or
SSE stream filtered by `initiativeId`. The existing `useLiveUpdates`
hook invalidates queries but does not expose raw events. A new
`onInitiativeActivity` subscription may be needed.
- **Tab badge data loading**: Content `(N)` requires a `listPages` count,
Plan `(N)` requires a `listPhases` count. These queries already run on
their respective tabs but would need to be hoisted to the tab bar level
(or the tab bar reads from the query cache). Ensure no waterfall.
- **Activity feed performance**: Storing events in memory (not DB) is fine
for the ticker (last 50 events). But if `[View All]` eventually becomes
a real page, a persistent activity log table will be needed.

View File

@@ -9,15 +9,18 @@
| Aspect | v1 | v2 |
|--------|----|----|
| Card metadata | Name + status + progress only | Added branch, projects, relative timestamp |
| Sort | None | Dropdown: Newest / Oldest / Name A-Z / Most Progress |
| Card metadata | Name + status + progress only | Branch, projects, timestamp, active agent count, last activity summary |
| Sort | None | Dropdown: Recent Activity / Oldest / Name A-Z / Name Z-A / % Complete |
| Search | None | Client-side text filter on initiative name |
| Create dialog | Had Branch field | Branch removed (auto-generated on first execution dispatch) |
| Loading state | 3 skeleton cards | 5 skeleton cards with shimmer animation |
| Error state | None | AlertCircle + message + Retry button |
| Empty (filtered) | None | "No matching initiatives" + Clear filters link |
| Empty (zero) | Basic text + CTA | Centered EmptyState component |
| Card actions | View + Spawn dropdown + `...` menu | Same (unchanged) |
| Card actions | View + Spawn dropdown + `...` menu | Same + enumerated `...` menu actions |
| Keyboard nav | None | Arrow keys navigate cards, Enter opens, `n` creates new |
| Multi-select | None | Checkbox select + batch action bar |
| View toggle | None | Card / compact list toggle for 20+ initiatives |
---
@@ -28,26 +31,29 @@
| [CW] [*Initiatives*] Agents *3 Inbox (2) Settings [cmd-k] [sun] * |
+=============================================================================+
| |
| Initiatives [ + New Initiative ] |
| Initiatives (3) [ + New Initiative ] |
| |
| [search____________________] [All v] [Newest v] |
| [search____________________] [All v] [Recent Activity v] |
| |
| +-----------------------------------------------------------------------+ |
| | Auth System Overhaul [ACTIVE] [REVIEW] | |
| | [git] cw/auth-overhaul [P] backend, frontend . 2h ago | |
| | [=========> ] 3/7 tasks [...] | |
| | [▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░] 3/7 tasks [*2 agents] [...] | |
| | Agent completed "Implement auth middleware" 2h ago | |
| +-----------------------------------------------------------------------+ |
| |
| +-----------------------------------------------------------------------+ |
| | API Redesign [DONE] [YOLO] | |
| | [git] cw/api-redesign [P] backend . 1d ago | |
| | [==========================] 12/12 tasks [...] | |
| | [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 12/12 tasks [...] | |
| | All tasks completed 1d ago | |
| +-----------------------------------------------------------------------+ |
| |
| +-----------------------------------------------------------------------+ |
| | Mobile App Redesign [ACTIVE] [REVIEW] | |
| | [P] frontend . 5m ago | |
| | [==> ] 1/5 tasks [...] | |
| | [▓▓░░░░░░░░░░░░░░░░░░░░░] 1/5 tasks [*1 agent] [...] | |
| | Agent started "Design mobile nav" 5m ago | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
@@ -56,23 +62,61 @@
Note: Third card has no branch yet (auto-generated on first execution dispatch),
so `[git]` badge is absent. Only `[P]` project badges and timestamp shown.
Note: Total initiative count `(3)` shown next to heading for at-a-glance density.
Agent count badge `[*2 agents]` only appears when > 0 running agents on initiative.
## Card Anatomy
```
+-------------------------------------------------------------------------+
| Initiative Name [STATUS] [MODE] |
| [checkbox] Initiative Name [STATUS] [MODE] |
| [git] cw/<branch> [P] project1, project2 . <relative-time> |
| [==========> ] N/M tasks [...] |
| [▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░] N/M tasks [*K agents] [...] |
| <last-activity-summary> |
+-------------------------------------------------------------------------+
```
### Metadata row elements
### Row 1: Title + badges
- `[checkbox]` — Hidden by default. Appears on hover or when any card is selected (batch mode).
- Initiative name — clickable, navigates to initiative detail.
- Status + mode badges right-aligned.
### Row 2: Metadata
- `[git] cw/<branch>` — GitBranch icon + branch name. Hidden if no branch yet.
- `[P] project1, project2` — Folder icon + comma-separated project names.
Hidden if no projects linked.
- `. <relative-time>` — Dot separator + relative timestamp (e.g., "2h ago", "1d ago").
Based on `updatedAt`.
### Row 3: Progress + agents
- Progress bar with `N/M tasks` label.
- `[*K agents]` — Running agent count (green dot + count). Hidden when 0.
- `[...]` — More actions menu, right-aligned.
### Row 4: Last activity
- Single-line activity summary in `text-muted-foreground text-sm`.
- Format: `"<Actor> <verb> "<target>" <relative-time>"`.
- Examples: `Agent completed "Implement auth middleware" 2h ago`,
`You approved task "Deploy to staging" 10m ago`,
`All tasks completed 1d ago`,
`Agent started "Design mobile nav" 5m ago`.
- Source: derived from most recent agent event or task status change on the initiative.
Priority order: agent error > agent question (waiting) > task completed > agent spawned > task created.
Falls back to `"Created <relative-time>"` if no events exist yet.
- Truncated with ellipsis if text overflows.
### Card hover / focus states
- **Hover**: `bg-muted/50` background transition (`transition-colors duration-150`),
`border-border/80` border brightens subtly. Checkbox fades in on Row 1 (see Multi-Select).
Cursor: `cursor-pointer`.
- **Keyboard focus** (via `j`/`k` or Tab): `ring-2 ring-primary ring-offset-2 ring-offset-background`.
No background change — the ring is the only focus indicator, keeping it distinct from hover.
- **Active/pressed**: `bg-muted/70` on `mousedown`, releases on `mouseup`.
- **Selected** (checkbox checked): `ring-2 ring-primary bg-primary/5` persistent highlight.
The ring distinguishes selected from focused — both can coexist (focused + selected).
- **Disabled**: N/A — cards are never disabled. Archived initiatives are visually dimmed
(`opacity-60`) but still interactive.
### Status badge colors
- `[ACTIVE]` — green
- `[DONE]` — grey
@@ -83,36 +127,85 @@ so `[git]` badge is absent. Only `[P]` project badges and timestamp shown.
- `[REVIEW]` — blue
### Progress bar
- Blue fill when in progress
- Green fill when 100% (all tasks done)
- Grey track for remaining
- Segmented fill: each segment represents one task. Completed segments are filled,
in-progress segments pulse, pending segments are grey track.
- Segment gap: 2px between segments (rendered via `gap-0.5` on a flex container,
each segment is a `div` with `flex: 1`). Minimum segment width: 4px.
- Color: indigo (`--primary`) fill for completed segments when initiative is in progress,
green (`--status-success-fg`) fill for all segments when 100% complete.
- In-progress segments: `--primary` at 50% opacity with `animate-pulse` (1.5s ease-in-out).
This is the only animation on the card — intentionally subtle, draws the eye to where
work is actively happening.
- `pending_approval` segments: amber (`--status-warning-fg`) static fill — distinct from
in-progress (pulsing) and completed (solid).
- Grey track (`--muted`) for pending/not-started segments.
- At > 20 tasks, segments merge into a continuous bar (segments too narrow to distinguish).
The bar becomes three colored sections: completed (solid), in-progress (pulsing), pending (grey).
- The `N/M tasks` label is always visible regardless of bar width.
- Tooltip on hover shows breakdown: "3 completed, 1 in progress, 1 pending approval, 2 pending".
- Zero tasks edge case: show "No tasks yet" in `text-muted-foreground text-xs` instead of
an empty bar. This happens for freshly created initiatives before planning runs.
### More menu `[...]`
Opened via click on `[...]` button or `.` keyboard shortcut on focused card.
Uses shadcn `<DropdownMenu>`. Closed on `Escape`, click outside, or item selection.
```
[...]
+----------------+
| Discuss |
| Plan |
| ----------- |
| Archive |
| Delete | ← window.confirm; Shift+click bypasses
+----------------+
+------------------------+
| View Enter | ← navigate to initiative detail
| ---------------------- |
| Discuss... | ← spawn discuss agent (opens dialog)
| Plan... | ← spawn plan agent (opens dialog)
| Refine... | ← spawn refine agent (opens dialog)
| ---------------------- |
| Duplicate | ← clone initiative (name, projects, phases — no tasks/agents)
| Archive a | ← sets status to archived; window.confirm, Shift+click bypasses
| ---------------------- |
| Delete d | ← destructive red text; window.confirm, Shift+click bypasses
+------------------------+
```
Right-side hints (`Enter`, `a`, `d`) are keyboard shortcut annotations in
`text-muted-foreground text-xs`. They match the global keyboard bindings —
these are not menu-local shortcuts, they work without opening the menu.
Actions are context-aware:
- `Archive` only shown for `active` / `done` initiatives. `Unarchive` shown for `archived`.
- `Discuss/Plan/Refine` hidden for `archived` initiatives.
- `Delete` always shown, always destructive-colored (`text-destructive`).
- `Duplicate` hidden for `archived` initiatives (duplicating dead work is a mistake).
- When all agent actions are hidden (archived), the second separator is also hidden
to avoid double separators.
---
## Search + Filter + Sort Controls
```
[search____________________] [All v] [Newest v]
[search____________________] [All v] [Recent Activity v]
```
### Responsive layout
```
>= 768px: [search_______________] [All v] [Recent Activity v]
< 768px: [search_______________________________________]
[All v] [Recent Activity v]
```
At mobile widths, the filter row wraps to two lines: search takes full width on
the first line, dropdowns share the second line. Search is always first because
it is the most-used control.
### Search input
- Placeholder: "Search initiatives..."
- Client-side filter on `initiative.name` (case-insensitive substring match)
- Debounced 150ms
- Clear button `[x]` appears when non-empty
- Keyboard shortcut: `/` focuses the search input (standard convention)
### Status filter dropdown `[All v]`
@@ -126,18 +219,26 @@ so `[git]` badge is absent. Only `[P]` project badges and timestamp shown.
+---------------+
```
### Sort dropdown `[Newest v]`
### Sort dropdown `[Recent Activity v]`
```
[ Newest v]
+---------------+
| Newest | ← updatedAt DESC (default)
| Oldest | ← updatedAt ASC
| Name A-Z | ← name ASC
| Most Progress| ← completion % DESC
+---------------+
[ Recent Activity v]
+----------------------+
| Recent Activity | ← updatedAt DESC (default) — most recently touched first
| Oldest | ← createdAt ASC
| Name A-Z | ← name ASC
| Name Z-A | ← name DESC
| % Complete | ← (completed tasks / total tasks) DESC, then updatedAt DESC
+----------------------+
```
"Recent Activity" replaces "Newest" — the label should describe the sort semantics,
not the age of the record. The default sort puts the initiative you most recently
interacted with at the top, which is the correct default for a workspace tool.
"% Complete" is unambiguous: it is `completed / total` as a percentage, with ties
broken by recent activity. An initiative at 3/7 (42.8%) sorts below 5/10 (50%).
---
## Loading State (5 skeleton cards with shimmer)
@@ -147,39 +248,56 @@ so `[git]` badge is absent. Only `[P]` project badges and timestamp shown.
| |
| Initiatives [ + New Initiative ] |
| |
| [search____________________] [All v] [Newest v] |
| [search____________________] [All v] [Recent Activity v] |
| |
| +-----------------------------------------------------------------------+ |
| | ░░░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ . ░░░░░ | |
| | ░░░░░░░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +-----------------------------------------------------------------------+ |
| +-----------------------------------------------------------------------+ |
| | ░░░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ . ░░░░░ | |
| | ░░░░░░░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +-----------------------------------------------------------------------+ |
| +-----------------------------------------------------------------------+ |
| | ░░░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ . ░░░░░ | |
| | ░░░░░░░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +-----------------------------------------------------------------------+ |
| +-----------------------------------------------------------------------+ |
| | ░░░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ . ░░░░░ | |
| | ░░░░░░░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +-----------------------------------------------------------------------+ |
| +-----------------------------------------------------------------------+ |
| | ░░░░░░░░░░░░░░░░░░░ ░░░░░░ ░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ . ░░░░░ | |
| | ░░░░░░░░░░░░░░ | |
| | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
Skeleton cards mirror card anatomy: title line, metadata line, progress line.
Shimmer animation sweeps left-to-right on a 1.5s loop.
Skeleton cards mirror card anatomy: title line, metadata line, progress line,
activity line (4 rows matching real card height). Shimmer animation sweeps
left-to-right on a 1.5s loop via `@keyframes shimmer` with `background: linear-gradient(...)`.
Skeleton height must match real card height to prevent layout shift on data load.
Skeleton count reasoning: 5 cards fills a typical viewport (900px content area
at ~120px per card with gaps). Showing fewer risks the page feeling empty; showing
more risks painting below the fold for nothing. This is a minor detail — the shimmer
animation is what makes loading feel fast, not the count.
The search/filter bar renders immediately (not skeletonized) — the controls are static
and user-operable even while data loads. If the user starts typing a search during load,
the filter is applied as soon as data arrives. No race condition: the query returns all
initiatives and the client filters.
---
@@ -203,7 +321,17 @@ Shimmer animation sweeps left-to-right on a 1.5s loop.
```
Uses `<ErrorState>` component (see shared-components.md).
Retry calls `refetch()` on the tRPC query.
Retry calls `refetch()` on the tRPC query. Retry button shows a spinner while
the refetch is in-flight (disabled state, `opacity-50 pointer-events-none`).
Error message is the tRPC error message, not a generic string. Examples:
- "Failed to load initiatives" (generic fallback)
- "Database connection lost" (SQLite file locked)
- "Server unreachable" (network error)
The error state preserves the filter bar — if the user had filters set before
the error, they remain. This prevents the disorienting "my filters disappeared"
experience on transient errors.
---
@@ -239,7 +367,7 @@ CTA button opens the CreateInitiativeDialog.
| |
| Initiatives [ + New Initiative ] |
| |
| [auth_____________________] [Active v] [Newest v] |
| [auth_____________________] [Active v] [Recent Activity v] |
| |
| +-----------------------------------------------------------------------+ |
| | | |
@@ -254,7 +382,160 @@ CTA button opens the CreateInitiativeDialog.
```
"Clear filters" is a text link (not a button). Resets search input to empty,
status filter to "All", sort to "Newest".
status filter to "All", sort to "Recent Activity".
---
## Keyboard Navigation
| Key | Action |
|-----|--------|
| `j` / `Arrow Down` | Move focus to next card |
| `k` / `Arrow Up` | Move focus to previous card |
| `Enter` | Open focused initiative |
| `x` | Toggle selection on focused card (batch mode) |
| `Shift+x` | Select range from last selected to focused card |
| `n` | Open Create Initiative dialog |
| `/` | Focus search input |
| `Escape` | Clear selection / blur search / close menu (priority order) |
| `.` | Open `[...]` menu on focused card |
| `d` | Quick-delete focused card (triggers confirm dialog) |
| `a` | Quick-archive focused card (triggers confirm dialog) |
Focus ring: `ring-2 ring-primary ring-offset-2` on the focused card.
Scroll-into-view: when `j`/`k` moves focus to a card partially or fully off-screen,
`scrollIntoView({ block: 'nearest', behavior: 'smooth' })` is called. This scrolls
the minimum distance needed — no jarring jumps to center.
Focus wraps: pressing `j` on the last card moves to the first.
Keyboard shortcuts are disabled when: any dialog is open, search input is focused,
or a dropdown is open. The `useHotkeys` hook should check `document.activeElement`
and bail if it is an input/textarea/select or inside a `[role="dialog"]`.
No keyboard shortcut hints are shown on the page itself. They are discoverable via
the command palette (`cmd-k`) which lists all available actions with their bindings.
---
## Multi-Select & Batch Operations
Checkbox appears on each card on hover, or on all cards when any card is selected.
### Batch action bar (appears when >= 1 card selected)
```
+=============================================================================+
| [x] 3 selected [ Archive ] [ Delete ] [Deselect all] |
+=============================================================================+
```
- Slides down from below the filter bar (above the card list), `animate-in slide-in-from-top`.
Slides back up when selection is cleared (`animate-out slide-out-to-top`).
- `Archive` — sets all selected to `archived`. Confirms unless Shift held.
Confirmation message: "Archive 3 initiatives?" (count is dynamic).
- `Delete` — destructive red (`text-destructive`). Confirms unless Shift held.
Confirmation message: "Permanently delete 3 initiatives? This cannot be undone."
- `Deselect all` — text link (`text-muted-foreground underline`), clears selection.
- `[x]` checkbox is tri-state: unchecked (0 selected), indeterminate (some selected),
checked (all visible selected). Clicking when indeterminate selects all visible.
- Selection count label: `"3 selected"` or `"3 of 12 selected"` when filtered
(so the user knows they are only operating on visible items, not all initiatives).
- Selection state is cleared when filters change (prevents invisible-selection bugs).
- Bar has `bg-muted border-b` styling, `h-12` fixed height, `z-10` to layer above cards.
- Bar is sticky (`sticky top-0`) so it remains visible when scrolling through selected cards.
---
## Compact List View (toggle)
For workspaces with 20+ initiatives, a compact list view is more scannable than cards.
### Toggle control (right of filter bar)
```
[search_______________] [All v] [Recent Activity v] [card|list]
```
`[card|list]` — Two icon buttons (LayoutGrid / List lucide icons). Active state
uses `bg-muted`. Persisted to `localStorage` key `cw-initiatives-view`.
### Compact list wireframe
```
+=============================================================================+
| |
| Initiatives (12) [ + New Initiative ] |
| |
| [search_______________] [All v] [Recent Activity v] [card|*list*] |
| |
| +-----------------------------------------------------------------------+ |
| | NAME STATUS PROGRESS AGENTS ACTIVITY | |
| | ------------------------------------------------------------------- | |
| | Auth System Overhaul ACTIVE 3/7 (42%) *2 2h ago | |
| | API Redesign DONE 12/12 (100%) — 1d ago | |
| | Mobile App Redesign ACTIVE 1/5 (20%) *1 5m ago | |
| | Logging Refactor ACTIVE 0/3 (0%) — 3d ago | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
- Each row is clickable (navigates to initiative detail).
- Column headers are sortable by click (toggles ASC/DESC, synced with sort dropdown).
Active sort column shows a chevron indicator (up/down). Click again to reverse.
- Rows are 40px height (`h-10`) for density. Text is `text-sm`.
- Hover: `bg-muted/50` row highlight (`transition-colors duration-100`).
- Focus: same `ring-2 ring-primary` as card view. `j`/`k` navigation works identically.
- Selection: checkbox column appears on left (always visible in list view — no hover reveal,
because the row is too compact for hover-dependent UI). `x` to toggle, `Shift+x` for range.
- `[...]` menu: appears as the last column on each row (right-aligned, icon-only button).
Same menu items as card view. This is better than right-click context menus because:
(a) right-click is not discoverable, (b) it conflicts with browser dev tools context menu,
(c) it has no mobile equivalent.
- STATUS column: uses the same badge components as card view but in `text-xs` size.
- AGENTS column: green dot + number when > 0, em-dash when 0.
- ACTIVITY column: relative time only (no activity summary — too dense for a table row).
Full activity summary available on hover via a tooltip.
---
## Scale Behavior (50+ initiatives)
At high initiative counts, the page needs to stay responsive:
- **Card view**: No pagination. All cards render in a virtualized list (`@tanstack/react-virtual`)
when count exceeds 30. The virtual window renders ~10 cards plus 5 overscan on each side.
Scroll position is preserved on navigation back from initiative detail.
- **List view**: Same virtualization strategy. At 40px row height, 100 initiatives is only
4000px — manageable without virtualization, but virtualize anyway for consistency.
- **Filter bar counts**: The `(N)` count next to "Initiatives" updates to reflect filtered
count when filters are active: `Initiatives (5 of 47)`.
- **Search performance**: Client-side substring match is O(n) on initiative names. At 200
initiatives this is ~0.1ms — no performance concern. If initiatives ever hit 1000+,
move to server-side search (but that is not a v2 concern).
- **Auto-suggestion**: When the list exceeds 20 initiatives and the user is in card view,
a one-time subtle hint appears below the view toggle: `"Tip: Switch to list view for faster scanning"`.
Dismissable, stored in `localStorage` key `cw-list-view-hint-dismissed`.
---
## State Transitions
Transitions between loading, empty, error, and populated states should feel deliberate:
| From | To | Transition |
|------|----|-----------|
| Loading (skeletons) | Populated (cards) | Skeletons fade out (`opacity-0` 150ms), cards fade in (`opacity-100` 150ms). No layout jump — skeleton card dimensions match real card dimensions. |
| Loading | Empty (zero) | Skeletons fade out, empty state fades in. Container height stays stable (min-height matches 3 skeleton cards). |
| Loading | Error | Skeletons fade out, error state fades in centered. |
| Populated | Empty (filtered) | Cards slide out, empty-filter message fades in. Instant — no loading spinners for client-side filtering. |
| Error | Loading (retry) | Error fades out, skeletons fade in. Retry button shows spinner during refetch. |
Key principle: the container holding the card list has `min-h-[400px]` to prevent
the page from collapsing vertically during transitions. This avoids the jarring
"page jumping" effect when going from a tall state to a short empty state.
---
@@ -263,5 +544,125 @@ status filter to "All", sort to "Newest".
- `packages/web/src/routes/initiatives/index.tsx`
- `packages/web/src/components/InitiativeList.tsx`
- `packages/web/src/components/InitiativeCard.tsx`
- `packages/web/src/components/InitiativeCardCompact.tsx` (proposed — list view row)
- `packages/web/src/components/BatchActionBar.tsx` (proposed)
- `packages/web/src/components/EmptyState.tsx` (proposed)
- `packages/web/src/components/ErrorState.tsx` (proposed)
---
## Design Review Notes
Honest critique of the v2 spec. What was already good, what was weak, what was missing,
and what was added or changed during review.
### What the spec already got right
The v1-to-v2 delta table is excellent — it makes the design intent traceable. The card
anatomy with 4 rows is the right density for a mission-control tool. The segmented
progress bar, active agent count badge, and last-activity summary were all present
before this review. The sort options are well-chosen with clear semantics. The responsive
filter bar layout is properly specified. Credit where due: this spec was 80% there.
### 1. Card hover/focus/selected states were completely unspecified
The spec defined a focus ring in the keyboard nav section but said nothing about hover,
active (mousedown), or selected (checkbox checked) states. For a page where the primary
interaction is "scan cards and click one," this is a critical omission. A card with no
hover feedback feels dead. Added: hover (`bg-muted/50`), focus (`ring-2 ring-primary`),
active (`bg-muted/70`), selected (`ring-2 ring-primary bg-primary/5`), and the rule
that focus + selected can coexist visually.
### 2. Progress bar: good concept, missing edge cases
The segmented bar idea was solid. But the spec was silent on: `pending_approval` tasks
(a real status in this system — they need amber fill, not grey), zero-task initiatives
(shows an empty bar which looks broken), and the exact visual treatment of in-progress
segments (just saying "pulse" is not enough — added opacity and animation details).
The >20 task fallback was already there and is pragmatic.
### 3. Sort semantics were already clean
"Recent Activity" and "% Complete" were already well-defined with tiebreakers. No
changes needed here. The original spec nailed this.
### 4. Batch operations: specified but the bar lacked detail
Multi-select was present but the batch action bar was a one-liner. Missing: tri-state
checkbox behavior, filtered-count awareness (`"3 of 12 selected"`), sticky positioning,
animation in/out, confirmation message wording, and the `Shift+x` range selection
keyboard shortcut. These are the details that separate "we thought about batch ops"
from "we can actually implement batch ops."
### 5. Compact list view: right-click context menu was a bad call
The original spec said "No `[...]` menu in list view — right-click context menu provides
the same actions." This is wrong for three reasons: (a) right-click is not discoverable
by any user who does not already know it exists, (b) it conflicts with browser developer
tools on right-click, (c) it does not work on mobile/touch. Changed to: `[...]` icon
button as the last column in every row. Consistent with card view, no discovery problem.
### 6. Keyboard nav: missing `.` for menu, `d`/`a` quick actions, `Shift+x` range
The original `j/k/Enter/x/n///Escape` set was a good start but missed: `.` to open the
`[...]` menu (standard in GitHub), `d` and `a` for quick-delete/archive (common in
email clients), and `Shift+x` for range selection. Also missing: scroll-into-view
behavior (if `j` moves focus off-screen, the card must scroll into view), and the
critical note about disabling shortcuts when dialogs/inputs are focused.
### 7. Skeleton cards had 3 rows, real cards have 4
The ASCII wireframe for skeleton cards showed 3 shimmer rows per card, but the real card
anatomy has 4 rows (title, metadata, progress, activity). This causes a layout shift
on data load — exactly the problem skeletons are supposed to prevent. Fixed: added a
4th shimmer row to each skeleton card.
### 8. No scale strategy beyond "list view"
The spec had card view and list view but no mention of what happens at 50+ or 100+
initiatives. Added: virtualization via `@tanstack/react-virtual` at 30+ items, scroll
position preservation on back-navigation, and a one-time hint nudging card-view users
toward list view when count exceeds 20.
### 9. Error state was too thin
"AlertCircle + message + Retry" is the skeleton of an error state, not a complete one.
Missing: retry button loading state, error message source (tRPC error vs. generic),
filter bar preservation during error. These are the details that determine whether a
transient SQLite lock causes user confusion or is handled gracefully.
### 10. Last activity event priority was unspecified
The spec said "derived from most recent agent event or task status change" but did not
define which events take priority. An agent error is more important than a task creation.
Added priority order: agent error > agent question (waiting) > task completed > agent
spawned > task created. This ensures the activity line surfaces the most actionable
information, not just the most recent timestamp.
### Open questions for implementation
1. **Last activity data source**: The `agent_log_chunks` table has raw JSONL but no
structured events. The EventBus emits events but does not persist them. Two options:
(a) derive from task `status` + `updatedAt` (cheap, less rich), or (b) add a
lightweight `activity_log` table that captures the ~5 event types needed for
activity summaries. Option (b) is cleaner but adds a migration and a new repository.
Recommendation: option (a) for v2, option (b) for v2.1.
2. **Agent count query cost**: `listInitiatives` needs a LEFT JOIN on `agents` WHERE
`status = 'running'` GROUP BY `initiativeId`. This is a single query, not N+1.
The Drizzle ORM `with` clause or a raw subquery in the repository adapter handles
this. Not optional — the agent count badge is the highest-value addition in this spec.
3. **Compact list view priority**: v2 can ship without it. Cards-only works for the
typical 3-15 initiative range. But the toggle infrastructure (localStorage persistence,
conditional render) should be stubbed even if the list component is not built yet,
so adding it later is a 1-file change, not a refactor.
4. **Virtualization priority**: Not needed until someone actually has 30+ initiatives.
Skip for v2, add when performance is measurably impacted. The hint to switch to list
view is the cheap mitigation.
5. **`Shift+x` range selection complexity**: Range selection requires tracking "last
selected index" which interacts with sort order changes. If sort changes while
a range is partially selected, the range anchor becomes meaningless. Decision: clear
the range anchor (but not the selection) on sort change. Document this behavior.

View File

@@ -12,7 +12,14 @@
| Save indicator | None on phase description editor | `<SaveIndicator>` component (same as Content tab) |
| Empty state (no agent) | Single "No phases yet" with both spinner and buttons shown contextually | Split: dedicated "No phases yet" empty state with action buttons |
| Empty state (agent running) | Spinner + "Planning phases..." inline with buttons | Dedicated "Planning in progress..." state, NO action buttons |
| Empty state (agent failed) | None -- user had no signal | Dedicated "Planning failed" state with error message + retry button |
| Pending review | Banner only on individual phase detail | Additional top-level banner when proposals exist initiative-wide |
| Sidebar width | 260px (inconsistent with Content tab's 192px) | 240px (matches Content tab v2 -- consistent divider line across tabs) |
| Phase reordering | None | Drag-and-drop via `[≡]` toggle in sidebar header |
| Task tree | Flat list | Nested tree showing parent-child decomposition + dependency indicators |
| Task inline editing | None | Click task name to inline-edit; own `<SaveIndicator>` per row |
| Keyboard shortcuts | None | `N` to add task (when task list focused), `E` to edit selected task |
| Bulk task operations | None | `[...]` menu on Tasks header with bulk actions |
---
@@ -20,11 +27,11 @@
```
+=============================================================================+
| Phases [+] | Phase: OAuth Implementation |
| Phases [Detailing] [≡] [+] | Phase: OAuth Implementation |
| --------------------------+ [✓] Saved |
| +------------------------+| ------------------------------------------------|
| | 1. Auth Setup || Description: |
| | [DONE] 3/3 || Implement OAuth 2.0 authorization code flow |
| | [DONE] 3/3 tasks || Implement OAuth 2.0 authorization code flow |
| +------------------------+| with PKCE extension for public clients. |
| +------------------------+| |
| |*2. OAuth Flow *|| Support Google, GitHub, and Microsoft as |
@@ -33,24 +40,33 @@
| +------------------------+| Dependencies |
| +------------------------+| [+ Add dependency v] |
| | 3. UI Components || * Phase 1: Auth Setup [DONE] [x] |
| | [PENDING] 5 tasks || |
| | dep: 1, 2 || Tasks (0/5) [+ Add Task] |
| | [PENDING] 2/5 tasks || |
| | dep: 1, 2 || Tasks (2/5) [...] [N] [+ Add Task]|
| +------------------------+| ├── Set up OAuth routes * [DONE] |
| | ├── Implement PKCE flow * [RUNNING] |
| | │ agent: blue-fox-7 |
| 260px | ├── Google provider [PENDING] [x] |
| | │ └─ agent: blue-fox-7 |
| | └─ child: Validate scopes [PENDING] |
| | ├── Google provider [PENDING] [x] |
| | │ ← blocked by: Implement PKCE flow |
| | ├── GitHub provider [BLOCKED] [x] |
| | │ blocked by: Google provider |
| | │ blocked by: Google provider |
| | └── Microsoft provider [PENDING] [x] |
| | |
| 240px | detail panel (flex-1) |
| | |
+=============================================================================+
```
- Phase sidebar: 260px, `border-right`
- Phase sidebar: **240px**, `border-right` (see Design Review Notes on sidebar width rationale)
- Selected phase: left blue border + `bg-accent` background
- `[+]` in header triggers `handleStartAdd` (inline input at bottom of list)
- `[≡]` in header toggles drag-and-drop reorder mode (via `@dnd-kit/sortable`)
- `[✓] Saved` shows after phase description auto-save completes
- Task `[x]` delete buttons appear on hover; Shift+click bypasses confirm dialog
- Task tree shows parent-child nesting via indented `└─ child:` rows under parent tasks
- Dependency arrows (`← blocked by: <task-name>`) shown inline below blocked tasks
- `[N]` keyboard hint badge next to "Add Task" -- pressing `N` when task list is focused triggers inline task creation
- `[...]` on Tasks header row opens bulk actions menu (see Bulk Task Operations section)
---
@@ -59,7 +75,7 @@
```
+------------------------+
| 1. Auth Setup | Phase number + name (truncated)
| [DONE] 3/3 | StatusBadge (small) + "complete/total tasks"
| [DONE] 3/3 tasks | StatusBadge (small) + "complete/total tasks"
+------------------------+
+------------------------+
|*2. OAuth Flow *| * = selected (border-l-2 border-primary bg-accent)
@@ -68,26 +84,34 @@
+------------------------+
+------------------------+
| 3. UI Components |
| [PENDING] 5 tasks |
| [PENDING] 2/5 tasks | progress fraction: completed/total
| dep: 1, 2 | multiple dependency shorthand
+------------------------+
```
- Sidebar cards always show task progress as `N/M tasks` (not just total count)
- `N/M` uses `text-muted-foreground`; turns `text-status-success-fg` when N === M (all done)
- In reorder mode (`[≡]` active), each card gets a drag handle icon on the left
- Sidebar is `overflow-y-auto` with `scrollbar-thin` (webkit custom scrollbar, 4px wide)
- When > 8 phases, a subtle gradient fade at bottom edge signals scrollable content
- Selected phase auto-scrolls into view on selection change (`scrollIntoView({ block: 'nearest' })`)
---
## Phase Actions (sidebar header)
```
Phases [Detailing (2)] [+] [Detail All]
^^^^^^^^^^^^^^ ^^^^^^^^^^^
spinner + count sparkles icon
when detail agents disabled when no
are active eligible phases
Phases [Detailing (2)] [≡] [+] [Detail All]
^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^
spinner + count drag sparkles icon
when detail handle disabled when no
agents active toggle eligible phases
```
- `[Detailing (N)]` spinner badge shows when N detail agents are running
- `[Detail All]` spawns detail agents for all phases with 0 tasks
- `[+]` opens inline input for new phase name
- `[≡]` toggles reorder mode -- when active, cards become draggable, `bg-muted` highlight on sidebar
---
@@ -115,6 +139,111 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
---
## Task Inline Editing
Tasks support inline name editing. Click a task name to toggle into an input field with its own save state.
### Viewing (default)
```
├── Set up OAuth routes * [DONE]
```
### Editing (after click on name)
```
├── [Set up OAuth routes_______] [✓] * [DONE]
```
- Input field replaces the text span; `Enter` or blur saves, `Escape` cancels
- `[✓]` micro-indicator shows save state (same `<SaveIndicator>` logic, icon-only, no text via `compact` prop)
- `E` keyboard shortcut opens edit mode on the currently focused/selected task row
- `Tab` while editing moves to the next task row in edit mode (rapid-fire renaming workflow)
- Changes call `trpc.updateTask.useMutation({ taskId, name })` with 300ms debounce (not on-blur-only; type-and-go feel)
- Only one task can be in edit mode at a time -- clicking another task's name exits the current edit (triggering save)
- Running tasks (`status === 'running'`) have their name input disabled with `cursor-not-allowed` -- you cannot rename a task while an agent is working on it
---
## Task Tree Structure (parent-child + dependencies)
The plan tab is the primary place users understand task structure. The task tree communicates two relationships:
### 1. Parent-child decomposition (vertical nesting)
```
├── Implement PKCE flow * [RUNNING]
│ └─ agent: blue-fox-7
│ └─ child: Validate scopes [PENDING]
│ └─ child: Generate challenge [PENDING]
```
- Child tasks are indented one level under their parent (`parentTaskId`)
- `child:` prefix in `text-muted-foreground` distinguishes decomposition from agent info
- Children inherit phase context from parent; can be expanded/collapsed per parent
- Collapse state: parent rows show `[>]` / `[v]` chevron. Default: expanded if < 4 children, collapsed if >= 4
- Max nesting depth: 3 levels (parent > child > grandchild). Schema supports deeper, but UI truncates with "N deeper tasks..." link that opens Task Detail Modal
- Child count badge on collapsed parents: `Implement PKCE flow [3 subtasks] [RUNNING]`
### 2. Dependency blocking (inline annotation)
```
├── Google provider [PENDING] [x]
│ ← blocked by: Implement PKCE flow
├── GitHub provider [BLOCKED] [x]
│ ← blocked by: Google provider
```
- `←` arrow + `blocked by:` label in `text-muted-foreground text-xs`
- Blocked task name is a clickable link that scrolls to / highlights the blocking task (300ms `ring-2 ring-primary` pulse)
- Only shown when task has unresolved dependencies (hidden once dependency completes)
- Cross-phase dependencies: if the blocking task is in a different phase, show `← blocked by: <task-name> (Phase: <phase-name>)` -- clicking switches to that phase in the sidebar
- Circular dependency guard: if a task chain forms a cycle (should not happen, but can via manual edits), show `[!] Circular dependency` warning in `text-status-error-fg` instead of the blocked-by annotation
### Visual hierarchy
```
Task name text-sm font-medium
agent: <name> text-xs text-muted-foreground font-mono
child: <name> text-sm (indented, normal weight)
← blocked by: text-xs text-muted-foreground italic
```
---
## Bulk Task Operations
The `[...]` menu on the Tasks section header provides bulk operations for all tasks in the selected phase.
```
Tasks (2/5) [...] [N] [+ Add Task]
|
+------------------------+
| Select All Ctrl+A |
|------------------------|
| Delete All Tasks |
| Reset All to Pending |
| Re-categorize All > |
| > execute |
| > verify |
| > research |
|------------------------|
| Expand All Children |
| Collapse All Children |
+------------------------+
```
- `Select All` -- toggles checkbox selection on all task rows (for future multi-select operations); `Ctrl+A` shortcut when task list focused
- `Delete All Tasks` -- Shift+click bypasses confirm; normal click shows `window.confirm()`
- `Reset All to Pending` -- resets all non-running tasks in the phase back to `pending` status. Useful after a failed batch.
- `Re-categorize All` -- submenu with category options; updates all tasks in the phase
- `Expand All Children` / `Collapse All Children` -- bulk toggle for parent-child tree visibility
- Batch mutations: `trpc.deleteTasksByPhase`, `trpc.updateTaskCategory`, `trpc.resetTasksByPhase`
- Menu is a `<DropdownMenu>` from shadcn/ui
- Disabled when phase has 0 tasks
---
## Empty State -- No Phases (no agent running)
```
@@ -139,9 +268,10 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
- Centered `EmptyState` card in the detail panel area
- `[layers icon]` -- Lucide `Layers` icon, `text-muted-foreground`, 48px
- Two action buttons side by side:
- `[sparkles Plan with Agent]` -- primary variant, calls `planSpawn.spawn()`
- `[+ Add Phase]` -- outline variant, calls `onAddPhase()` for inline input
- Only shown when: `phasesLoaded && phases.length === 0 && !isPlanRunning`
- **`[sparkles Plan with Agent]` -- `variant="default"` (primary/filled), calls `planSpawn.spawn()`** -- this is the recommended action
- `[+ Add Phase]` -- `variant="outline"` (secondary/outline), calls `onAddPhase()` for inline input
- Visual hierarchy: "Plan with Agent" is indigo filled button (primary CTA), "Add Phase" is outline (secondary)
- Only shown when: `phasesLoaded && phases.length === 0 && !isPlanRunning && !planFailed`
---
@@ -172,12 +302,45 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
---
## Empty State -- Planning Failed (agent crashed)
```
+=============================================================================+
| Phases | |
| --------------------------+ |
| (empty) | +------------------------------------------+ |
| | | | |
| | | [AlertTriangle icon] | |
| | | Planning failed | |
| | | | |
| | | The planning agent crashed before | |
| | | creating any phases. | |
| | | | |
| | | [sparkles Retry Planning] [View Log] | |
| | | | |
| | +------------------------------------------+ |
| | |
+=============================================================================+
```
- `[AlertTriangle icon]` -- Lucide `AlertTriangle`, 48px, `text-status-error-fg`
- "Planning failed" -- `text-base font-medium text-status-error-fg`
- Subtext -- `text-sm text-muted-foreground`; shows truncated error reason if available from agent's last output (e.g., "API rate limit exceeded", "Account exhausted")
- Two action buttons:
- `[sparkles Retry Planning]` -- `variant="default"` (primary), calls `planSpawn.spawn()` again
- `[View Log]` -- `variant="outline"`, opens `AgentOutputViewer` for the crashed agent
- Shown when: `phasesLoaded && phases.length === 0 && !isPlanRunning && planAgent?.status === 'crashed'`
- Detection: query the most recent plan-category agent for this initiative; check `status === 'crashed'`
- Edge case -- partial failure: if the agent created *some* phases before crashing, this empty state does NOT show (phases exist). Instead, show a dismissible `AlertBanner` at the top of the detail panel: "Planning agent crashed after creating N phases. Some phases may be incomplete. [Retry] [Dismiss]"
---
## Pending Review Banner (top-level)
```
+=============================================================================+
| +---------------------------------------------------------------------+ |
| | [sparkles] Agent has proposals ready for review [Go to Review ->] | |
| | [sparkles] 5 proposals ready for review [Go to Review ->] | |
| +---------------------------------------------------------------------+ |
| Phases [+] | Phase: OAuth Implementation |
| --------------------------+ [✓] Saved |
@@ -187,10 +350,12 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
- Full-width banner above the sidebar+detail grid
- `[sparkles]` -- Lucide `Sparkles` icon, `text-amber-500`
- **Shows proposal count**: "N proposals ready for review" (not generic "Agent has proposals")
- Background: `bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800`
- `[Go to Review ->]` -- text button that navigates to `?tab=review`
- Shown when: proposals with `status === 'pending'` exist for this initiative
- Query: `trpc.listProposals.useQuery({ initiativeId, status: 'pending' })`
- Count derived from `pendingProposals.data?.length`
---
@@ -201,16 +366,21 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
| Phase 2: OAuth Implementation [READY] [git] branch [...] |
| | | |
| branch badge v |
| +--------+
| | Delete |
| | Phase |
| +--------+
| +--------------------+
| | Detail with Agent |
| | Duplicate Phase |
| |--------------------|
| | Delete Phase |
| +--------------------+
+------------------------------------------------------------------------+
```
- Phase name is inline-editable (click to toggle input)
- `[...]` dropdown: Delete Phase (Shift+click bypasses confirm)
- `[git] branch` badge shows initiative branch when set
- Phase name is inline-editable (click to toggle input); same `<SaveIndicator compact>` pattern as task names
- `[...]` dropdown:
- `Detail with Agent` -- spawns a detail agent for this phase (disabled if detail agent already running or phase has tasks)
- `Duplicate Phase` -- deep-copies phase + tasks with "(copy)" suffix; useful for templating similar phases
- `Delete Phase` -- destructive, red text. Shift+click bypasses confirm. Normal click: `window.confirm()`
- `[git] branch` badge shows initiative branch when set; clicking copies branch name to clipboard (toast: "Copied to clipboard")
---
@@ -227,20 +397,85 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
---
## Empty State -- Phase Selected, No Tasks
```
+------------------------------------------------------------------------+
| Phase 2: OAuth Implementation [READY] [...] |
|------------------------------------------------------------------------|
| Description: |
| Implement OAuth 2.0 authorization code flow... |
| |
| Tasks (0) [+ Add Task] |
| +------------------------------------------------------------------+ |
| | | |
| | [ListTodo icon] | |
| | No tasks in this phase | |
| | | |
| | Add tasks manually, or let an agent | |
| | decompose this phase into tasks. | |
| | | |
| | [sparkles Detail with Agent] [+ Add Task] | |
| | | |
| +------------------------------------------------------------------+ |
+------------------------------------------------------------------------+
```
- Only appears inside the Tasks section of the detail panel (NOT a full-panel takeover like the no-phases empty state)
- `[sparkles Detail with Agent]` -- `variant="default"` (primary CTA), spawns a detail agent for this phase
- `[+ Add Task]` -- `variant="outline"` (secondary), opens inline task input
- `[ListTodo icon]` -- Lucide `ListTodo`, 36px, `text-muted-foreground`
- Distinct from the top-level "No phases yet" empty state -- this one is scoped to the Tasks area of an existing phase
---
## Per-Phase Pending Review Banner
```
+------------------------------------------------------------------------+
| [!] This phase is pending review. Go to the Review tab to review. |
| [!] 3 proposals pending review. Go to the Review tab to review. |
+------------------------------------------------------------------------+
```
- Shown below phase header when phase-specific proposals are pending
- `[!]` -- `AlertCircle` icon, `text-amber-500`
- **Shows count**: "N proposals pending review" (not generic text)
- Distinct from top-level banner (this is phase-scoped)
---
## Task Row Hover & Focus States
```
Default:
├── Google provider [PENDING]
Hover:
├── Google provider [PENDING] [play] [x]
^^^^^^^^^^^^^ ^^^^^ ^^^
bg-muted/50 row bg queue delete
cursor-pointer (on hover only)
Focused (keyboard nav):
├── Google provider [PENDING] [play] [x]
^^^^
ring-1 ring-primary/50 outline on row
Selected (Space toggle):
├── [✓] Google provider [PENDING] [play] [x]
^^^
checkbox visible when any task selected
```
- Hover reveals action buttons (`[play]` to queue, `[x]` to delete) at the right edge of the row
- `[play]` button: `variant="ghost" size="icon"` -- Lucide `Play` icon, calls `trpc.dispatchTask`
- `[x]` button: `variant="ghost" size="icon"` -- Lucide `X` icon, `text-destructive` on hover
- Running tasks replace `[play]` with `[square]` (stop button) and hide `[x]` (cannot delete running task)
- Focus ring uses `ring-1 ring-primary/50` (subtle, not aggressive -- this is keyboard nav, not a call to action)
- Hover and focus states stack: hovering a focused row shows both `bg-muted/50` and focus ring
---
## Task Detail Modal (overlay)
```
@@ -249,12 +484,15 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
| |
| Status [PENDING] | Category execute |
| Phase 2. OAuth Flow | Priority normal |
| Agent (none) | |
| Agent (none) | Parent Task (none) |
| |
| Description |
| Create Express routes for /auth/login, /auth/callback, |
| and /auth/logout with proper middleware chain. |
| |
| Child Tasks |
| (none -- will be created during decomposition) |
| |
| Dependencies |
| * DB Schema Migration [DONE] |
| |
@@ -265,13 +503,45 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
+----------------------------------------------------------+
```
- Opened by clicking any task row
- `[x]` close button top-right
- Opened by clicking any task row (or pressing `Enter` on focused row)
- `[x]` close button top-right; `Escape` also closes
- **Parent Task** field shows the parent task name (clickable) if `parentTaskId` is set, otherwise "(none)"
- **Child Tasks** section lists decomposed children if any exist; each child is clickable to open its own modal
- **Dependencies** section shows blocking tasks with status; `[+ Add]` button to add a new dependency (dropdown of other tasks in same phase)
- **Blocks** section shows downstream tasks that depend on this one (read-only, informational)
- Description is a multi-line `<textarea>` with auto-resize; auto-saves with 500ms debounce + `<SaveIndicator>`
- `[ Queue Task ]` dispatches the task; `[ Stop Task ]` kills the running agent
- `[ Queue Task ]` is disabled with tooltip "Blocked by N tasks" when task has unresolved dependencies
- `[ Delete Task ]` -- `variant="ghost"` `text-destructive`, bottom-left of modal. Shift+click bypasses confirm.
- Shared component via `<TaskModal>` in `ExecutionContext`
---
## Keyboard Shortcuts (Plan Tab)
| Key | Context | Action |
|-----|---------|--------|
| `N` | Task list focused | Open inline "Add Task" input at bottom of list |
| `E` | Task row focused | Toggle inline name editing on focused task |
| `Tab` | Inline edit active | Save current edit, move to next task in edit mode |
| `Delete` / `Backspace` | Task row focused | Delete task (with confirm unless Shift held) |
| `Arrow Up` / `Arrow Down` | Task list | Navigate between task rows |
| `Arrow Right` | Collapsed parent focused | Expand children |
| `Arrow Left` | Expanded parent focused | Collapse children |
| `Arrow Left` | Child task focused | Jump focus to parent task |
| `Enter` | Task row focused | Open Task Detail Modal |
| `Space` | Task row focused | Toggle task selection (for future multi-select) |
| `Escape` | Inline edit active | Cancel edit, restore original value |
| `Ctrl+A` | Task list focused | Select all tasks |
| `?` | Plan tab focused, no input active | Show keyboard shortcut overlay |
- Shortcuts are scoped to the task list container (`onKeyDown` handler on the task list wrapper)
- Only active when no other input/textarea is focused (check `document.activeElement.tagName`)
- Discoverable via tooltip on `[+ Add Task]` button: "Add task (N)"
- `?` shortcut opens a shadcn `<Dialog>` listing all shortcuts -- same pattern as GitHub's `?` key
---
## Source
- `packages/web/src/components/ExecutionTab.tsx` (Plan tab = ExecutionTab with sidebar+detail layout)
@@ -280,5 +550,183 @@ Same `<SaveIndicator>` component as Content tab, positioned top-right of phase d
- `packages/web/src/components/execution/PhaseDetailPanel.tsx`
- `packages/web/src/components/execution/PhaseActions.tsx`
- `packages/web/src/components/execution/TaskModal.tsx`
- `packages/web/src/components/execution/BulkTaskMenu.tsx` (new)
- `packages/web/src/components/execution/TaskTree.tsx` (new -- replaces flat list)
- `packages/web/src/components/execution/TaskRowInlineEdit.tsx` (new -- extracted edit mode)
- `packages/web/src/components/execution/PhaseEmptyTasks.tsx` (new -- per-phase empty state)
- `packages/web/src/components/execution/PlanTabSkeleton.tsx` (new -- loading skeleton)
- `packages/web/src/components/KeyboardShortcutOverlay.tsx` (new -- `?` key dialog, reusable)
- `packages/web/src/components/TaskRow.tsx`
- `packages/web/src/hooks/useAutoSave.ts`
---
## Design Review Notes
Critique and rationale for all v2 Plan Tab design decisions. Each item maps to a review criterion.
### 1. Sidebar Width: 240px (consistent across tabs)
**Problem**: Content tab v1 used 192px for the page tree. Plan tab v1 was 260px. The 68px discrepancy means the divider line jumps when switching tabs -- jarring in a tabbed layout sharing the same viewport.
**Resolution**: Both Content tab and Plan tab now use **240px**. The divider line is stable across tab switches. Rationale:
- Phase cards carry status badges, task counts, and dependency indicators. 192px was too cramped.
- Content tab v2 independently moved to 240px to accommodate deep nesting + breadcrumb truncation.
- The convergence at 240px is incidental but correct -- both sidebars have similar information density now.
- **240px is the canonical sidebar width for all tabbed views within initiative detail.** Any future tabs with sidebars should use 240px unless there is a strong reason to deviate.
### 2. Three-Way Empty State Split (no phases / planning / planning failed)
**Problem**: v1 had no way to tell the user "planning failed." The agent could crash silently, leaving the user staring at a "No phases yet" screen with no signal.
**Resolution**: Added a third empty state -- "Planning failed" with `AlertTriangle` icon, error message, `[Retry Planning]` primary button, and `[View Log]` outline button. Detection: query the most recent plan-category agent for this initiative, check `status === 'crashed'`.
The three states form a clean state machine:
- `!isPlanRunning && !planFailed && phases.length === 0` -> "No phases yet" (with action buttons)
- `isPlanRunning && phases.length === 0` -> "Planning in progress..." (no action buttons)
- `!isPlanRunning && planFailed && phases.length === 0` -> "Planning failed" (retry + log buttons)
**Partial failure edge case**: If the agent crashes *after* creating some phases, the empty states do not apply (phases exist). Instead, a dismissible `AlertBanner` appears at the top of the detail panel: "Planning agent crashed after creating N phases. [Retry] [Dismiss]". This is critical -- without it, partial failures are invisible.
**Error context**: The failed empty state now shows a truncated error reason extracted from the agent's last output (e.g., "API rate limit exceeded"). This saves the user from having to click "View Log" just to understand *why* it failed.
### 3. Task Tree: Parent-Child + Dependency Visualization
**Problem**: The v1 flat task list gave no signal about task structure. Users managing 10+ agents need to understand the DAG at a glance -- which tasks decompose into subtasks, which tasks block other tasks.
**Resolution**: The task tree now communicates two distinct relationships:
- **Parent-child decomposition**: Indented rows under parent tasks (uses `parentTaskId` from the schema). Collapsible per parent. The `child:` prefix distinguishes these from agent assignment lines.
- **Dependency blocking**: `← blocked by: <task-name>` annotations below blocked tasks. The blocking task name is a clickable link that scrolls to and highlights the blocking task.
- **Cross-phase dependencies**: When a blocking task lives in a different phase, the annotation includes the phase name and clicking it switches the sidebar selection.
- **Collapse behavior**: Parents with >= 4 children default to collapsed, showing a child count badge. This prevents the tree from becoming unwieldy in heavily-decomposed phases.
**What this is NOT**: This is not a full DAG graph. The Plan tab shows a *tree* (hierarchical nesting + inline annotations), not a topological graph with SVG arrows. That is the Execution tab's job. The Plan tab's job is *editing* the structure, not *visualizing* execution order. The inline `← blocked by:` annotations are sufficient for understanding dependency relationships during planning; the full pipeline visualization belongs to the Execution tab.
**Known limitation**: The tree representation cannot fully express a DAG with convergent dependencies (task C depends on both A and B, which are not parent-child related). The inline annotations handle this for display, but visually it is not as clear as a graph. This is an acceptable tradeoff -- the Plan tab prioritizes editability over visualization fidelity.
### 4. Button Hierarchy: "Plan with Agent" (primary) vs "Add Phase" (outline)
**Problem**: Both buttons were visually ambiguous about which action is recommended.
**Resolution**: Explicit variant assignment:
- `Plan with Agent` -- `variant="default"` (indigo filled, primary CTA). This is the happy path. New users should click this first.
- `Add Phase` -- `variant="outline"` (secondary). This is for power users who want manual control.
The sparkles icon on "Plan with Agent" already signals "AI-powered," and making it the filled button reinforces it as the recommended action. The outline variant for "Add Phase" correctly communicates "alternative path."
### 5. Review Banner: Proposal Count
**Problem**: "Agent has proposals ready for review" is vague. Is it 1 proposal or 50? The user can't gauge urgency without switching tabs.
**Resolution**: Show the count: "5 proposals ready for review." Both the top-level banner and the per-phase banner now include counts. The count comes from `pendingProposals.data?.length` which is already fetched by the query.
### 6. Phase Reordering (drag-and-drop)
**Problem**: No way to reorder phases after creation. If the user adds phases manually or the agent creates them in the wrong order, there's no recourse.
**Resolution**: Added `[≡]` toggle button in the sidebar header. When active:
- Phase cards become draggable (via `@dnd-kit/sortable`)
- Sidebar gets a subtle `bg-muted` highlight to indicate mode change
- Drag handles appear on each card
- Drop triggers `trpc.updatePhaseSortOrder` mutation
- Toggle off returns to normal selection behavior
The toggle approach (vs. always-draggable) is intentional: drag-and-drop interferes with click-to-select in a sidebar this narrow. You don't want accidental drags when you're trying to navigate.
### 7. Phase Status Summary in Sidebar Cards
**Problem**: Sidebar cards showed task count but not progress. "5 tasks" tells you nothing about completion state.
**Resolution**: Changed to `N/M tasks` format (e.g., "2/5 tasks", "3/3 tasks"). When all tasks are complete (N === M), the text switches from `text-muted-foreground` to `text-status-success-fg` for a visual "done" signal without adding another badge.
### 8. Bulk Task Operations
**Problem**: No way to operate on all tasks in a phase at once. If the agent decomposes a phase poorly, the user has to delete tasks one by one.
**Resolution**: `[...]` dropdown menu on the Tasks section header, expanded to include:
- "Select All" (Ctrl+A) -- enables checkbox mode for future multi-select operations
- "Delete All Tasks" -- nukes all tasks in the phase. `window.confirm()` unless Shift held.
- "Reset All to Pending" -- resets all non-running tasks back to pending. Essential after a batch failure where some tasks are stuck in error states.
- "Re-categorize All" -- submenu to change the category of all tasks in the phase.
- "Expand All Children" / "Collapse All Children" -- tree visibility toggles for heavily decomposed phases.
The Select All + checkbox mode lays groundwork for future multi-select operations (bulk delete selected, bulk re-categorize selected) without building the full multi-select UI now.
### 9. Task Inline Editing + Save State
**Problem**: The `<SaveIndicator>` existed for phase descriptions but not for task names. If users can edit task names inline, they need feedback that the edit persisted.
**Resolution**: Each task row gets its own micro save indicator (icon-only `<SaveIndicator>` -- checkmark or spinner, no text label). The indicator appears inline next to the task name input during editing and disappears after save completes. This uses the same `<SaveIndicator>` component with a `compact` prop that renders icon-only.
### 10. Keyboard Shortcut for Add Task (and friends)
**Problem**: In a keyboard-first mission control UI, requiring a mouse click to add a task is a friction point. Power users managing dozens of tasks want to stay on the keyboard.
**Resolution**: Full keyboard shortcut table added (see "Keyboard Shortcuts" section). Key decisions:
- `N` for new task (mnemonic: **N**ew). Scoped to task list focus.
- `E` for edit (mnemonic: **E**dit). Opens inline editing on the focused row.
- `Tab` during inline edit advances to the next task in edit mode (rapid-fire renaming).
- `Delete`/`Backspace` for delete. With confirm unless Shift held (consistent with mouse Shift+click pattern).
- `Arrow Up`/`Down` for row navigation. `Arrow Left`/`Right` for tree expand/collapse (borrowed from VS Code tree behavior).
- `Space` toggles task selection (multi-select mode). `Ctrl+A` selects all.
- `Enter` to open modal.
- `?` opens a shortcut overlay dialog (GitHub-style discoverability).
- All shortcuts are scoped to the task list container and disabled when an input is focused.
- The `[N]` badge hint next to "Add Task" button makes the primary shortcut discoverable. The `?` overlay covers the rest.
---
### 11. Missing: Per-Phase Empty State (phase exists, no tasks)
**Problem caught in review**: The spec documented empty states for "no phases" and "planning in progress/failed," but NOT for the case where a phase exists with zero tasks. The user selects a phase, sees the description, and then... a blank Tasks section with just the "Add Task" button. No guidance.
**Resolution**: Added "Empty State -- Phase Selected, No Tasks" section. The Tasks area (not full panel) shows a small inline empty state with two CTAs: "Detail with Agent" (primary, spawns decomposition) and "Add Task" (outline, manual). This mirrors the plan-level empty state pattern: AI-first is primary, manual is secondary.
### 12. Missing: Task Row Hover & Focus States
**Problem caught in review**: The spec defined keyboard shortcuts and inline editing but never documented what happens on mouse hover or keyboard focus. In a dense list UI, hover states are not cosmetic -- they communicate interactivity and reveal action affordances.
**Resolution**: Added "Task Row Hover & Focus States" section. Key decisions:
- Hover reveals `[play]` (queue) and `[x]` (delete) action buttons at row's right edge. Hidden by default to keep the list clean.
- Running tasks swap `[play]` for `[square]` (stop) and hide `[x]` (cannot delete running tasks).
- Keyboard focus uses a subtle `ring-1 ring-primary/50` outline (not `ring-2` -- that's too aggressive for navigation).
- Hover and focus states stack visually (both can be active on the same row).
### 13. Missing: Phase Detail Header Actions
**Problem caught in review**: The original phase header dropdown only had "Delete Phase." That is one action in a dropdown -- just use a button. A dropdown implies a menu with multiple options.
**Resolution**: Expanded the `[...]` dropdown to three actions:
- "Detail with Agent" -- spawns task decomposition for the selected phase
- "Duplicate Phase" -- deep-copies phase + tasks, appends "(copy)" suffix. Useful for templating similar phases (e.g., duplicating "Google OAuth" to create "GitHub OAuth" with the same task structure).
- "Delete Phase" -- destructive, red text, bottom of menu with separator above.
Now the dropdown earns its existence.
### 14. Concern: Task Description Editing in Modal Only
The current spec puts task description editing exclusively in the Task Detail Modal. For quick edits, opening a modal is friction. Consider adding expandable inline description editing (click to expand a 2-line textarea below the task row) in a future iteration. Not adding it now because the tree is already vertically dense and expandable descriptions would make it harder to scan. But log this as a known future improvement.
### 15. Concern: No Loading / Skeleton States Documented
The spec documents empty states comprehensively but says nothing about what the user sees while phases are *loading*. On a slow connection or large initiative, there is a flash of empty state before data arrives. The plan tab should show:
- Sidebar: 3 skeleton cards (pulsing `bg-muted animate-pulse`, matching card dimensions)
- Detail panel: skeleton lines for title + description + task list
This prevents the "No phases yet" empty state from flashing before the actual phases load, which would be confusing.
### 16. Implementation Note: Component Boundary Map
New or modified components for v2 Plan Tab:
| Component | Path (proposed) | New? |
|-----------|----------------|------|
| `PhaseSidebarItem` | `packages/web/src/components/execution/PhaseSidebarItem.tsx` | Modified -- add task progress, dep indicator, drag handle |
| `PhaseActions` | `packages/web/src/components/execution/PhaseActions.tsx` | Modified -- add `[≡]` toggle, `[Detail All]`, detailing badge |
| `TaskTree` | `packages/web/src/components/execution/TaskTree.tsx` | **New** -- replaces flat task list with nested tree |
| `TaskRow` | `packages/web/src/components/TaskRow.tsx` | Modified -- add inline edit, hover actions, focus state, child indent |
| `TaskRowInlineEdit` | `packages/web/src/components/execution/TaskRowInlineEdit.tsx` | **New** -- extracted edit mode for TaskRow |
| `BulkTaskMenu` | `packages/web/src/components/execution/BulkTaskMenu.tsx` | **New** |
| `PhaseEmptyTasks` | `packages/web/src/components/execution/PhaseEmptyTasks.tsx` | **New** -- per-phase "no tasks" empty state |
| `PlanTabSkeleton` | `packages/web/src/components/execution/PlanTabSkeleton.tsx` | **New** -- loading skeleton |
| `KeyboardShortcutOverlay` | `packages/web/src/components/KeyboardShortcutOverlay.tsx` | **New** -- `?` key dialog, reusable across tabs |

View File

@@ -11,7 +11,7 @@
|--------|----|----|
| Loading state | Plain "Loading diff..." text | Centered `[spinner]` + "Loading diff..." text |
| Error state | None | `[AlertCircle]` + error message + `[Retry]` button |
| Unresolved threads | Count in diff header only | Count in header + `[v]` jump-to-next button in sidebar |
| Unresolved threads | Count in diff header only | Count in header + `[ChevronUp]`/`[ChevronDown]` prev/next-unresolved buttons in sidebar |
| Empty state | "No changes in this phase" | `[check-circle]` icon + "No changes to review" + description |
---
@@ -20,50 +20,70 @@
```
+=============================================================================+
| Diff: src/auth/oauth.ts 3 unresolved | Threads [v] |
| ----------------------------------------------------------- 300px |
| @@ -12,6 +12,14 @@ | |
| import { generatePKCE } from './pkce'; | Thread #1 |
| +export async function authorize( | Line 14 |
| + client: OAuthClient, | "Should we |
| + scopes: string[] | validate |
| +) { | scopes?" |
| + const { verifier, challenge } = generatePKCE(); | [Reply___] |
| + // ... | [Resolve] |
| +} | |
| | Thread #2 |
| export function validateToken( | Line 28 |
| - token: string | "Add expiry |
| + token: string, | check here" |
| + options?: ValidateOptions | [RESOLVED] |
| | |
| Files (3) | Diff: src/auth/oauth.ts [Unified|Split] 3 unresolved |=| Threads [^] [v] |
| ---------------+-------------------------------------------------------------+-+ |
| * oauth.ts | @@ -12,6 +12,14 @@ |‖| Thread #1 |
| pkce.ts | import { generatePKCE } from './pkce'; |‖| Line 14 |
| index.ts | +export async function authorize( [+ comment] |‖| "Should we |
| | + client: OAuthClient, |‖| validate |
| | + scopes: string[] |‖| scopes?" |
| | +) { |‖| [Reply___________] |
| | + const { verifier, challenge } = generatePKCE(); |‖| [Resolve] |
| | + // ... |‖| |
| | +} |‖| Thread #2 |
| | |‖| Line 28 |
| | export function validateToken( |‖| "Add expiry |
| | - token: string |‖| check here" |
| | + token: string, |‖| [RESOLVED] |
| | + options?: ValidateOptions |‖| |
| | |‖| |
+=============================================================================+
^
resize handle (drag)
```
`[v]` is the "Jump to next unresolved" button. Clicking it scrolls the diff viewer
to the next unresolved comment thread. Disabled when all threads are resolved.
### Layout: three-pane
| Pane | Width | Notes |
|------|-------|-------|
| File tree | `w-48` (192px), collapsible | Lists all files in the diff. `*` marks files with unresolved threads. Click to navigate. |
| Diff viewer | flex-1 (fills remaining) | Unified or split view. Scrolls independently. |
| Threads sidebar | min `240px`, default `320px`, max `480px`, **resizable** via drag handle | `‖` is a 4px drag handle (`cursor-col-resize`). User can drag to resize. Stores width in `localStorage` key `cw-review-sidebar-width`. |
### Navigation buttons
`[^]` (ChevronUp) = "Previous unresolved". `[v]` (ChevronDown) = "Next unresolved".
Both scroll the diff viewer to the corresponding unresolved thread and highlight it.
Disabled when all threads are resolved. Wraps around (last -> first, first -> last).
Both have visible text labels on wider viewports: `[^ Prev] [Next v]`.
On narrow viewports (< 1280px), icon-only with tooltips: "Previous unresolved (Shift+N)" / "Next unresolved (N)".
### Diff header anatomy
```
+------------------------------------------------------------------------+
| Diff: <file-path> N unresolved |
| Diff: <file-path> [Unified | Split] N unresolved |
+------------------------------------------------------------------------+
```
- File path is the currently-focused file
- File path is the currently-focused file (bold `font-mono text-sm font-medium`)
- `[Unified | Split]` — segmented toggle, same style as `<ThemeToggle>`. Default: Unified. Persists in `localStorage` key `cw-review-view-mode`.
- **Unified**: standard diff with `+`/`-` lines interleaved (default)
- **Split**: side-by-side two-column view — old on left, new on right, synced scroll
- "N unresolved" uses `text-status-warning-fg` when N > 0, `text-muted-foreground` when 0
### Sidebar header anatomy
```
+---------------------------+
| Threads [v] |
+---------------------------+
+----------------------------------+
| Threads (3) [^] [v] |
+----------------------------------+
```
- `[v]` (ChevronDown icon) scrolls to next unresolved thread in diff viewer
- Tooltip: "Jump to next unresolved"
- `(3)` = total thread count for current file, `text-muted-foreground`
- `[^]` (ChevronUp icon) scrolls to previous unresolved thread. Tooltip: "Previous unresolved (Shift+N)"
- `[v]` (ChevronDown icon) scrolls to next unresolved thread. Tooltip: "Next unresolved (N)"
- Both buttons: `variant="ghost" size="icon-sm"`, disabled state when 0 unresolved
---
@@ -143,23 +163,23 @@ This replaces the v1 "No phases pending review" plain text.
| | [play] Start Preview | |
| +-----------------------------------------------------------+ |
| |
| Diff: src/auth/oauth.ts 3 unresolved | Threads [v] |
| ---------------------------------------------------+ 300px |
| @@ -12,6 +12,14 @@ | |
| import { generatePKCE } from './pkce'; | Thread #1 |
| +export async function authorize( | Line 14 |
| + client: OAuthClient, | "Should we |
| + scopes: string[] | validate |
| +) { | scopes?" |
| + const { verifier, challenge } = ... | [Reply___] |
| + // ... | [Resolve] |
| +} | |
| | Thread #2 |
| export function validateToken( | Line 28 |
| - token: string | "Add expiry |
| + token: string, | check here" |
| + options?: ValidateOptions | [RESOLVED] |
| | |
| Files (3) | Diff: src/auth/oauth.ts [Uni|Spl] 3 unresolved | Threads [^][v] |
| ----------+---------------------------------------------------+-+ |
| * oauth | @@ -12,6 +12,14 @@ |‖| Thread #1 |
| pkce | import { generatePKCE } from './pkce'; |‖| Line 14 |
| index | +export async function authorize( |‖| "Should we |
| | + client: OAuthClient, |‖| validate |
| | + scopes: string[] |‖| scopes?" |
| | +) { |‖| [Reply___] |
| | + const { verifier, challenge } = ... |‖| [Resolve] |
| | + // ... |‖| |
| | +} |‖| Thread #2 |
| | |‖| Line 28 |
| | export function validateToken( |‖| "Add expiry |
| | - token: string |‖| check here" |
| | + token: string, |‖| [RESOLVED] |
| | + options?: ValidateOptions |‖| |
| | |‖| |
+=============================================================================+
```
@@ -170,29 +190,302 @@ This replaces the v1 "No phases pending review" plain text.
### Unresolved thread (in sidebar)
```
+---------------------------+
| Thread #1 |
| Line 14 |
| "Should we validate |
| scopes?" |
| [Reply_______________] |
| [Resolve] |
+---------------------------+
+----------------------------------+
| Thread #1 [x] |
| Line 14 · agent: blue-fox-7 |
| "Should we validate |
| scopes before passing |
| to the provider?" |
| |
| [Reply_____________________] |
| [Resolve] |
+----------------------------------+
```
### Resolved thread (in sidebar)
- `[x]` = delete thread (destructive, Shift+click to skip confirmation per UI Patterns)
- Line number is a clickable link — scrolls diff viewer to that line and highlights it
- Agent attribution shows who created the thread (`text-xs text-muted-foreground font-mono`)
- Reply input: `<Textarea>` with `rows={1}`, auto-expands. Submit with `Cmd+Enter`.
- `[Resolve]` button: `variant="outline" size="sm"`
### Resolution transition (animation)
When `[Resolve]` is clicked:
1. **Immediate**: `[Resolve]` button replaced with `[RESOLVED]` badge (no delay)
2. **300ms**: Thread card fades to `opacity-50` via `transition-opacity duration-300`
3. **400ms**: Thread collapses to single-line summary via `transition-all duration-400`:
```
+---------------------------+
| Thread #2 |
| Line 28 |
| "Add expiry check here" |
| [RESOLVED] |
+---------------------------+
+----------------------------------+
| [v] Thread #2 · Line 28 |
| [RESOLVED] |
+----------------------------------+
```
- Resolved threads are visually dimmed: `opacity-60`
4. Collapsed resolved threads can be expanded by clicking `[v]` (ChevronRight) to reveal full content
5. The corresponding diff gutter marker transitions from `bg-status-warning-dot` to `bg-status-success-dot`
6. Unresolved count in header decrements immediately (optimistic update)
### Resolved thread — collapsed (default)
```
+----------------------------------+
| [>] Thread #2 · Line 28 |
| [RESOLVED] |
+----------------------------------+
```
### Resolved thread — expanded
```
+----------------------------------+
| [v] Thread #2 · Line 28 |
| "Add expiry check here" |
| [RESOLVED] [Unresolve] |
+----------------------------------+
```
- Resolved threads: `opacity-50`, collapsed by default
- `[RESOLVED]` badge uses `bg-status-success-bg text-status-success-fg`
- `[Unresolve]` text button: `text-xs text-muted-foreground hover:text-foreground` — reverts to unresolved state
- `[>]`/`[v]` toggle: `ChevronRight`/`ChevronDown`, `variant="ghost" size="icon-xs"`
---
## Inline Comment Creation
How a user starts a new review thread on a diff line.
### Trigger: click on gutter line number
Every diff line has a clickable gutter area (line number column). Hovering a line shows a `[+]` icon in the gutter (`opacity-0 group-hover:opacity-100`, `text-primary`). Clicking it opens an inline comment form directly below that diff line.
```
| 14 | +export async function authorize(
| | +---------------------------------------------+
| | | [avatar] Add a comment... |
| | | [_____________________________________] |
| | | [Cancel] [Submit] |
| | +---------------------------------------------+
| 15 | + client: OAuthClient,
```
### Trigger: select text in diff, then click "Comment"
Selecting text within a diff line reveals a floating toolbar above the selection:
```
| 14 | +export async function ████████████(
+----------+
| [Comment]|
+----------+
```
Clicking `[Comment]` opens the inline form (same as above) with the selected text quoted in the input as a blockquote prefix.
### Inline comment form anatomy
```
+----------------------------------------------+
| [textarea: placeholder "Add a comment..."] |
| |
| [Cancel] [Comment] |
+----------------------------------------------+
```
- Textarea: `<Textarea rows={2}>`, auto-expands, max 6 rows. `font-mono text-sm`.
- `[Cancel]`: `variant="ghost" size="sm"` — closes form, discards input
- `[Comment]`: `variant="default" size="sm"` — creates thread, scrolls sidebar to show it
- `Cmd+Enter` submits, `Escape` cancels
- On submit: new thread appears in sidebar, gutter line gets a thread marker dot (`bg-status-warning-dot`)
### Gutter thread markers
Lines with existing threads show a colored dot in the gutter:
```
| 12 | import { generatePKCE } from './pkce';
| •14 | +export async function authorize( <- dot = thread on this line
| 15 | + client: OAuthClient,
```
- Unresolved thread dot: `bg-status-warning-dot` (amber)
- Resolved thread dot: `bg-status-success-dot` (green)
- Clicking the dot scrolls the sidebar to that thread and highlights it
---
## File Navigation (File Tree Pane)
Left pane listing all files changed in the current phase diff.
### File tree anatomy
```
+-------------------+
| Files (3) |
| ───────────── |
| * oauth.ts +5-2 | <- selected (bold), has unresolved threads (*)
| pkce.ts +28 | <- additions only
| index.ts +1-1 |
+-------------------+
```
- Pane width: `w-48` (192px), collapsible via `[PanelLeftClose]` icon in pane header
- Collapsed state stores in `localStorage` key `cw-review-files-collapsed`
- `*` prefix on files with unresolved threads (uses `text-status-warning-fg`)
- `+N-M` shows addition/deletion line counts (`text-diff-add-fg` / `text-diff-remove-fg`)
- Selected file: `bg-accent font-medium`
- File paths truncated with ellipsis (`truncate`) — full path in tooltip
- Sort order: files with unresolved threads first, then alphabetical
- Keyboard: `j`/`k` to move selection, `Enter` to open file in diff viewer
### When file tree is collapsed
```
+--+
|[>| <- PanelLeftOpen icon, click to expand
+--+
```
Diff header still shows the current file path so context is never lost.
---
## Diff View Modes
### Unified view (default)
Standard interleaved diff — additions and deletions inline, single column. Same as shown in Default State wireframe above.
### Split view
Side-by-side two-column layout — old file on left, new file on right. Scroll is synced.
```
+------------------------------------------------------------------+
| Diff: src/auth/oauth.ts [Unified | *Split*] 3 unresolved |
+------------------------------------------------------------------+
| OLD (left) | NEW (right) |
| 12 import { generatePKCE } | 12 import { generatePKCE } |
| 13 // ... | 13 // ... |
| 14 ░░░░░░░░░░░░░░░░░░░░░░░░░ | 14 +export async function |
| ░░░░░░░░░ (empty) | 15 + client: OAuthClient, |
| ░░░░░░░░░ | 16 + scopes: string[] |
| ░░░░░░░░░ | 17 +) { |
| | |
| 28 - token: string | 31 + token: string, |
| | 32 + options?: ValidateOpt |
+------------------------------------------------------------------+
```
- Empty lines on the shorter side filled with `bg-muted/30` hatching
- Removed lines: `bg-diff-remove-bg` on left only
- Added lines: `bg-diff-add-bg` on right only
- Each side has its own line numbers
- Gutter comment markers (`[+]` on hover, thread dots) appear on both sides
---
## Hunk-Level Controls
Each hunk (`@@ ... @@` header) has accept/reject controls for bulk-accepting or rejecting changes within that hunk.
### Hunk header anatomy
```
+------------------------------------------------------------------------+
| @@ -12,6 +12,14 @@ function setup() [Accept Hunk] [Reject Hunk] |
+------------------------------------------------------------------------+
```
- `[Accept Hunk]`: `variant="ghost" size="xs"`, `CheckCheck` icon + "Accept". Marks all changes in this hunk as accepted — lines get `bg-diff-add-bg/40` (dimmed, signaling "reviewed").
- `[Reject Hunk]`: `variant="ghost" size="xs"`, `X` icon + "Reject". Opens a confirmation: "Reject this hunk? This will create a revert suggestion." On confirm, creates a thread with a revert suggestion.
- Both buttons: `opacity-0 group-hover:opacity-100` (visible on hunk header hover)
- Accepted hunks show a subtle `[check]` icon in the hunk header
- Hunk header background: `bg-diff-hunk-bg` (from theme.md diff tokens)
---
## Partial Diff State (agent still working)
When an agent is actively running and producing changes, the diff may be incomplete. This state shows what is available so far with a clear indicator that more changes are expected.
```
+=============================================================================+
| Files (1) | Diff: src/auth/oauth.ts [Uni|Spl] 0 unresolved | ... |
| -----------+-----------------------------------------------------+ |
| ~ oauth.ts | @@ -12,6 +12,8 @@ | |
| | +export async function authorize( | |
| | + client: OAuthClient, | |
| | | |
| +-----------------------------------------------------+ |
| | [spinner] Agent blue-fox-7 is still working... | |
| | Changes may be incomplete. Auto-refreshes on save. | |
+=============================================================================+
```
- `~` prefix on files being actively modified (uses `text-status-active-fg`)
- Bottom banner: `bg-status-active-bg border-t border-status-active-border`
- `[spinner]` is `Loader2 animate-spin` inline with text
- Diff auto-refreshes when new output detected (via `onAgentOutput` subscription)
- Thread creation is allowed on partial diffs — threads persist even as diff updates
- Banner disappears when agent completes (status transitions away from `running`)
---
## Syntax Highlighting
The diff viewer uses syntax-highlighted code rendering. Colors are derived from the **terminal tokens** defined in `theme.md` section 3, ensuring consistent code coloring across the agent output viewer and the diff viewer.
| Code element | Token | Example |
|-------------|-------|---------|
| Keywords (`export`, `async`, `function`, `const`) | `--terminal-tool` (blue) | `text-terminal-tool` |
| Strings | `--terminal-result` (green) | `text-terminal-result` |
| Comments | `--terminal-muted` (dimmed) | `text-terminal-muted` |
| Types / annotations | `--terminal-system` (grey) | `text-terminal-system` |
| Errors / invalids | `--terminal-error` (red) | `text-terminal-error` |
| Default / identifiers | `--foreground` | `text-foreground` |
Implementation note: use a lightweight syntax highlighter (e.g., `shiki` with the token mapping above, or a custom Prism theme wired to CSS custom properties). The diff background colors (`--diff-add-bg`, `--diff-remove-bg`) must remain visible through the syntax highlighting — syntax colors are applied to `color` only, never `background-color`.
---
## Keyboard Shortcuts
Review-tab-specific shortcuts, active when the Review tab is focused.
| Key | Action | Notes |
|-----|--------|-------|
| `n` | Next unresolved thread | Same as `[v]` button. Wraps around. |
| `Shift+N` | Previous unresolved thread | Same as `[^]` button. Wraps around. |
| `j` / `k` | Next / previous file in file tree | Moves file selection, loads diff |
| `]` / `[` | Next / previous hunk | Scrolls diff to next/prev `@@` header |
| `r` | Resolve focused thread | Only when a thread is focused in sidebar |
| `c` | Open comment on current line | Opens inline comment form at cursor line |
| `Escape` | Close inline comment / deselect | Context-dependent |
| `Cmd+Enter` | Submit comment / reply | When comment textarea is focused |
| `u` | Toggle unified/split view | Same as `[Unified|Split]` toggle |
| `f` | Toggle file tree pane | Collapse/expand the file tree |
| `?` | Show keyboard shortcut overlay | Lists all shortcuts in a popover |
Shortcuts are displayed in a help popover (triggered by `?` key) anchored to the bottom-right of the review tab.
```
+------------------------------------+
| Review Shortcuts |
| ────────────── |
| n / Shift+N Next/prev thread |
| j / k Next/prev file |
| ] / [ Next/prev hunk |
| r Resolve thread |
| c Add comment |
| u Toggle view mode |
| f Toggle file tree |
| ? This help |
+------------------------------------+
```
---
@@ -200,6 +493,77 @@ This replaces the v1 "No phases pending review" plain text.
- `packages/web/src/components/review/ReviewTab.tsx`
- `packages/web/src/components/review/DiffViewer.tsx`
- `packages/web/src/components/review/DiffViewerSplit.tsx` (proposed — split view variant)
- `packages/web/src/components/review/ReviewSidebar.tsx`
- `packages/web/src/components/review/ReviewFileTree.tsx` (proposed — file navigation pane)
- `packages/web/src/components/review/InlineCommentForm.tsx` (proposed — inline comment creation)
- `packages/web/src/components/review/HunkHeader.tsx` (proposed — hunk-level controls)
- `packages/web/src/components/review/PreviewPanel.tsx`
- `packages/web/src/components/review/FileCard.tsx`
- `packages/web/src/components/review/ReviewShortcutHelp.tsx` (proposed — keyboard shortcut overlay)
---
## Design Review Notes
This section documents design decisions, trade-offs, and open questions identified during review.
### 1. Sidebar width: fixed 300px was inadequate
**Problem**: A fixed 300px sidebar cannot accommodate long comments, code snippets in replies, or multi-reply threads without severe cramping. At 300px with padding, you get roughly 35 characters per line — barely enough for a sentence.
**Resolution**: Changed to resizable sidebar with drag handle. Default 320px, min 240px, max 480px. Width persists in localStorage. The drag handle (`‖`) uses `cursor-col-resize` and is 4px wide — thin enough to not waste space, wide enough to be a reliable grab target.
### 2. Jump-to-next button was ambiguous
**Problem**: The `[v]` icon (ChevronDown) alone is ambiguous — could mean "collapse", "dropdown", or "scroll down". Only having "next" with no "previous" forces users to cycle through all threads to go back one.
**Resolution**: Added both `[^]` (ChevronUp) and `[v]` (ChevronDown) with tooltips that include the keyboard shortcut. On wider viewports, text labels are shown: `[^ Prev] [Next v]`. Keyboard shortcuts `n` / `Shift+N` map to these.
### 3. Thread resolution had no visual feedback
**Problem**: Clicking "Resolve" with no documented visual transition leaves the user guessing — did it work? Where did the thread go?
**Resolution**: Documented a four-step animation: immediate badge swap, opacity fade (300ms), collapse to single-line summary (400ms), and gutter marker color change (amber to green). Also added `[Unresolve]` for reverting mistakes — resolution should not be a one-way door.
### 4. Inline comment creation was completely missing
**Problem**: The spec documented viewing and resolving threads but never explained how to CREATE one. This is the most fundamental interaction in a review workflow.
**Resolution**: Two creation paths documented: (a) click `[+]` gutter icon on any diff line, (b) select text and click floating `[Comment]` toolbar. Both open an inline form below the diff line. Gutter markers (colored dots) provide persistent visual indicators of where threads exist.
### 5. Multi-file navigation was absent
**Problem**: Real-world diffs span dozens of files. Without a file tree or file tabs, the user has no way to navigate between files or see which files need attention.
**Resolution**: Added a collapsible left pane (`w-48`) with a file tree. Files with unresolved threads are marked with `*` and sorted first. Addition/deletion counts are color-coded. Keyboard navigation with `j`/`k`.
### 6. No unified vs. split view toggle
**Problem**: Developers have strong preferences here. Unified is better for small changes; split is better for large refactors. Not offering both is a deal-breaker for power users.
**Resolution**: Added `[Unified | Split]` segmented toggle in the diff header. Split view wireframed with synced scroll, separate line numbers, and hatched empty-line fills. Preference persists in localStorage. Keyboard shortcut `u` for fast toggling.
### 7. "Agent still working" state was undocumented
**Problem**: The empty state covered "no changes" and the error state covered failures, but the common case of "agent is actively producing changes" was missing. Users would see a partial diff with no explanation.
**Resolution**: Added "Partial Diff State" section. Files being modified get a `~` prefix (active blue). A bottom banner with spinner explains the situation. Diff auto-refreshes as the agent works. Threads created on partial diffs are preserved across updates.
### 8. No per-hunk accept/reject
**Problem**: Thread resolution is per-comment, but code review also needs per-change-block controls. Without hunk-level accept/reject, users cannot efficiently signal "this chunk looks good" vs. "this chunk needs rework."
**Resolution**: Added `[Accept Hunk]` / `[Reject Hunk]` controls in each `@@` hunk header. Accept dims the hunk to signal "reviewed." Reject opens a confirmation and creates a revert suggestion thread. Controls are hover-revealed to keep the UI clean.
### 9. No keyboard shortcuts
**Problem**: The spec is for a "keyboard-first" mission control tool, but the review tab had zero keyboard shortcuts documented. Mouse-only review is slow and antithetical to the design stance.
**Resolution**: Full keyboard shortcut table added: `n`/`Shift+N` for thread navigation, `j`/`k` for file navigation, `]`/`[` for hunk navigation, `r` for resolve, `c` for comment, `u` for view toggle, `f` for file tree toggle. `?` opens a help overlay.
### 10. Syntax highlighting not specified
**Problem**: The diff viewer was plain text with only `+`/`-` coloring. No mention of syntax highlighting, despite the theme spec already defining terminal tokens suitable for code coloring.
**Resolution**: Added a syntax highlighting section mapping code elements to terminal tokens from `theme.md`. This ensures visual consistency between agent output and diff review. Important constraint documented: syntax colors on `color` only, never `background-color`, so diff add/remove backgrounds remain visible.

View File

@@ -1,7 +1,7 @@
# Settings Pages (v2)
### Route: `/settings/*`
### Source: `packages/web/src/routes/settings.tsx`, `packages/web/src/routes/settings/projects.tsx`
### Source: `packages/web/src/routes/settings.tsx`, `packages/web/src/routes/settings/*.tsx`
---
@@ -9,13 +9,18 @@
| Aspect | v1 | v2 |
|--------|----|----|
| Sub-pages | Health Check, Projects (2 pages) | Health Check, Accounts, Projects, Workspace, Danger Zone (5 pages) |
| Nav style | Plain text tabs | Icon + label tabs with active indicator |
| Projects error state | None | `[AlertCircle]` + "Failed to load projects" + `[Retry]` |
| Delete button | Immediate (with `window.confirm`) | Shows `[spinner]` during mutation to prevent double-click |
| Health Check | Unchanged | Unchanged |
| Delete button | Immediate (with `window.confirm`) | Shows `[spinner]` during mutation, toast "Deleted" on success before card animates out |
| Health Check | Basic server status only | Server vitals (uptime, PID, process count, DB size), version info |
| Accounts | Embedded in Health Check | Dedicated sub-page with add/remove actions, exhaustion status, usage bars |
| Workspace | None | `.cwrc` settings: workspace name, default execution mode |
| Danger Zone | None | Reset workspace, clear all data, destructive operations |
---
## Settings Layout (unchanged)
## Settings Layout
```
+=============================================================================+
@@ -23,7 +28,8 @@
+=============================================================================+
| |
| Settings |
| [ Health Check ] [*Projects*] |
| [heart] Health [key] Accounts [folder] Projects |
| [sliders] Workspace [alert-triangle] Danger Zone |
| ----------------------------------------------------------------------- |
| |
| <Settings Page Outlet> |
@@ -31,31 +37,196 @@
+=============================================================================+
```
### Sub-page nav anatomy
- Each tab: Lucide icon (16px) + label, `gap-1.5`
- Active: `bg-muted text-foreground font-medium rounded-md px-3 py-1.5`
- Inactive: `text-muted-foreground hover:text-foreground px-3 py-1.5`
- Danger Zone tab: icon and text use `text-destructive` when active (not `text-foreground`)
- Tabs wrap to two rows naturally on narrower viewports (no breakpoint logic)
### Sub-page icons
| Sub-page | Lucide Icon | Route |
|----------|-------------|-------|
| Health | `HeartPulse` | `/settings/health` |
| Accounts | `KeyRound` | `/settings/accounts` |
| Projects | `FolderGit2` | `/settings/projects` |
| Workspace | `SlidersHorizontal` | `/settings/workspace` |
| Danger Zone | `AlertTriangle` | `/settings/danger` |
`/settings` redirects to `/settings/health`.
---
## Health Check -- Default State
```
+=============================================================================+
| Settings |
| [*heart*] Health [key] Accounts [folder] Projects ... |
| ----------------------------------------------------------------------- |
| [ Refresh ] |
| |
| SERVER VITALS |
| +-----------------------------------------------------------------------+ |
| | [CheckCircle] Running | |
| | | |
| | Uptime 2d 14h 32m PID 48291 | |
| | Started 2026-03-01 08:14 Process count 3 | |
| | Version 0.9.2 Database 4.2 MB | |
| +-----------------------------------------------------------------------+ |
| |
| REGISTERED PROJECTS (3) |
| +-----------------------------------------------------------------------+ |
| | [CheckCircle] backend github.com/org/backend-api Clone found | |
| | [CheckCircle] frontend github.com/org/frontend-app Clone found | |
| | [XCircle] shared github.com/org/shared-lib Clone missing| |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
### Server vitals card anatomy
Two-column key/value grid inside a single card. Keys are `text-xs text-muted-foreground`, values are `text-sm font-mono`.
| Field | Source | Notes |
|-------|--------|-------|
| Status | Always "Running" (if the page loads, the server is up) | `[CheckCircle]` success icon |
| Uptime | `server.uptime` from `systemHealthCheck` | Formatted as `Xd Xh Xm` |
| Started | `server.startedAt` | ISO datetime, formatted to locale |
| PID | `process.pid` from `status` endpoint | Integer |
| Process count | `health.processCount` | Active agent processes |
| Version | Package version from build | `font-mono` |
| Database | File size of SQLite `.db` file | Formatted as `X.X MB` |
The projects summary in health check is a read-only compact list (no edit actions). For full project management, use the Projects sub-page.
### Health Check -- Error State
```
+=============================================================================+
| Settings |
| [*heart*] Health [key] Accounts [folder] Projects ... |
| ----------------------------------------------------------------------- |
| |
| [AlertCircle] |
| Failed to load health data |
| |
| [ Retry ] |
| |
+=============================================================================+
```
Uses shared `<ErrorState>` component with `onRetry`.
---
## Accounts -- Default State
```
+=============================================================================+
| Settings |
| [heart] Health [*key*] Accounts [folder] Projects ... |
| ----------------------------------------------------------------------- |
| [ Refresh ] [ Add ] |
| |
| +-----------------------------------------------------------------------+ |
| | [CheckCircle] alice@company.com | |
| | [claude] [Pro] 2 agents (1 active) Available | |
| | | |
| | Session (5h) [========--------] 62% resets in 2h 14m | |
| | Weekly (7d) [=====-----------] 38% resets in 4d 11h | |
| | [trash] | |
| +-----------------------------------------------------------------------+ |
| |
| +-----------------------------------------------------------------------+ |
| | [AlertTriangle] bob@company.com | |
| | [claude] [Max] 1 agent (0 active) Exhausted | |
| | until 2:45 PM | |
| | | |
| | Session (5h) [================] 98% resets in 0h 32m | |
| | Weekly (7d) [=============---] 88% resets in 1d 6h | |
| | [trash] | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
### Account card anatomy
Reuses the existing `<AccountCard>` component with two additions:
1. A `[trash]` delete button (bottom-right of card) with Shift+click bypass pattern
2. Exhaustion status uses `status-warning-*` tokens when exhausted
### Add Account dialog
`[ Add ]` opens a dialog:
```
+----------------------------------------------+
| Add Account [x] |
| |
| Provider [claude_____________v] |
| Email [_____________________] |
| |
| [ Cancel ] [ Add Account ]|
+----------------------------------------------+
```
- Provider dropdown populated from `listProviderNames` tRPC query
- Email is a text input, required
- On success: toast "Account added", invalidate `listAccounts`
### Accounts -- Empty State
```
+=============================================================================+
| Settings |
| [heart] Health [*key*] Accounts [folder] Projects ... |
| ----------------------------------------------------------------------- |
| |
| +-----------------------------------------------------------------------+ |
| | | |
| | [KeyRound] | |
| | No accounts registered | |
| | Use "cw account add" or click below. | |
| | | |
| | [ Add Account ] | |
| | | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
Uses shared `<EmptyState>` with `KeyRound` icon.
---
## Projects -- Default State
```
+=============================================================================+
| Settings |
| [ Health Check ] [*Projects*] |
| [heart] Health [key] Accounts [*folder*] Projects ... |
| ----------------------------------------------------------------------- |
| [ Register Project ] |
| |
| +-----------------------------------------------------------------------+ |
| | backend | |
| | github.com/org/backend-api | |
| | backend [pencil] | |
| | github.com/org/backend-api [external-link] | |
| | | |
| | Default branch: main [pencil] [trash] | |
| | Default branch: main [pencil] | |
| | Initiatives: Auth Overhaul, Payments [trash] | |
| +-----------------------------------------------------------------------+ |
| |
| +-----------------------------------------------------------------------+ |
| | frontend | |
| | github.com/org/frontend-app | |
| | frontend [pencil] | |
| | github.com/org/frontend-app [external-link] | |
| | | |
| | Default branch: develop [pencil] [trash] | |
| | Default branch: develop [pencil] | |
| | Initiatives: Auth Overhaul [trash] | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
@@ -65,17 +236,29 @@
```
+------------------------------------------------------------------------+
| <project-name> |
| <project-url> |
| <project-name> [pencil] |
| <project-url> [external-link] |
| |
| Default branch: <branch> [pencil] [trash] |
| Default branch: <branch> [pencil] |
| Initiatives: <name>, <name> | [0 if none] [trash] |
+------------------------------------------------------------------------+
```
- `[pencil]` (Pencil icon): click to toggle inline edit mode
- `[trash]` (Trash2 icon): click triggers `window.confirm()`; Shift+click bypasses
- Project name: `text-sm font-semibold`. The `[pencil]` next to it toggles inline name editing.
- Project URL: clickable `<a>` link, opens in new tab. `[external-link]` (ExternalLink icon, 12px) after URL. `text-xs text-muted-foreground hover:text-foreground underline-offset-2 hover:underline`.
- Default branch `[pencil]`: click to toggle inline branch editing (same as v1).
- Initiatives row: `text-xs text-muted-foreground`. Each initiative name is a link to `/initiatives/$id`. Shows "No initiatives" in dim text if none linked. Data from a new `getProjectInitiatives` query or joined from existing data.
- `[trash]` (Trash2 icon): click triggers `window.confirm()`; Shift+click bypasses. On successful delete, shows `toast.success("Project deleted")` before card animates out with `animate-out fade-out slide-out-to-left` (150ms).
### Inline branch editing (click pencil)
### Inline name editing (click pencil next to name)
```
| [backend__________] [enter to save / esc to cancel] |
```
Same pattern as branch editing. Validates uniqueness client-side before save.
### Inline branch editing (click pencil next to branch)
```
| Default branch: [develop________] [enter to save / esc to cancel] |
@@ -88,7 +271,7 @@
```
+=============================================================================+
| Settings |
| [ Health Check ] [*Projects*] |
| [heart] Health [key] Accounts [*folder*] Projects ... |
| ----------------------------------------------------------------------- |
| |
| [AlertCircle] |
@@ -110,16 +293,18 @@
```
+------------------------------------------------------------------------+
| backend |
| github.com/org/backend-api |
| backend [pencil] |
| github.com/org/backend-api [external-link] |
| |
| Default branch: main [pencil] [spinner] |
| Default branch: main [pencil] |
| Initiatives: Auth Overhaul [spinner] |
+------------------------------------------------------------------------+
```
- `[trash]` icon replaced by `[spinner]` (`Loader2` with `animate-spin`) during `deleteMutation.isPending`
- Button is `disabled` to prevent double-click
- Only the card being deleted shows the spinner; other cards remain interactive
- On success: `toast.success("Project deleted")` fires, then the card animates out (`animate-out fade-out slide-out-to-left`, 150ms duration)
---
@@ -128,7 +313,7 @@
```
+=============================================================================+
| Settings |
| [ Health Check ] [*Projects*] |
| [heart] Health [key] Accounts [*folder*] Projects ... |
| ----------------------------------------------------------------------- |
| |
| +-----------------------------------------------------------------------+ |
@@ -152,7 +337,7 @@
```
+=============================================================================+
| Settings |
| [ Health Check ] [*Projects*] |
| [heart] Health [key] Accounts [*folder*] Projects ... |
| ----------------------------------------------------------------------- |
| [ Register Project ] |
| |
@@ -178,9 +363,133 @@ pencil/trash placeholders. Shimmer animation sweeps left-to-right on a 1.5s loop
---
## Workspace -- Default State
```
+=============================================================================+
| Settings |
| [heart] Health [key] Accounts [folder] Projects |
| [*sliders*] Workspace [alert-triangle] Danger Zone |
| ----------------------------------------------------------------------- |
| |
| GENERAL |
| +-----------------------------------------------------------------------+ |
| | Workspace root /Users/dev/my-project | |
| | Config file .cwrc (version 1) | |
| +-----------------------------------------------------------------------+ |
| |
| EXECUTION DEFAULTS |
| +-----------------------------------------------------------------------+ |
| | Default mode [REVIEW v] | |
| | New initiatives start in this execution mode. | |
| | | |
| | Merge approval [x] Require approval for merge tasks | |
| | Agents must get human sign-off before merging. | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
### Workspace settings fields
| Field | Type | Source | Notes |
|-------|------|--------|-------|
| Workspace root | Read-only | `workspaceRoot` from server context | Display only, not editable |
| Config file | Read-only | Parsed `.cwrc` | Shows filename + version |
| Default mode | Select dropdown | `.cwrc` (future) | `review` or `yolo` |
| Merge approval | Checkbox | `.cwrc` (future) | Default for new initiatives |
These fields require extending the `.cwrc` config schema (currently `{ version: 1 }` only). Until the backend supports these, the section renders as read-only with a "Coming soon" badge on the editable fields.
---
## Danger Zone
```
+=============================================================================+
| Settings |
| [heart] Health [key] Accounts [folder] Projects |
| [sliders] Workspace [*alert-triangle*] Danger Zone |
| ----------------------------------------------------------------------- |
| |
| [AlertTriangle] These actions are irreversible. |
| |
| +-----------------------------------------------------------------------+ |
| | Clear all agent data | |
| | Removes all agent records, log chunks, and output history. | |
| | Projects, initiatives, and tasks are preserved. | |
| | [ Clear Agents ]| |
| +-----------------------------------------------------------------------+ |
| |
| +-----------------------------------------------------------------------+ |
| | Reset workspace | |
| | Deletes ALL data: initiatives, tasks, agents, accounts, projects. | |
| | The .cwrc config file and git repos are preserved on disk. | |
| | [ Reset Workspace ] | |
| +-----------------------------------------------------------------------+ |
| |
+=============================================================================+
```
### Danger Zone card anatomy
```
+------------------------------------------------------------------------+
| <action-title> text-sm font-semibold |
| <description> text-xs text-muted-fg |
| <preserved-note> text-xs text-muted-fg |
| [ Destructive Button ] |
+------------------------------------------------------------------------+
```
- Cards use `border-destructive/30` border tint
- Buttons use `variant="destructive"` styling
- Each button triggers a `window.confirm()` with explicit confirmation text: "Type RESET to confirm"
- Shift+click bypasses (consistent with app-wide pattern)
- Top banner: `[AlertTriangle]` icon with `text-destructive`, `text-sm font-medium`
---
## Source
- `packages/web/src/routes/settings.tsx`
- `packages/web/src/routes/settings/health.tsx`
- `packages/web/src/routes/settings/accounts.tsx` (proposed)
- `packages/web/src/routes/settings/projects.tsx`
- `packages/web/src/routes/settings/workspace.tsx` (proposed)
- `packages/web/src/routes/settings/danger.tsx` (proposed)
- `packages/web/src/components/RegisterProjectDialog.tsx`
- `packages/web/src/components/AccountCard.tsx`
- `packages/web/src/components/AddAccountDialog.tsx` (proposed)
---
## Design Review Notes
### What was improved in this revision
1. **Sub-page expansion (2 to 5 pages).** The original spec only had Health Check and Projects, which was embarrassingly thin for a workspace managing 10+ agents, multiple API accounts, and multiple git repos. Added: Accounts (dedicated), Workspace settings, Danger Zone. Each maps to real backend capabilities that already exist (`accountProcedures`, `.cwrc` config, `systemHealthCheck`).
2. **Health Check redesigned as a vitals dashboard.** The original wireframe said "Health Check: Unchanged" and showed nothing. The implementation (`health.tsx`) already renders server status, accounts, and projects -- but the wireframe never documented any of it. Now specced: server uptime, PID, process count, DB size, version, and a compact project clone status list. Accounts moved out to their own dedicated sub-page (they deserve more than a read-only summary).
3. **Accounts promoted to first-class sub-page.** The codebase already has `AccountCard` with full usage bars, exhaustion tracking, and agent counts. The health check page was cramming all this into a secondary section. Now accounts get their own page with add/remove actions (the tRPC procedures `addAccount` / `removeAccount` already exist), making it actually useful instead of display-only.
4. **Project cards enriched.** Three additions: (a) inline name editing alongside inline branch editing -- if you can edit the branch, you should be able to edit the name; (b) project URL is now a clickable external link (it was just dead text before -- why show a URL you cannot click?); (c) initiative cross-reference row showing which initiatives use this project (answers "can I safely delete this?").
5. **Delete flow improved.** Added explicit toast confirmation ("Project deleted") before card animates out. The spinner-only approach was correct for preventing double-click, but the user gets zero feedback that the delete succeeded -- the card just vanishes. A brief toast bridges that gap.
6. **Sub-page navigation given visual identity.** Plain text tabs for 5+ items is a mess. Added Lucide icons per sub-page with the same active/inactive styling from app-layout nav. Danger Zone gets destructive-red treatment to warn before you even click.
7. **Workspace settings page.** Currently `.cwrc` is `{ version: 1 }` with zero user-facing config. This wireframe establishes the UI surface for workspace-level defaults (execution mode, merge approval) so when the config schema grows, the frontend is ready. Read-only fields (workspace root, config version) provide orientation.
8. **Danger Zone.** Every serious tool needs one. Two graduated destructive actions: clear agent data (preserves structure) and full workspace reset (nuclear option). Confirmation requires typing "RESET" to prevent accidents. The Shift+click bypass pattern still applies for power users.
### What was NOT added (and why)
- **Keyboard shortcuts remapping page**: Skipped. The app has maybe 5 keyboard shortcuts total (tab switching 1-4, Cmd+K). A remapping UI is premature -- there is not enough shortcut surface to justify a settings page. Revisit when shortcuts exceed 15+.
- **Import/export settings**: Skipped. The `.cwrc` file is already a JSON file at the workspace root that can be committed to git. There is nothing else to export -- accounts are per-machine (OAuth tokens), projects are per-workspace. A dedicated import/export UI would be solving a problem that does not exist yet.
- **Notification preferences**: Skipped. The app has no notification system. Adding a preferences page for a feature that does not exist is pure wireframe theater.
- **Provider configuration page**: Skipped. Provider presets are code-level config (`src/agent/providers/presets.ts`) with 7 built-in providers. There is no user-facing need to edit provider CLI args or session ID extraction patterns. If custom providers are needed later, that is a CLI config concern, not a settings UI concern.

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
Complete design system overhaul. Replaces the achromatic shadcn/ui defaults with an indigo-branded, status-aware, dark-mode-first token system.
<!-- v2.1 --> **Design rationale:** Indigo (#6366F1) was chosen deliberately — it sits between blue (trust/tech) and violet (creativity/intelligence), making it semantically appropriate for an AI orchestration tool. It is also perceptually distinct from every status hue in the system (blue-210, green-142, amber-38, red-0, purple-270), preventing brand/status confusion.
## Current State (v1 Problems)
```
@@ -28,6 +30,8 @@ Problems:
## 1. Color System — Indigo Brand (#6366F1)
<!-- v2.1 --> **Palette range note:** The core palette covers: indigo (brand), zinc (neutrals), and destructive red. For richer UI expression, an extended indigo scale is provided below the base tokens for use in gradients, illustrations, and emphasis treatments where `--primary` alone is insufficient.
### Light Mode
```css
@@ -61,6 +65,18 @@ Problems:
--ring: 239 84% 67%; /* indigo focus ring */
--radius: 0.375rem; /* 6px — down from 8px */
/* v2.1 — Extended indigo scale for gradients, highlights, hover states */
--indigo-50: 226 100% 97%; /* #EEF0FF — same as accent bg */
--indigo-100: 228 96% 93%; /* #DDD6FE */
--indigo-200: 232 92% 86%; /* #C4B5FD */
--indigo-300: 235 88% 78%; /* #A78BFA */
--indigo-400: 237 86% 72%; /* #818CF8 */
--indigo-500: 239 84% 67%; /* #6366F1 — primary */
--indigo-600: 243 75% 59%; /* #4F46E5 */
--indigo-700: 245 58% 51%; /* #4338CA */
--indigo-800: 244 47% 42%; /* #3730A3 */
--indigo-900: 242 47% 34%; /* #312E81 */
}
```
@@ -71,10 +87,10 @@ Problems:
--background: 240 6% 7%; /* #111114 */
--foreground: 240 5% 96%; /* #F4F4F5 */
--card: 240 5% 10%; /* #19191D — surface level 1 */
--card: 240 5% 11%; /* #1B1B1F — surface level 1 (+4%) */ <!-- v2.1 widened from 10% -->
--card-foreground: 240 5% 96%;
--popover: 240 5% 13%; /* #202025 — surface level 2 */
--popover: 240 5% 16%; /* #272729 — surface level 2 (+5%) */ <!-- v2.1 widened from 13% -->
--popover-foreground: 240 5% 96%;
--primary: 239 84% 67%; /* #6366F1 — same indigo */
@@ -95,19 +111,25 @@ Problems:
--border: 240 4% 16%;
--input: 240 4% 16%;
--ring: 239 84% 67%;
/* v2.1 — Surface level 3 for command palette and stacked overlays */
--surface-3: 240 5% 21%; /* #333336 */
}
```
### Surface Hierarchy (Dark Mode)
Three-tier elevation model using lightness alone (no box shadows in dark mode):
<!-- v2.1 --> Four-tier elevation model using lightness alone (no box shadows in dark mode). Steps widened from 3% to 4-5% for clearer visual separation — the original 7→10→13 (3% steps) was too subtle on most displays, especially at low brightness.
```
Level 0 --background 240 6% 7% #111114 Page background
Level 1 --card 240 5% 10% #19191D Cards, panels, sidebars
Level 2 --popover 240 5% 13% #202025 Dropdowns, tooltips, command palette
Level 1 --card 240 5% 11% #1B1B1F Cards, panels, sidebars (+4%)
Level 2 --popover 240 5% 16% #272729 Dropdowns, tooltips (+5%)
Level 3 --surface-3 240 5% 21% #333336 Command palette, overlaid menus (+5%)
```
<!-- v2.1 --> **Why widen the steps?** On a typical IPS panel at 200 nits, 3% lightness difference in the sub-15% range is nearly imperceptible. 4-5% steps guarantee that Level 0 vs Level 1 reads as distinct surfaces without squinting. Level 3 is new — the command palette and stacked dialogs need a surface above popover.
Visual reference:
```
@@ -115,10 +137,14 @@ Visual reference:
| Level 0 (7%) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ |
| |
| +------------------------------------------------------------------+ |
| | Level 1 (10%) ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | |
| | Level 1 (11%) ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | |
| | | |
| | +----------------------------------------------+ | |
| | | Level 2 (13%) ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ | | |
| | | Level 2 (16%) ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ | | |
| | | | | |
| | | +----------------------------------+ | | |
| | | | Level 3 (21%) ████████████████ | | | |
| | | +----------------------------------+ | | |
| | +----------------------------------------------+ | |
| | | |
| +------------------------------------------------------------------+ |
@@ -132,6 +158,10 @@ Visual reference:
Six semantic status sets with bg/fg/border/dot variants for both light and dark modes.
<!-- v2.1 --> **Hue separation analysis:** The six status hues are: active (210 blue), success (142 green), warning (38 amber), error (0 red), neutral (240 zinc), urgent (270 purple). The tightest hue gap is active-210 vs brand-indigo-239 (29 degrees apart) — acceptable because status bg/fg pairs are never used in brand contexts. The urgent-270 vs active-210 gap is 60 degrees, which is sufficient but can be tight under protanopia. Consider `--status-urgent` at 285-290 (magenta-shifted) if accessibility testing reveals confusion. The primary concern: `urgent` (purple) may read as "special/brand-related" since indigo is nearby. If this becomes an issue during implementation, shift urgent to 330 (hot pink/fuchsia) for maximum perceptual distance from both blue and indigo.
<!-- v2.1 --> **Missing status: `queued`.** With 10+ agents and many tasks, a "queued/dispatched but not yet started" state is common. Consider adding a 7th token: `queued` at hue 195 (cyan) — sits between active-blue and success-green, reads as "ready but not running."
### Light Mode
| Status | Use Case | bg | fg | border | dot |
@@ -324,10 +354,27 @@ For `AgentOutputViewer` — always-dark surface even in light mode (terminal aes
--terminal-tool: 217 91% 60%; /* [Tool Call] blue accent */
--terminal-result: 142 72% 45%; /* [Result] green accent */
--terminal-error: 0 84% 60%; /* [Error] red accent */
/* v2.1 — Additional terminal tokens */
--terminal-cursor: 120 100% 65%; /* blinking cursor color — bright green */
--terminal-selection-bg: 239 84% 67% / 0.25; /* indigo at 25% opacity */
--terminal-link: 217 91% 70%; /* clickable file paths, URLs */
--terminal-warning: 38 92% 60%; /* [Warning] amber accent */
--terminal-line-number: 240 5% 35%; /* gutter line numbers */
/* v2.1 — ANSI color overrides (for raw terminal output rendering) */
--terminal-ansi-black: 240 6% 7%;
--terminal-ansi-red: 0 84% 60%;
--terminal-ansi-green: 142 72% 45%;
--terminal-ansi-yellow: 38 92% 60%;
--terminal-ansi-blue: 217 91% 60%;
--terminal-ansi-magenta: 270 91% 65%;
--terminal-ansi-cyan: 195 80% 55%;
--terminal-ansi-white: 240 5% 85%;
}
.dark {
--terminal-bg: 240 5% 10%; /* matches card surface */
--terminal-bg: 240 5% 11%; /* matches card surface */ <!-- v2.1 updated from 10% to match new card -->
--terminal-fg: 120 100% 80%;
--terminal-muted: 240 5% 55%;
--terminal-border: 240 4% 16%;
@@ -337,6 +384,23 @@ For `AgentOutputViewer` — always-dark surface even in light mode (terminal aes
--terminal-tool: 217 91% 60%;
--terminal-result: 142 72% 45%;
--terminal-error: 0 84% 60%;
/* v2.1 — Additional terminal tokens (same values — always-dark context) */
--terminal-cursor: 120 100% 65%;
--terminal-selection-bg: 239 84% 67% / 0.25;
--terminal-link: 217 91% 70%;
--terminal-warning: 38 92% 60%;
--terminal-line-number: 240 5% 35%;
/* v2.1 — ANSI colors (same as light — terminal is always dark) */
--terminal-ansi-black: 240 6% 7%;
--terminal-ansi-red: 0 84% 60%;
--terminal-ansi-green: 142 72% 45%;
--terminal-ansi-yellow: 38 92% 60%;
--terminal-ansi-blue: 217 91% 60%;
--terminal-ansi-magenta: 270 91% 65%;
--terminal-ansi-cyan: 195 80% 55%;
--terminal-ansi-white: 240 5% 85%;
}
```
@@ -352,6 +416,11 @@ terminal: {
tool: 'hsl(var(--terminal-tool))',
result: 'hsl(var(--terminal-result))',
error: 'hsl(var(--terminal-error))',
// v2.1 additions
cursor: 'hsl(var(--terminal-cursor))',
link: 'hsl(var(--terminal-link))',
warning: 'hsl(var(--terminal-warning))',
'line-number': 'hsl(var(--terminal-line-number))',
},
```
@@ -441,6 +510,8 @@ Visual reference:
## 5. Typography — Geist Sans + Geist Mono
<!-- v2.1 --> **Why Geist?** Geist is Vercel's open-source typeface (MIT licensed), purpose-built for developer tooling interfaces. It has tighter default letter-spacing than Inter, making it denser at small sizes — critical for the mission-control aesthetic where every pixel counts. The mono variant has consistent glyph widths optimized for terminal output, not just code editors. **Alternatives considered:** Inter (too ubiquitous, wider spacing), Berkeley Mono (license cost, proprietary), JetBrains Mono (excellent for code but no matching sans-serif), IBM Plex (good but the duo feels corporate, not dev-tool). Geist wins because both weights ship as one package with matching x-heights.
### Installation
```bash
@@ -484,6 +555,8 @@ export default {
No font size scale changes; keep Tailwind defaults (`text-xs` through `text-4xl`).
<!-- v2.1 --> **Density guidance:** For mission-control panels, prefer `text-xs` (12px) for metadata/timestamps, `text-sm` (14px) for body content in dense tables, and `text-base` (16px) only for primary reading areas (page editor, proposal content). Headers rarely need `text-2xl` or above — `text-lg` to `text-xl` is the sweet spot for section headers in a dense UI.
---
## 6. Radius — 6px Base
@@ -531,17 +604,19 @@ v1 vs v2:
### Dark Mode
<!-- v2.1 --> Elevated surfaces in dark mode use subtle inset highlights (top-edge light leak) and faint outer glow rather than traditional drop shadows. Pure `none` left elevated elements feeling flat and disconnected, especially stacked dialogs and the command palette.
```css
.dark {
--shadow-xs: none;
--shadow-sm: none;
--shadow-md: none;
--shadow-lg: none;
--shadow-xl: none;
--shadow-sm: inset 0 1px 0 hsl(0 0% 100% / 0.04); /* subtle top-edge highlight */
--shadow-md: inset 0 1px 0 hsl(0 0% 100% / 0.05), 0 2px 8px hsl(0 0% 0% / 0.3); /* highlight + ambient */
--shadow-lg: inset 0 1px 0 hsl(0 0% 100% / 0.06), 0 4px 16px hsl(0 0% 0% / 0.4); /* for dialogs */
--shadow-xl: inset 0 1px 0 hsl(0 0% 100% / 0.06), 0 8px 32px hsl(0 0% 0% / 0.5); /* command palette */
}
```
Dark mode uses **borders + surface lightness hierarchy** instead of box shadows. This avoids the muddy appearance of shadows on dark backgrounds.
Dark mode uses **borders + surface lightness hierarchy** as the primary elevation signal. The inset highlights act as secondary reinforcement — they simulate a top-edge light source at extremely low opacity so they never look muddy. The outer shadow is pure black at high opacity, which reads as "depth" on dark backgrounds without the grey-wash problem of traditional shadows.
Tailwind extension:
@@ -567,6 +642,151 @@ Usage guidance:
---
<!-- v2.1 -->
## 7b. Transition & Animation Tokens
Motion tokens ensure consistent feel across the UI. Mission control tools should feel *snappy* — never sluggish, never jarring.
```css
:root {
/* Durations */
--duration-instant: 50ms; /* tooltip show, dot pulse */
--duration-fast: 100ms; /* button hover, toggle states */
--duration-normal: 200ms; /* panel open/close, tab switch */
--duration-slow: 350ms; /* dialog enter, page transition */
--duration-glacial: 500ms; /* skeleton shimmer cycle */
/* Easing curves */
--ease-default: cubic-bezier(0.2, 0, 0, 1); /* emphatic decel — most UI transitions */
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* element exiting — slide out */
--ease-out: cubic-bezier(0, 0, 0.2, 1); /* element entering — slide in */
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);/* playful overshoot — toasts, notifications */
/* Composites (for Tailwind arbitrary values or direct use) */
--transition-colors: color, background-color, border-color, text-decoration-color, fill, stroke;
--transition-transform: transform;
}
```
Tailwind extension:
```ts
transitionDuration: {
instant: 'var(--duration-instant)',
fast: 'var(--duration-fast)',
normal: 'var(--duration-normal)',
slow: 'var(--duration-slow)',
},
transitionTimingFunction: {
default: 'var(--ease-default)',
in: 'var(--ease-in)',
out: 'var(--ease-out)',
spring: 'var(--ease-spring)',
},
```
Usage: `transition-colors duration-fast ease-default`, `transition-transform duration-normal ease-out`.
**Reduced motion:** Respect `prefers-reduced-motion`:
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
---
<!-- v2.1 -->
## 7c. Z-Index Scale
Predictable stacking order prevents z-index wars. Every layer is explicitly named.
```css
:root {
--z-base: 0;
--z-raised: 1; /* cards with hover lift */
--z-sticky: 10; /* sticky headers, tab bars */
--z-sidebar: 20; /* collapsible sidebar */
--z-dropdown: 30; /* select menus, popovers */
--z-overlay: 40; /* backdrop behind modals */
--z-modal: 50; /* dialogs, modals */
--z-toast: 60; /* toast notifications */
--z-command: 70; /* command palette (above everything) */
--z-tooltip: 80; /* tooltips (top of stack) */
}
```
Tailwind extension:
```ts
zIndex: {
base: 'var(--z-base)',
raised: 'var(--z-raised)',
sticky: 'var(--z-sticky)',
sidebar: 'var(--z-sidebar)',
dropdown: 'var(--z-dropdown)',
overlay: 'var(--z-overlay)',
modal: 'var(--z-modal)',
toast: 'var(--z-toast)',
command: 'var(--z-command)',
tooltip: 'var(--z-tooltip)',
},
```
---
<!-- v2.1 -->
## 7d. Focus-Visible Styles
Keyboard navigation must be unambiguous. All interactive elements get a visible focus ring on keyboard interaction only (not mouse clicks).
```css
/* Global focus-visible style */
*:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
border-radius: var(--radius);
}
/* Remove default outline for mouse users */
*:focus:not(:focus-visible) {
outline: none;
}
/* High-contrast focus for inputs (inset ring) */
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
outline: none;
box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring));
}
```
The double-ring technique (background gap + ring color) ensures the focus indicator is visible on both light and dark backgrounds, and against any surface color.
---
<!-- v2.1 -->
## 7e. Responsive Breakpoints
The app is a desktop-first power tool. Mobile is explicitly out of scope — the minimum supported width is 1024px. These breakpoints govern panel layout collapse behavior:
```
Breakpoint Width Behavior
sm ≥640px (unused — below minimum)
md ≥768px (unused — below minimum)
lg ≥1024px Minimum supported. Sidebar collapsed by default.
xl ≥1280px Sidebar open by default. Two-column layouts.
2xl ≥1536px Three-column layouts. Agent grid shows 4+ cards per row.
```
No custom breakpoints needed — Tailwind defaults are sufficient. The app shell uses `min-w-[1024px]` to enforce the minimum.
---
## 8. Dark Mode Implementation
### Default Behavior
@@ -642,22 +862,25 @@ function ThemeProvider({ children }: { children: ReactNode }) {
() => (localStorage.getItem('cw-theme') as Theme) || 'system'
);
const isDark = useMemo(() => {
if (theme === 'system') {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return theme === 'dark';
}, [theme]);
// v2.1 — Track system preference as reactive state so isDark updates on OS theme change
const [systemDark, setSystemDark] = useState(
() => window.matchMedia('(prefers-color-scheme: dark)').matches
);
const isDark = theme === 'dark' || (theme === 'system' && systemDark);
const setTheme = useCallback((t: Theme) => {
setThemeState(t);
applyTheme(t);
}, []);
// Listen for system theme changes when in 'system' mode
// Listen for system theme changes
useEffect(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const handler = () => { if (theme === 'system') applyTheme('system'); };
const handler = (e: MediaQueryListEvent) => {
setSystemDark(e.matches);
if (theme === 'system') applyTheme('system');
};
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, [theme]);
@@ -668,8 +891,12 @@ function ThemeProvider({ children }: { children: ReactNode }) {
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
```
<!-- v2.1 --> **Bug fix in original:** `isDark` was a `useMemo` depending only on `[theme]`, which meant it would never re-derive when the OS theme toggled while in `system` mode. The fix introduces `systemDark` as tracked state that the `useEffect` updates, ensuring `isDark` is always correct. Also exported the `useTheme` hook — the original omitted it.
---
## 9. Migration Notes
@@ -707,9 +934,9 @@ function ThemeProvider({ children }: { children: ReactNode }) {
|-------|----|----|-------|
| `--background` | `0 0% 3.9%` | `240 6% 7%` | Lighter, zinc-tinted |
| `--foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted |
| `--card` | `0 0% 3.9%` | `240 5% 10%` | **Elevated above bg** |
| `--card` | `0 0% 3.9%` | `240 5% 11%` | **Elevated above bg (+4%)** | <!-- v2.1 widened -->
| `--card-foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted |
| `--popover` | `0 0% 3.9%` | `240 5% 13%` | **Elevated above card** |
| `--popover` | `0 0% 3.9%` | `240 5% 16%` | **Elevated above card (+5%)** | <!-- v2.1 widened -->
| `--popover-foreground` | `0 0% 98%` | `240 5% 96%` | Zinc-tinted |
| `--primary` | `0 0% 98%` | `239 84% 67%` | **White to indigo** |
| `--primary-foreground` | `0 0% 9%` | `0 0% 100%` | Black to white |
@@ -738,28 +965,37 @@ function ThemeProvider({ children }: { children: ReactNode }) {
4. **`--radius` shrinks from 8px to 6px.** All rounded corners tighten slightly.
5. **Dark mode card/popover surfaces are elevated.** v1 had card = background (both 3.9%). v2 separates them: background 7% < card 10% < popover 13%.
5. **Dark mode card/popover surfaces are elevated.** v1 had card = background (both 3.9%). v2 separates them with wider steps: background 7% < card 11% < popover 16% < surface-3 21%. <!-- v2.1 widened from 10/13 -->
### New Token Categories
These are entirely new in v2 — no v1 equivalents exist:
- **Status tokens** (24 light + 24 dark = 48 new properties)
- **Terminal tokens** (10 new properties)
- **Terminal tokens** (22 light + 22 dark = 44 new properties) <!-- v2.1 expanded from 10 -->
- **Diff tokens** (14 new properties)
- **Shadow tokens** (5 new properties, `none` in dark)
- **Shadow tokens** (5 new properties, dark mode uses inset highlights + glow) <!-- v2.1 updated -->
- **Transition/animation tokens** (9 new properties) <!-- v2.1 added -->
- **Z-index scale** (10 new properties) <!-- v2.1 added -->
- **Extended indigo scale** (10 new properties, light mode only) <!-- v2.1 added -->
- **Surface level 3** (1 new property, dark mode only) <!-- v2.1 added -->
### File Changes Required
| File | Changes |
|------|---------|
| `packages/web/src/index.css` | Replace all `:root` and `.dark` token values; add status, terminal, diff, shadow tokens; add Geist font imports |
| `packages/web/tailwind.config.ts` | Add `fontFamily`, `colors.status`, `colors.terminal`, `colors.diff`, `boxShadow`, `borderRadius` extensions |
| `packages/web/package.json` | Add `geist` dependency |
| `packages/web/index.html` | Add dark mode inline script in `<head>` |
| `packages/web/src/lib/theme.tsx` | New file: ThemeProvider context |
| `packages/web/src/layouts/AppLayout.tsx` | Add ThemeToggle to header |
| `packages/web/src/App.tsx` (or root) | Wrap with ThemeProvider |
<!-- v2.1 expanded with implementation order and specific details -->
| # | File | Changes | Depends On |
|---|------|---------|------------|
| 1 | `packages/web/package.json` | Add `geist` dependency | — |
| 2 | `packages/web/src/index.css` | Replace all `:root` and `.dark` token values; add status, terminal, diff, shadow, transition, z-index tokens; add Geist font imports; add focus-visible styles; add reduced-motion media query | #1 |
| 3 | `packages/web/tailwind.config.ts` | Add `fontFamily`, `colors.status`, `colors.terminal`, `colors.diff`, `boxShadow`, `borderRadius`, `transitionDuration`, `transitionTimingFunction`, `zIndex` extensions | #2 |
| 4 | `packages/web/index.html` | Add dark mode inline script in `<head>` before stylesheets | — |
| 5 | `packages/web/src/lib/theme.tsx` | New file: ThemeProvider context + `useTheme` hook | — |
| 6 | `packages/web/src/App.tsx` (or root) | Wrap with `<ThemeProvider>` | #5 |
| 7 | `packages/web/src/layouts/AppLayout.tsx` | Add ThemeToggle to header (Sun/Monitor/Moon) | #5 |
| 8 | Existing components using `bg-primary` | Audit: buttons, badges, links — now indigo instead of black/white | #2 |
| 9 | `AgentOutputViewer` component | Migrate to terminal tokens (`bg-terminal`, `text-terminal-fg`, etc.) | #2, #3 |
| 10 | All status badge/dot components | Migrate from hardcoded colors to `bg-status-*-bg text-status-*-fg` | #2, #3 |
---
@@ -767,3 +1003,65 @@ These are entirely new in v2 — no v1 equivalents exist:
- `packages/web/src/index.css` (current theme)
- `packages/web/tailwind.config.ts` (current Tailwind config)
---
## Design Review Notes
**Reviewer:** Design system review, v2.1 pass
**Date:** 2026-03-02
**Method:** Inline edits marked with `<!-- v2.1 -->` comments throughout the document.
### What's Strong
1. **The v1 problem analysis is honest and complete.** Calling out that `secondary`, `muted`, and `accent` all resolve to the same gray — that's the kind of specificity that builds trust. Good.
2. **Status token structure is well-considered.** The bg/fg/border/dot quad covers every common badge pattern without requiring component-specific tokens. The status-to-entity mapping table is exactly what a developer needs.
3. **Terminal always-dark pattern is correct.** Not negotiable — terminal output on a white background is a usability crime. The spec nails this.
4. **Diff tokens are clean and complete.** Hunk/add/remove with border variants covers the review tab needs without overcomplicating.
5. **Migration table is thorough.** Showing v1 vs v2 side-by-side for every token eliminates guesswork.
### What Was Fixed (inline)
1. **Dark mode surface hierarchy widened from 3% to 4-5% steps.** The original 7→10→13 was too subtle. On a typical display at reasonable brightness, 3% lightness difference in the sub-15% range is nearly invisible. Changed to 7→11→16→21 (four tiers). Added Level 3 (`--surface-3`) for the command palette.
2. **Dark mode shadows: no longer `none`.** Pure nothing left elevated components feeling flat. Added subtle inset top-highlights (simulating edge light) and faint black outer glow for dialogs/command palette. The technique: `inset 0 1px 0 hsl(0 0% 100% / 0.05)` gives a crisp top edge without the grey-wash problem.
3. **Terminal tokens expanded.** Added: `--terminal-cursor`, `--terminal-selection-bg`, `--terminal-link`, `--terminal-warning`, `--terminal-line-number`, plus full ANSI color override set (8 colors). The original 10 tokens were not enough for a real terminal renderer — any agent output with ANSI escape codes would fall through to browser defaults.
4. **ThemeProvider `isDark` bug fixed.** The `useMemo` depended only on `[theme]`, which meant toggling the OS dark mode while in `system` mode would not update `isDark` until the next render triggered by something else. Replaced with tracked `systemDark` state that the `useEffect` listener updates.
5. **`useTheme` hook export was missing.** The context was defined but no consumer hook was exported. Fixed.
### What Was Added (inline)
1. **Design rationale for indigo.** The original spec just declared `#6366F1` without explaining why. Added positioning rationale (between blue and violet = trust + intelligence) and hue collision analysis against status tokens.
2. **Extended indigo scale (50-900).** The base palette had exactly one indigo value (`--primary`). That's not enough for hover states, gradient backgrounds, selected rows, badge variants, or any treatment that needs a lighter/darker brand shade. Added 10-step scale from indigo-50 to indigo-900.
3. **Transition/animation tokens (Section 7b).** Completely missing from the original. A design system without motion tokens guarantees inconsistency — one dev uses 150ms ease-in-out, another uses 300ms linear, and the UI feels schizophrenic. Defined 5 durations, 4 easing curves, and a reduced-motion media query.
4. **Z-index scale (Section 7c).** Missing, and critical for a layered UI with sidebars, dropdowns, modals, toasts, and a command palette all potentially visible simultaneously. Defined 10 named layers from `base` to `tooltip`.
5. **Focus-visible styles (Section 7d).** Missing. A keyboard-first tool *must* have visible focus indicators. Added global `:focus-visible` styles with a double-ring technique for inputs.
6. **Responsive breakpoints (Section 7e).** Missing. Documented that the app is desktop-first with a 1024px minimum, and mapped Tailwind breakpoints to panel layout behavior.
7. **Font rationale.** Geist is a defensible choice for 2026, but the original didn't explain *why*. Added comparison against Inter, Berkeley Mono, JetBrains Mono, and IBM Plex.
8. **Migration file changes table expanded.** Added dependency ordering (which file depends on which), implementation step numbers, and three new rows: component audit for `bg-primary` consumers, AgentOutputViewer terminal migration, and status badge migration.
### Still Missing / Future Considerations
1. **Spacing scale.** The spec doesn't define a spacing system beyond Tailwind defaults. For a dense mission-control UI, consider documenting preferred spacing patterns: 4px micro-gaps (between icon + label), 8px component padding, 12px card padding, 16px section gaps. Not blocking — Tailwind defaults work — but documenting the conventions prevents drift.
2. **Icon sizing tokens.** With Lucide icons throughout, standardize on 16px (inline/badges), 20px (buttons), 24px (navigation). Not in this spec's scope but should be in a shared-components doc.
3. **Color-blind testing.** The status token hues were chosen for maximum separation, but the amber-38 (warning) vs red-0 (error) distinction is the classic deuteranopia problem. Recommendation: rely on shape (icons/badges) and position in addition to color. The spec should note this explicitly — "never communicate status through color alone."
4. **`--chart-*` tokens.** If the app ever shows usage graphs, agent activity timelines, or cost charts, you will need a categorical color palette (6-8 colors, perceptually distinct). Not needed now, but reserve the `--chart-*` namespace.
5. **Print styles.** Not a priority for a real-time orchestration tool, but if anyone ever tries to print a proposal or review diff, the terminal always-dark tokens will produce solid black pages. Consider a `@media print` override that forces terminal tokens to light values.