# Feature Spec: Reviewer-Grade Diff and Agent Console (AOP)

> Purpose: define M46 as a deterministic, reviewer-first experience for code diff inspection and agent observability, with explicit security, performance, and accessibility constraints for agentic execution.

**Version:** 2.0 (execution hardening rewrite)
**Date:** 2026-03-05
**Status:** Draft
**Roadmap Mapping:** M46
**Depends On:** M44 (dashboard component baseline), M45 (Focus view and route architecture)

---

## 0. Scope and Standards

### 0.1 User Jobs and Priority

M46 serves three jobs in strict priority order:

1. **Reviewer job (primary):** understand what changed and decide approve/deny/request changes quickly and accurately.
2. **Operator job (secondary):** understand what the agent is doing now and diagnose stuck/looping behavior.
3. **Investigation job (tertiary):** inspect raw provider output when structured logs are insufficient.

### 0.2 Critical Corrections from v1.0

| v1.0 issue                                                       | Why it is risky                                                          | v2.0 correction                                                               |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| Diff HTML rendered with direct `dangerouslySetInnerHTML` pattern | XSS risk if content is not sanitized and constrained                     | Require sanitization and strict rendering boundary rules                      |
| "Best possible" tooling claims without constraints               | Encourages over-engineering and brittle assumptions                      | Define concrete acceptance targets and fallback paths                         |
| `fs.watch()` as primary stream design                            | File watcher behavior differs across OS/filesystems and can leak handles | Define polling-based delta stream contract with optional watcher optimization |
| Raw stdout persistence always-on                                 | May capture secrets, tokens, and sensitive reasoning                     | Add policy gate, retention limits, and explicit warning UX                    |
| Keyboard shortcuts not scoped                                    | Hotkeys can break typing in textareas/inputs                             | Scope shortcuts to focused diff region and suppress in editable controls      |
| Console flow optimistic about agent responses                    | User can send messages when no active run and see no response            | Add preflight run/session checks and explicit disabled/error states           |
| Single-pane composition pressure                                 | Review and observability overload in one panel                           | Enforce progressive disclosure and route-native Focus layout hierarchy        |

### 0.3 Non-Negotiable Principles

1. **Reviewer first:** code understanding and decision actions stay first-class.
2. **Progressive disclosure:** deep diagnostics remain available but not default-noisy.
3. **Truthful signals:** inferred states must be labeled inferred.
4. **Safe by default:** sensitive/raw output handling must be policy-controlled.
5. **Deterministic contracts:** every route and state has explicit fallback and error code.

### 0.4 Required Quality Standards

- TypeScript strict mode passes.
- ESLint zero warnings.
- Test coverage >= 90% lines/branches/functions/statements.
- `npm run build`, `npm run typecheck`, `npm run lint`, `npm test` all pass.
- `npm run validate:mcp-contracts` and `npm run validate:architecture` pass.
- Accessibility baseline: WCAG 2.2 AA for all new interactive UI.

### 0.5 Performance and Accessibility Budgets

- Diff overview initial render target: under 700ms for a 500-line, 20-file diff on baseline dev hardware.
- Monaco inspector open target: under 1200ms after click for medium files (<5k LOC).
- Log stream update latency target: new entry visible within 3s.
- No unbounded in-memory arrays for logs/events (hard cap required).
- Hotkeys must not fire while focus is in `input`, `textarea`, contenteditable, or Monaco editor.
- Phone baseline remains required from M45: core review actions usable at 360px CSS width.

---

## 1. Experience Architecture

### 1.1 Route and View Model

M46 builds on M45 route model:

- `/?view=board`: board triage context.
- `/feature/:id`: Focus route for deep review.
- `/analytics`: unaffected by M46 except links to review diagnostics.

M46 features are centered in `/feature/:id` with a `review` sub-mode.

### 1.2 Progressive Disclosure Rules

Default state in Focus review mode:

- Expanded by default:
  - Diff overview (UX-41)
  - Review actions
  - Agent log stream summary (UX-44)
- Collapsed by default:
  - Raw output viewer (UX-45)
  - Full activity timeline (UX-46)
  - Session internals (UX-48)

### 1.3 Data Trust Labels

Each review/console section must declare source class:

- `Structured Artifact`: derived from validated project artifacts (`decisions.md`, `worker-events.jsonl`).
- `Raw Provider Output`: unnormalized provider stdout (higher sensitivity).
- `Derived`: UI-computed summaries (stats, counters, inferred labels).

---

## 2. Revised Improvements (UX-41 to UX-49)

### UX-41 - Diff Overview Renderer (`diff2html`)

**Intent:** provide high-scanability, GitHub-like patch overview for multi-file review.

**Requirements:**

- Use `diff2html` for unified patch rendering.
- Render modes supported: `line-by-line` and `side-by-side`.
- Include per-file list and jump links.
- Show empty state: `No changes in this feature worktree.`
- Limit container height and use internal scrolling.
- Sanitize any HTML output path before insertion.
- CSS import must follow Next.js constraints (global style import in allowed global scope, not arbitrary component-local import if disallowed by framework rules).

### UX-42 - Monaco File Inspector

**Intent:** provide full-file context for one selected changed file.

**Requirements:**

- API route: `GET /api/features/:id/file-diff?path=<encoded_path>`.
- Response includes `original`, `modified`, `language`, `change_type`.
- Support `added`, `modified`, `deleted`, `renamed` semantics.
- Use Monaco `DiffEditor` in read-only mode.
- Provide explicit loading/error states.
- On phone baseline, inspector opens full-screen and disables side panel overlap.

### UX-43 - Diff Stats and Navigation

**Intent:** reduce review time with clear scope metrics and keyboard navigation.

**Requirements:**

- Compute totals: files changed, additions, deletions.
- File selector enables direct jump.
- Hotkeys (scoped to focused diff container):
  - `j` / `n`: next file
  - `k` / `p`: previous file
  - `o`: open selected file in Monaco inspector
  - `[` / `]`: previous/next hunk
- Header includes discoverable hotkey hint toggle.

### UX-44 - Live Agent Log Stream

**Intent:** show recent `decisions.md` notes in near real time.

**Requirements:**

- API route: `GET /api/features/:id/log-stream` (SSE).
- Server implementation uses polling + cursor/delta strategy as baseline.
- `fs.watch` may be used only as optional optimization and must not be sole mechanism.
- Client supports follow mode with pause/resume autoscroll.
- Cap in-memory entries (default 200 visible, older dropped from UI buffer).
- Entry rendering includes timestamp, actor/role badge, and note text.

### UX-45 - Raw Agent Output Viewer

**Intent:** enable deep debugging with explicit sensitivity controls.

**Requirements:**

- Raw output persistence is **policy-gated**:
  - enabled only if `observability.raw_agent_logs_enabled === true` (or equivalent config key).
- API routes:
  - `GET /api/features/:id/raw-logs?role=<planner|builder|qa>&limit=<n>`
  - `GET /api/features/:id/raw-logs/:filename`
- Filename validation must prevent traversal and reject non-matching role/timestamp pattern.
- UI must display sensitivity warning banner: `Raw output may contain sensitive data.`
- If feature disabled by policy: show explanatory disabled state, not error toast.
- Access model: raw output is visible to all dashboard users by default (no elevated-role filter in M46).
- Retention defaults:
  - maximum age 60 days
  - no maximum file count in this milestone
  - no per-file size cap in this milestone

### UX-46 - Agent Activity Timeline

**Intent:** show per-iteration execution metadata for diagnosis.

**Requirements:**

- API route: `GET /api/features/:id/timeline?limit=<n>&role=<optional>`.
- Source: `worker-events/{run_id}.jsonl`, filtered by `feature_id`.
- Return newest-first entries with summary counts.
- UI includes role filter and validity filter.
- Hard cap default limit to 100 entries.

### UX-47 - Interactive Agent Console

**Intent:** allow targeted human intervention during active feature execution.

**Requirements:**

- Uses existing `POST /api/actions` with action `feature.send_message`.
- Preflight checks before send:
  - feature exists
  - feature not terminal (`merged`, `failed`, `stopped`)
  - active orchestrator/runtime session present
- Client UX:
  - textarea input
  - `Cmd/Ctrl + Enter` to send
  - send disabled when preflight fails
- Message rate limit (client + server): minimum 1s between sends per feature/session.
- Render sent user messages in local transcript with source label `You`.
- Agent responses continue to arrive via UX-44 stream.

### UX-48 - Agent Session Status Panel

**Intent:** expose current session wiring for troubleshooting without overwhelming reviewer flow.

**Requirements:**

- Source from `cluster` and `role_status` in feature summary/detail payload.
- Display session IDs truncated with copy action.
- Show active role marker tied to `role_status`.
- Panel collapsed by default.
- On phone baseline, rows wrap without horizontal overflow.

### UX-49 - Unified Review Workspace

**Intent:** unify diff review and agent console without overloading default Focus view.

**Requirements:**

- Available in `/feature/:id` review mode for `ready_to_merge` and optionally `qa` when enabled.
- Two-pane layout on desktop:
  - Left: UX-41/42/43
  - Right: UX-44/47 plus collapsed UX-48
- Phone layout stacks sections vertically with sticky review actions footer.
- Right pane must preserve review action visibility at all times.
- If one section fails (for example raw logs unavailable), the rest of review workspace remains functional.

---

## 3. API Contracts (Mandatory)

### 3.1 Uniform Envelope

All new routes return:

- Success:
  ```json
  { "ok": true, "data": {}, "meta": { "source": "artifact|tool|derived", "stale": false } }
  ```
- Error:
  ```json
  { "ok": false, "error": { "code": "string", "message": "string", "retryable": false } }
  ```

### 3.2 Route Catalog

| Route                                  | Method    | Purpose                                                      | UX    |
| -------------------------------------- | --------- | ------------------------------------------------------------ | ----- |
| `/api/features/:id/file-diff`          | GET       | Original/modified content for Monaco                         | UX-42 |
| `/api/features/:id/log-stream`         | GET (SSE) | `decisions.md` delta stream                                  | UX-44 |
| `/api/features/:id/raw-logs`           | GET       | List raw provider log files                                  | UX-45 |
| `/api/features/:id/raw-logs/:filename` | GET       | Read one raw provider log file                               | UX-45 |
| `/api/features/:id/timeline`           | GET       | Worker event timeline entries                                | UX-46 |
| `/api/actions`                         | POST      | Existing actions route, extended usage for send_message flow | UX-47 |

### 3.3 Query and Input Validation

- `:id` must match feature id pattern.
- `path` in file-diff route must be URL-decoded, normalized, and validated against feature diff scope.
- `limit` query params must be bounded server-side.
- `filename` must match `^(planner|builder|qa)-[0-9]{13}\.txt$`.

### 3.4 Minimum Error Codes

- `feature_not_found`
- `invalid_input`
- `artifact_missing`
- `artifact_unreadable`
- `tool_unavailable`
- `tool_timeout`
- `unauthorized_action`
- `policy_disabled`
- `session_inactive`

### 3.5 SSE Event Contract (`log-stream`)

Event types:

- `snapshot`: initial entries payload
- `append`: incremental entries
- `heartbeat`: keepalive every 15s
- `error`: non-fatal stream warnings

Payload shape:

```json
{
  "type": "append",
  "cursor": "string",
  "entries": [
    {
      "id": "string",
      "timestamp": "2026-03-05T00:00:00.000Z",
      "actor": "builder:feature-auth",
      "role": "builder",
      "text": "message"
    }
  ]
}
```

---

## 4. Backend Persistence Change (Raw Output)

### 4.1 File Target

- `apps/control-plane/src/providers/cli-worker-provider.ts`

### 4.2 Behavior

After successful worker command completion, best-effort persist raw stdout when enabled by policy:

- Path: `.aop/features/<feature_id>/logs/<role>-<unix_ms>.txt`
- Skip write when stdout is empty.
- Cleanup policy removes raw log files older than 60 days.
- No file-count rotation in this milestone.
- No per-file truncation in this milestone.
- Failures in persistence must not fail orchestration flow.

### 4.3 Security Constraints

- Persistence disabled unless explicit policy opt-in.
- Route readers must enforce filename regex and feature scoping.
- Raw output read routes are not role-restricted in M46 (visible to all dashboard users by default).
- UI must clearly label raw output as sensitive and non-normalized.

---

## 5. Type Additions (`packages/web-dashboard/src/lib/types.ts`)

```typescript
export interface AgentLogEntry {
  id: string;
  timestamp: string;
  actor: string;
  role: 'planner' | 'builder' | 'qa' | 'orchestrator' | string;
  text: string;
}

export interface WorkerEventEntry {
  ts: string;
  role: string;
  output_types: string[];
  patch_count: number;
  plan_submission_count: number;
  request_count: number;
  note_count: number;
  valid: boolean;
  error_code: string | null;
  provider: string;
  model: string;
}

export interface AgentSessionCluster {
  orchestrator_session_id: string;
  planner_session_id: string;
  builder_session_id: string;
  qa_session_id: string;
}

export interface FileDiffPayload {
  path: string;
  language: string;
  change_type: 'added' | 'modified' | 'deleted' | 'renamed' | 'unknown';
  original: string;
  modified: string;
}

export interface RawLogFileMeta {
  filename: string;
  role: 'planner' | 'builder' | 'qa';
  timestamp: string;
  size_bytes: number;
}
```

`FeatureSummary` additions:

- `cluster?: AgentSessionCluster`
- `role_status?: { planner: RoleStatus; builder: RoleStatus; qa: RoleStatus }`

`FeatureDetail` additions:

- `log_entries?: AgentLogEntry[]`

---

## 6. Delivery Plan for Agentic Execution

### 6.1 Slice A - Reviewer Diff Foundation

- UX-41, UX-42, UX-43
- Include keyboard scoping, mobile-safe inspector behavior, and diff stats tests.

### 6.2 Slice B - Observability Baseline

- UX-44, UX-46, UX-48
- Implement stream contract + timeline route with bounded limits.

### 6.3 Slice C - Interactive Console

- UX-47
- Add preflight/session checks and message rate-limit handling.

### 6.4 Slice D - Sensitive Raw Output Mode

- UX-45
- Backend persistence + policy gate + sensitive UI guardrails.

### 6.5 Slice E - Integrated Workspace

- UX-49 composition and responsiveness polish for desktop + 360px phone baseline.

---

## 7. Test Plan Requirements

### 7.1 API Tests

- Validate response envelope for all new routes.
- Validate error codes for missing feature/artifact and invalid input.
- Validate traversal protection for raw log filename route.

### 7.2 UI Tests

- Diff overview renders stats and file navigation.
- Hotkeys only active in focused diff region and never in text inputs.
- Console send disabled when session inactive.
- Follow mode behavior in log stream panel.

### 7.3 Integration Tests

- End-to-end review flow in Focus route with partial failures (one panel unavailable) still functional.
- Raw logs hidden when policy disabled.

### 7.4 Performance Checks

- Diff render and Monaco open latencies measured against Section 0.5 targets.
- Log stream memory cap enforced in long-running session simulation.

---

## 8. Acceptance Criteria

M46 is accepted when all are true:

1. Reviewer workflow criteria
   - Reviewer can inspect multi-file diff, open file-level Monaco inspector, and complete review actions without leaving `/feature/:id`.
2. Observability criteria
   - Live decisions stream appears within 3s of file append and remains stable over 30-minute session.
3. Safety criteria
   - Raw output is inaccessible unless policy-enabled and clearly marked sensitive.
4. Accessibility criteria
   - Keyboard-only user can navigate diff files and send console message.
   - No color-only status communication.
5. Engineering criteria
   - Required command suite passes (`lint`, `typecheck`, `test`, `build`, contract and architecture validators).

---

## 9. Out of Scope

- Browser-hosted TTY attach equivalent (`xterm.js` + server PTY bridge).
- Inline review annotations and threaded code comments.
- Prompt editing and agent policy mutation from dashboard.
- Multi-feature shared chat channels.
- LLM summarization of raw logs (future enhancement).

---

## 10. Open Questions

None for this revision.
