# Console v2 Feature Matrix — Complete System Audit
*Generated 2026-05-12. Source of truth for integration testing.*

## Table of Contents
1. [Backend API Routes](#part-1-backend-api-routes)
2. [Frontend Features & Components](#part-2-frontend-ui-features--components)
3. [Integration Matrix](#part-3-integration-matrix)
4. [Known Gaps & Red Flags](#part-4-known-gaps--red-flags)
5. [Live Testing Checklist](#part-5-live-testing-recommendations)

---

## Part 1: Backend API Routes (by domain)

### 1.1 Engine Entity Routes — Read-Only
**`recoil/api/engine_routes.py`**

| Method | Path | Purpose | Returns |
|--------|------|---------|---------|
| GET | `/api/projects` | List all projects | `Project[]` |
| GET | `/api/projects/{project_id}` | Get project details | `Project \| null` (404) |
| GET | `/api/projects/{pid}/episodes` | List synthesized episodes (P3) | `Episode[]` |
| GET | `/api/projects/{pid}/episodes/{eid}/scenes` | List scenes for episode | `Scene[]` (synthetic, one per episode) |
| GET | `/api/projects/{pid}/episodes/{eid}/scenes/{sid}/beats` | List beats for scene | `Beat[]` |
| GET | `/api/beats/{beat_id}/takes` | List takes for a beat | `Take[]` |
| GET | `/api/beats/{beat_id}/lineage` | Get lineage graph (parent_take_id chains) | `Lineage \| null` |
| GET | `/api/events` | Get engine events (newest-first, cap 500) | `EngineEvent[]` |
| GET | `/api/memory` | Get engine memory entries | `MemoryEntry[]` |

Notes:
- All paths validate project_id/beat_id against path-traversal (Debug R1)
- Lineage rooted at beat by default; take-rooted when `takeId` query param provided

---

### 1.2 Mutation Routes — Real Actions + EventBus
**`recoil/api/mutation_routes.py`**

| Method | Path | Purpose | Notes |
|--------|------|---------|-------|
| POST | `/api/proposals/{id}/approve` | Approve proposal | Emits `engine/proposals` |
| POST | `/api/proposals/{id}/reject` | Reject proposal | Emits `engine/proposals` |
| POST | `/api/proposals/{id}/defer` | Defer proposal | Emits `engine/proposals` |
| POST | `/api/takes/{id}/mark-primary` | Mark take as primary | Emits `engine/takes` |
| POST | `/api/takes/{id}/mark-circled` | Toggle/set circled flag (idempotent v2) | `{value?: bool}` body |
| POST | `/api/takes/{id}/reject` | Reject a take | Emits `engine/takes` |
| POST | `/api/memory/{id}/toggle` | Toggle memory entry on/off | `{value?: bool}` body |

Notes:
- Idempotent v2: `value` in body = SET (safe to retry); absent body = legacy toggle
- Take mutations emit `take_id_not_on_disk` fallback event if id not found on disk

---

### 1.3 Server-Sent Events
**`recoil/api/sse_routes.py`**

| Method | Path | Behavior |
|--------|------|----------|
| GET | `/api/events/stream` | Yields `{id, event: "engine_event", data: JSON}` per event; never closes server-side |

Notes:
- `Last-Event-ID` header (set by EventSource) takes precedence over query param
- Supports resume via last event ID; empty header = "start fresh" (Debug R5)

---

### 1.4 Workspace State Routes
**`recoil/api/workspace_state.py`**

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/workspace-state/{wid}` | Read persisted UI state |
| POST | `/api/workspace-state/{wid}` | Upsert persisted UI state |
| DELETE | `/api/workspace-state/{wid}` | Delete workspace state (no UI button yet) |
| POST | `/api/workspace-state/{wid}/report` | Report corrupt state (SessionRestoreModal) |

Notes:
- `wid` must be UUIDv4
- 409 Conflict on schema_version mismatch (Law 4)
- Server stores opaque `payload_json` — does NOT parse ColumnLayout/TabState (TS+zod owned)

---

### 1.5 Chat Proposal Lifecycle
**`recoil/api/proposals_routes.py`** — mounted at `/api/chat/proposals/`

| Method | Path | Purpose | Notes |
|--------|------|---------|-------|
| POST | `/api/chat/proposals` | Create proposal | Stored in `~/.recoil/proposals/{project}/{uuid}.json` |
| GET | `/api/chat/proposals/{project_id}` | List proposals | Emits BUS event on status change |
| POST | `/api/chat/proposals/{id}/approve` | Approve + dispatch | SAFE PLACEHOLDER — emits event, returns stub dispatch_id |
| POST | `/api/chat/proposals/{id}/reject` | Reject proposal | Emits BUS event |

⚠️ **Overlap:** These routes coexist with `/api/proposals/{id}/approve|reject|defer` (mutation_routes). Two endpoints for proposals — Phase 20+ should consolidate.

---

### 1.6 Terminal / TTyd Routes
**`recoil/api/ttyd_routes.py`**

| Method | Path | Purpose | Returns |
|--------|------|---------|---------|
| GET | `/api/ttyd/health` | Probe ttyd installation | `{status: "ok"}` \| 503 |
| POST | `/api/ttyd/start` | Start per-project ttyd session | `{port, session_id, context_limit}` |
| POST | `/api/ttyd/stop` | Stop per-project ttyd session | `{ok: true}` |
| GET | `/api/ttyd/status` | Poll session status | `{port, status, running}` |
| GET | `/api/ttyd/context-window` | Get JSONL context usage | `{used_tokens, limit, percent}` |

Notes:
- Port range: 7681–7700 (20 concurrent sessions max)
- Session capture timeout 15s, graceful stop 3s
- MCP shim bootstraps via ttyd to expose Claude Code to browser

---

### 1.7 Click History
**`recoil/api/clicks_routes.py`**

| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/clicks` | Record click event (shot/take/pass/file) |
| GET | `/api/clicks` | Get click history (newest-first) |

Notes:
- Ring buffer: max 50 cached per project, tail of 256KB JSONL on disk
- Atomic disk write under `_RING_LOCK` (O_APPEND)

---

### 1.8 Media / Selection / System Status

| Method | Path | Purpose | File |
|--------|------|---------|------|
| GET | `/api/media/{project_id}/{path}` | Serve media files (frames, videos) | `media_routes.py` |
| GET | `/api/selection/current` | Get current selection | `selection_routes.py` |
| POST | `/api/selection/current` | Set current selection | `selection_routes.py` — **never called from UI** |
| GET | `/api/system-status` | API health snapshot | `system_status.py` |

---

### 1.9 Stub Routes (Fixture Data — Phases 17/20)
**`recoil/api/stub_routes.py`**

| Method | Path | Status |
|--------|------|--------|
| GET | `/api/queue` | Fixture only — filters via in-memory `_acted` set (resets on restart) |
| GET | `/api/chat/{project_id}` | STUB — fixture chat array |
| GET | `/api/chat/context-window` | STUB — fixture |
| GET | `/api/slash-commands` | STUB — fixture list |
| GET | `/api/commands-ref` | STUB — fixture |

---

## Part 2: Frontend UI Features & Components

**Root:** `recoil/console-v2/packages/desktop/src/`

### 2.1 Navigation Pane (Left) — `nav/HierarchyNavigator.tsx`

| Feature | API Routes | Notes |
|---------|-----------|-------|
| Project tree (project → episode → scene → beat) | GET /projects, /episodes, /scenes, /beats | Lazy-load on expand |
| Beat focus → takes & lineage | GET /beats/{bid}/takes, /beats/{bid}/lineage | Triggers on click |
| Click history | POST/GET /clicks | Ring buffer per project |

---

### 2.2 Artifact Stage (Center-Right) — `stage/ArtifactStage.tsx`

| Feature | API Routes | Notes |
|---------|-----------|-------|
| Frame display / video player | GET /media/{project_id}/{path} | Aspect-ratio aware |
| Take browser (carousel) | Via beat focus data | Pagination, primary/circled badges |
| Lineage explorer (graph) | GET /beats/{beat_id}/lineage | Node selection, minimap |
| Events inspector | GET /events + SSE /events/stream | Filter by severity/scope |
| Memory inspector | GET /memory + POST /memory/{id}/toggle | On/off toggle |
| Queue inspector | GET /queue + POST /proposals/{id}/* | Proposal approve/reject/defer |

Template selector at top: 8 predefined stage layouts.

---

### 2.3 Chat Pane (Left-Center) — `chat/ChatColumn.tsx`

| Feature | API Routes | Notes |
|---------|-----------|-------|
| Message bubbles | GET /chat/{project_id} | **STUB** — fixture data |
| Slash launcher | GET /slash-commands | **STUB** — no real dispatch |
| Proposal tray (inline) | GET /chat/proposals/{pid}, POST approve/reject | SSE for real-time |
| Terminal iframe (ttyd) | POST /ttyd/start, GET /ttyd/status | Per-project lifecycle |
| Context bar (token usage) | GET /ttyd/context-window | Polls every 5s |

---

### 2.4 Shell Chrome — `shell/`

| Component | API Route | Behavior |
|-----------|-----------|----------|
| Titlebar | POST /workspace-state/{wid} | Auto-saves layout every 1s (debounced) |
| StatusBar | GET /system-status | Polls every 5s; green/red/yellow pill |
| StatusPopover | GET /system-status | Drill-down on ttyd health, API latency |
| BottomBay | — | Keyboard hints + command palette toggle |
| Events drawer | SSE /events/stream | Slide-out; filter by scope |

---

### 2.5 Take Inspector — `stage/TakeInspector.tsx`

| Feature | API Routes |
|---------|-----------|
| Take metadata (shot ID, file paths) | From beat/take data |
| Mark primary / mark circled / reject | POST /takes/{id}/mark-primary, /mark-circled, /reject |
| Lineage inline (parent preview) | From lineage data |

---

## Part 3: Integration Matrix

| Frontend Feature | Primary Routes | EventBus |
|-----------------|---------------|---------|
| HierarchyNavigator | GET /projects, /episodes, /scenes, /beats | — |
| TakesBrowser | GET /beats/{bid}/takes | `engine/takes` |
| LineageExplorer | GET /beats/{bid}/lineage | — |
| ArtifactStage | GET /media/{project_id}/{path} | — |
| ChatColumn | POST /ttyd/start, GET /ttyd/status, /ttyd/context-window, GET /chat/proposals/{pid} | `chat/proposals` |
| ProposalTray | GET /chat/proposals/{pid}, POST approve/reject, POST /proposals/{id}/defer | `chat/proposals`, `engine/proposals` |
| EventsInspector | GET /events, SSE /events/stream | All scopes |
| EngineMemoryInspector | GET /memory, POST /memory/{id}/toggle | `engine/memory` |
| QueueInspector | GET /queue, POST /proposals/{id}/* | `engine/proposals` |
| StatusBar/Popover | GET /system-status | — |
| Workspace persistence | GET/POST /workspace-state/{wid} | — |

---

## Part 4: Known Gaps & Red Flags

### 4.1 Frontend Features with No Real Backend

| Feature | Route | Status | Impact |
|---------|-------|--------|--------|
| Real chat history | GET /api/chat/{pid} | **STUB** | Fixture only; no persistence |
| Slash command dispatch | POST /api/slash/dispatch | **NOT IMPLEMENTED** | UI exists, nothing fires |
| Send chat message | POST /api/chat/{pid} | **NOT IMPLEMENTED** | UI can type; no receiver |
| Proposal dispatch (re-roll) | Approve → pipeline | **SAFE PLACEHOLDER** | Emits event, stub dispatch_id only |

### 4.2 Backend Routes with No Frontend Caller

| Route | Notes |
|-------|-------|
| POST /api/selection/current | Called by MCP shim (in-progress build), never by UI |
| DELETE /api/workspace-state/{wid} | No UI button |
| POST /internal/bus | Used by MCP shim, not UI |

### 4.3 Structural Issues

| Issue | Impact | Phase to fix |
|-------|--------|-------------|
| Two overlapping proposal endpoints (`/api/chat/proposals` + `/api/proposals`) | Confusing; double surface area | Phase 20+ |
| Stub queue `_acted` map resets on uvicorn restart | Queue re-shows acted proposals | Phase 20+ |
| EventBus ring buffer (max 500 events) — no persistence | SSE clients miss history on backend restart | Future |
| `POST /api/selection/current` never called from UI | MCP shim connection is the only writer | parent-take-mcp build |
| Click history JSONL unbounded | Tail read every GET; degrades over time | Future |

### 4.4 Take Mutation Silent Failure

Take mutations return `{ok: true}` even when the take ID isn't found on disk — they emit a `take_id_not_on_disk` fallback event instead of a 404. The UI sees success but nothing changed. Verify in EventsInspector after any take action.

---

## Part 5: Live Testing Checklist

### Phase Readiness Summary

| Phase | Status | What's Real |
|-------|--------|------------|
| Phase 16 (Engine entities) | ✅ COMPLETE | /projects, /beats, /episodes, /scenes, /memory, /events |
| Phase 17 (Workspace state + stubs) | ✅ COMPLETE | /workspace-state, all shell chrome |
| Phase 19 (Mutations + EventBus) | ✅ COMPLETE | /proposals/*, /takes/*, /memory/*/toggle |
| Phase 20 (Chat + slash framework) | ⚠️ FRAMEWORK ONLY | Whitelist validated; no real handlers |
| parent-take-mcp (lineage + MCP shim) | 🔄 IN PROGRESS | parent_take_id threading, console_mcp_shim.py |
| Phase 8+ (Proposal dispatch) | ⚠️ PLACEHOLDER | Returns stub; no real re-roll |

---

### Navigation

- [ ] Expand project tree — all levels lazy-load without error
- [ ] Click beat → takes populate in stage; lineage graph appears
- [ ] Click history captures click; `/api/clicks` returns it on reload

### Artifact Stage

- [ ] Switch all 8 stage templates — no crash, correct component mounts
- [ ] Frame displays for a take with a known image file
- [ ] Video plays for a take with a known video file
- [ ] Lineage explorer renders parent chain (requires parent_take_id data post-build)
- [ ] Events inspector shows events; SSE updates in real-time
- [ ] Memory inspector toggles an entry; persists after page refresh
- [ ] Queue inspector shows proposals; approve/reject shrinks queue

### Chat / Terminal

- [ ] TTyd start — port allocated in 7681–7700 range; iframe loads
- [ ] Context bar polls and updates token %; doesn't error on poll
- [ ] Proposal tray shows proposals from `/api/chat/proposals/{pid}`
- [ ] Slash launcher opens; typing `/` filters commands

### Shell Chrome

- [ ] Status bar shows green on healthy API; red on API down (kill uvicorn)
- [ ] Workspace state saves on layout change; restores on F5 reload
- [ ] SessionRestoreModal appears on corrupt workspace JSON

### Mutations (verify via EventsInspector)

- [ ] Mark take circled (no body) → toggles; (with `{value: true}`) → sets idempotently
- [ ] Mark take primary → badge appears in take list
- [ ] Reject take → take flagged
- [ ] Reject proposal → SSE event within ~1s; queue updates
- [ ] Toggle memory → state flips; confirmed via GET /api/memory

### Integration Smoke Tests

| Scenario | Steps | Pass Condition |
|----------|-------|----------------|
| Full hierarchy load | GET /projects → click episode → click beat → view take | No 404s; frame displays |
| Proposal round-trip | Create proposal → GET /queue → approve → SSE event | Queue shrinks; event appears in inspector |
| Terminal session | POST /ttyd/start → open iframe → type command | Port assigned; iframe loads |
| Workspace restore | POST /workspace-state → F5 → verify | Layout, selections match |
| Lineage chain | GET /beats/{bid}/lineage → click node | Graph renders; parent chain visible |
