# Feature Spec: Feature Focus View UX Redesign — Track B: Mission-Control Enhancements

> Purpose: Extend the redesigned focus view with advanced analytics, interactive diagnostics, and power-user tooling — built on top of the two-pane layout delivered in Track A.

**Version:** 1.3
**Date:** 2026-03-14
**Status:** Outstanding
**Roadmap Mapping:** M46-B (Focus View Mission-Control Enhancements)
**Depends On:** M46-A (Track A: Core Focus View Redesign, complete)
**Track A Spec:** `spec-files/outstanding/agentic_orchestrator_feature_focus_view_track_a_spec.md`

---

## Overview

Track B features extend the focus view beyond its current data surface. They are explicitly follow-on deliverables and must not block Track A completion. All Track B features are built on top of the `FocusLayout`, `ContextSidebar`, tab architecture, and shared Aequitas light-theme tokens established in Track A.

Track B delivers the following features:

| Section | Feature                        | Tab / Location       |
| ------- | ------------------------------ | -------------------- |
| §A      | Agent Transcript View (polish) | Transcript tab       |
| §B      | Code Evolution Scrubber        | Runtime tab          |
| §C      | File Collision Radar           | Overview tab         |
| §D      | Predictive Token Usage Burn    | Sidebar usage panel  |
| §E      | Feature Genealogy Tree         | Overview tab         |
| §F      | Gate Flakiness Analyzer        | Gates & QA tab       |
| §G      | Diff Annotation for Reviewers  | Changes tab          |
| §H      | Command Palette                | Root layout (global) |
| §I      | Per-Role Agent Performance     | Overview tab         |
| §J      | Merge Queue Position           | Sidebar              |

---

## §A: Agent Transcript View

### Purpose

Surface the actual text output of each agent role (planner, builder, QA) as a readable conversation transcript, not a flat log. This answers the question developers most want answered when watching an AI agent: "what was it thinking?"

### User Job

Operator/reviewer wants to understand why the builder agent wrote specific code, why the planner chose a particular approach, or why QA flagged a test — without reading raw log files.

### Data Sources

`FeatureDetail.log_entries: AgentLogEntry[]` is already fetched on page load. Each entry has:

- `actor` — the agent instance identifier
- `role` — `'planner' | 'builder' | 'qa' | 'orchestrator'`
- `text` — the raw text output of that agent turn
- `timestamp` — ISO string

`RawLogFileMeta[]` (new lazy endpoint `GET /api/features/[id]/log-files`) provides metadata for raw log files per role, enabling a "download raw log" link per role group. This fetch is on-demand, not on tab activation.

### Component: `TranscriptTab.tsx`

````
┌──────────────────────────────────────────────────────┐
│  TRANSCRIPT                                          │
│                                                      │
│  [All] [Planner] [Builder] [QA] [Orchestrator]       │  ← role filter pills
│                                                      │
│  ┌──────────────────────────────────────┐            │
│  │ 🟦 PLANNER  · 14:03:21              │ ↓ raw log  │
│  │                                      │            │
│  │  I'll break this into 3 steps:       │            │
│  │  1. Add the route handler            │            │
│  │  2. Update the schema               │            │
│  │  3. Write tests for edge cases       │            │
│  └──────────────────────────────────────┘            │
│                                                      │
│  ┌──────────────────────────────────────┐            │
│  │ 🟧 BUILDER  · 14:05:44              │ ↓ raw log  │
│  │                                      │            │
│  │  ```typescript                       │            │
│  │  export async function handler() {   │  ← syntax  │
│  │    ...                               │    highlighted│
│  │  }                                   │            │
│  │  ```                                 │            │
│  └──────────────────────────────────────┘            │
└──────────────────────────────────────────────────────┘
````

**Rendering rules:**

- Entries are grouped into consecutive same-role "turns." A new bubble starts when the role changes.
- Each bubble has a role-colored left border (using phase color variables: planner=blue, builder=orange, qa=gold, orchestrator=muted).
- Text is rendered as Markdown. Code fences (` ``` `) are syntax-highlighted using the same Monaco tokenizer already available in the project (no additional dependency).
- Long turns (> 40 lines) are collapsed to 20 lines with a "Show N more lines" inline toggle.
- Role filter pills filter the visible turns by role. Default: All.
- Each role group header has a "↓ raw log" link that triggers a lazy fetch of `RawLogFileMeta` for that role and initiates a file download.
- If `log_entries` is empty, a placeholder is shown: "No transcript available — logs are written once the agent completes its first turn."

### Tab Placement

`transcript` tab. Visible when `log_entries.length > 0`. Not a default tab for any phase — users navigate to it deliberately.

### Implementation Notes

- No new API endpoint required for the transcript itself. `log_entries` is in `FeatureDetail`.
- The `RawLogFileMeta` endpoint is optional and fetched only on "↓ raw log" click, never eagerly.
- Markdown rendering: use an existing lightweight parser (e.g., `marked` or similar) — check if one is already in `package.json` before adding a dependency.

---

## §B: Code Evolution Scrubber ("Time Machine")

### Purpose

Let the user scrub through the evolution of the codebase checkpoint-by-checkpoint, watching the diff update as they move through time. Transforms the static diff into an interactive story of how the code was written.

### User Job

Operator wants to understand the sequence of decisions the builder made, or find the exact checkpoint where a specific change was introduced. Currently requires manually selecting from a dropdown and waiting for a fetch each time.

### Data Sources

`FeatureDetail.checkpoints: FeatureCheckpoint[]` — already available. Each checkpoint has `diff_snapshot: string` (the full diff at that point in time), `timestamp`, `files_changed[]`, `validation_status`, and `violations[]`.

No new API endpoint required for the basic scrubber. `diff_snapshot` is already on the checkpoint object.

For the advanced "Play" mode, diffs are pre-loaded from `checkpoint.diff_snapshot` — no per-step network requests needed.

### Component: `CheckpointScrubber.tsx`

Placed inside `RuntimeTab.tsx`, replacing the current dropdown-based checkpoint selector.

```
┌──────────────────────────────────────────────────────────┐
│  CODE EVOLUTION  · 12 checkpoints                        │
│                                                          │
│  ◀  ▶  ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━○  [Play ▷]     │
│  cp-001              cp-006         cp-012               │
│  14:01               14:22          15:03                │
│                                                          │
│  Checkpoint 6 of 12  · 14:22:18                          │
│  ✓ valid  · 4 files changed                              │
│                                                          │
│  ┌────────────────────────────────────────────────────┐  │
│  │  [Diff content renders here at full width]         │  │
│  └────────────────────────────────────────────────────┘  │
│                                                          │
│  File filter: [All files ▾]                              │
└──────────────────────────────────────────────────────────┘
```

**Interaction:**

- **Scrubber slider:** `<input type="range">` mapped to checkpoint index. Moving it updates the displayed diff instantly (all diffs are already in memory from `checkpoints[]`).
- **◀ / ▶ buttons:** Step one checkpoint backward/forward.
- **Play ▷:** Auto-advances one checkpoint per 1.2 seconds. Button toggles to "Pause ⏸" while playing.
- **Checkpoint metadata strip:** Shows checkpoint ID, timestamp, validation status badge, files-changed count for the currently-selected checkpoint.
- **Violations inline:** If `violations.length > 0`, a red banner appears below the metadata strip listing them.
- **File filter dropdown:** Filters the rendered diff to a single file path from `files_changed[]`. "All files" is the default.
- Diff is rendered by `DiffViewer` (existing component), passed the `diff_snapshot` of the selected checkpoint.

**Threshold:** Only rendered when `checkpoints.length >= 2`. With 0–1 checkpoints, the existing single-snapshot view is shown (no scrubber).

---

## §C: File Collision Radar

### Purpose

Show the operator which files this feature is touching and highlight where other concurrently-active features are editing the same paths — before a merge conflict actually occurs. This is an early warning system unique to a multi-agent orchestrator dashboard.

### User Job

Operator running multiple parallel features wants to know which ones are on a collision course before they hit a merge conflict that blocks the queue.

### Data Sources

- `GET /api/features/[id]/files` → `string[]` — list of file paths touched by this feature (derived server-side from the feature's diff; trivial to compute).
- `DashboardStatusPayload.features: FeatureSummary[]` — already fetched on page load; provides the list of all active feature IDs.
- For each other active feature: `GET /api/features/[otherId]/files` → `string[]` — fetched lazily on Collision Radar tab activation, in parallel for all active features. Results are cached in component state for the session.

### Pure Function: `lib/collision-radar.ts`

```typescript
export interface CollisionEntry {
  path: string;
  depth: number; // directory depth (0 = root, 1 = src/, etc.)
  competing_features: string[]; // other feature IDs touching this path
  risk: 'high' | 'medium' | 'low';
}

export function deriveCollisionRisk(
  thisFeatureFiles: string[],
  otherFeatures: Record<string, string[]>, // featureId → file list
): CollisionEntry[];
```

Risk scoring:

- `high` — exact file match with ≥1 other active feature
- `medium` — same directory (one level up) with ≥1 other active feature
- `low` — same top-level package/module with ≥1 other active feature

### Component: `CollisionRadar.tsx`

Placed in the **Overview tab**, below Verification Signals. Hidden when no collisions are detected (returns empty array from `deriveCollisionRisk()`).

```
┌───────────────────────────────────────────────────────────┐
│  FILE COLLISION RADAR                                     │
│  2 high-risk · 3 medium-risk conflicts detected           │
│                                                           │
│  src/                                                     │
│    auth/                                                  │
│      ▓▓▓▓ login.ts        HIGH · feat/auth-tokens        │  ← red
│      ▒▒▒▒ session.ts      MED  · feat/db-schema          │  ← amber
│    api/                                                   │
│      ░░░░ routes.ts        LOW  · feat/route-cleanup      │  ← muted
│                                                           │
│  [Show all N files ▾]                                     │
└───────────────────────────────────────────────────────────┘
```

- Renders as an indented file tree (not a treemap — simpler, more readable at typical file counts).
- Each file shows: path, risk badge, competing feature IDs as links to `/feature/{id}`.
- Only `high` and `medium` entries shown by default. "Show all N files" toggle reveals `low`.
- If no active features share files: panel is hidden entirely.
- Loading state: shows skeleton rows while parallel file-list fetches complete.

---

## §D: Predictive Token Usage Burn Chart

### Purpose

Answer "how much token usage will this feature consume in total, and is it on track?" Dollar cost is secondary and only shown when an optional token-to-dollar conversion is configured in `policy.yaml`.

### User Job

Operator managing a token budget wants to know before a feature exceeds its limit. Team lead wants to understand usage variance across features. If a dollar conversion is configured, the same panel may also show estimated dollar impact as a derived secondary metric.

### Data Sources

- `FeatureDetail.cost: CostSummary` — current usage (`tokens_used`, optional `estimated_cost_usd`, `recorded_at`)
- `WorkerEventEntry[]` from `GET /api/features/[id]/worker-events` — per-iteration timing and request counts, enabling a usage-over-time curve
- `ProviderAnalytics[]` from `GET /api/analytics/provider` — historical `avg_tokens_used` and `avg_duration_ms` per provider/model, used as the projection baseline
- Budget policy from existing `GET /api/policy/budget`, which should expose `per_feature_limit_tokens` and optional `token_cost_conversion`
- Optional token-to-dollar conversion from `policy.yaml`, exposed via the policy endpoint when configured

### Policy Configuration

Primary budgeting is token-based.

`policy.yaml` may optionally define:

```yaml
budget:
  per_feature_limit_tokens: 250000
  token_cost_conversion:
    usd_per_1k_tokens: 0.35
```

Rules:

- `per_feature_limit_tokens` is the primary budget threshold used by the focus view.
- `token_cost_conversion` is optional.
- If `token_cost_conversion` is absent, the UI shows token usage only and does not invent or require dollar estimates.
- If `token_cost_conversion` is present, the UI may show derived USD as a secondary line or tooltip.
- If both token and dollar limits are present, token limit remains the primary budget bar and enforcement visualization; dollar values remain informational unless policy explicitly says otherwise.

### Pure Function: `lib/usage-burn.ts`

```typescript
export interface UsageBurnProjection {
  current_tokens_used: number;
  projected_final_tokens: number;
  projected_completion_at: string | null; // ISO timestamp estimate
  budget_limit_tokens: number | null;
  will_exceed_budget: boolean;
  confidence: 'high' | 'medium' | 'low'; // based on data availability
  usage_points: { timestamp: string; tokens_used: number }[]; // historical curve
  derived_usd?: {
    current_estimated_usd: number;
    projected_final_estimated_usd: number;
    usd_per_1k_tokens: number;
  } | null;
}

export function projectUsageBurnRate(
  cost: CostSummary,
  workerEvents: WorkerEventEntry[],
  providerBaseline: ProviderAnalytics | null,
  budgetLimitTokens: number | null,
  tokenCostConversion: { usd_per_1k_tokens: number } | null,
  phase: string,
): UsageBurnProjection;
```

Confidence is `high` when `workerEvents.length >= 5`, `medium` when 2–4, `low` when < 2 (falls back to provider baseline only).

### Component: `UsageBurnChart.tsx`

Placed in the **sidebar usage panel** — replaces the existing `FeatureCostPanel` with an expanded version. Basic token totals remain; derived dollar values are secondary and only rendered when conversion is configured. The chart appears below them, collapsed by default.

```
┌────────────────────────────────┐
│  USAGE                         │
│  42k of 200k tokens  (21%)     │
│  ████░░░░░░░░░░░░ budget bar  │
│  ≈ $0.42 of ≈ $2.00 est.       │  ← optional, only if conversion configured
│                                │
│  ▼ Projection                  │  ← expand toggle
│  ·····  ────────────────────── │
│       ↑ now       ↑ projected  │
│  Projected final: ~180k tokens │
│  Projected est.: ~$1.80        │  ← optional, only if conversion configured
│  Est. completion: ~47 min      │
│  ⚠ Trending 1.4× historical avg│
└────────────────────────────────┘
```

- Sparkline chart: x-axis = time (from `usage_points`), y-axis = cumulative tokens used. A dashed projection line extends from the current point to the estimated final usage.
- Budget threshold rendered against token usage.
- `will_exceed_budget === true` → budget bar turns red, warning badge shown.
- If `confidence === 'low'`: projection shown with a disclaimer: "Estimate based on provider average — insufficient data for this run."
- If token-to-dollar conversion exists, render derived USD as a secondary label only. It must never replace the token metric as the primary budget visualization.
- Chart rendered with a minimal SVG (no chart library dependency; simple polyline).

---

## §E: Feature Genealogy Tree

### Purpose

Show the complete revision history of a feature — parent revisions, the reasons they were abandoned, and which succeeded — as a visual lineage tree. This is a novel artifact of AI-driven development with no human-workflow equivalent.

### User Job

Reviewer or team lead wants to understand how many attempts it took to produce this feature and why previous versions failed, without reading through a flat history log.

### Data Sources

`GET /api/features/[id]/genealogy` → `GenealogyNode[]`:

```typescript
interface GenealogyNode {
  feature_id: string;
  phase: string;
  status: string;
  revision_of: string | null;
  revision_reason: string | null;
  plan_version: number;
  last_updated: string;
}
```

Server derives this by traversing `revision_of` links across the features index. The response is a flat array; the client builds the tree structure.

### Component: `GenealogyTree.tsx`

Placed in **Overview tab**, collapsed by default. Not shown when `revision_of === null` and no other features reference this feature's ID as their `revision_of`.

```
┌───────────────────────────────────────────────────────────┐
│  REVISION HISTORY  · 3 attempts                           │
│                                                           │
│  feat/my-feature [v1] ──(blocked: schema mismatch)──┐    │
│                                                      ↓    │
│  feat/my-feature [v2] ──(blocked: gate failure)─────┐    │
│                                                      ↓    │
│  feat/my-feature [v3] ← YOU ARE HERE                      │
│  [ready_to_merge]                                         │
└───────────────────────────────────────────────────────────┘
```

- Each node is a pill: feature ID + plan version + phase badge.
- Edges are labeled with `revision_reason` (truncated to 40 chars with tooltip).
- Current feature is highlighted.
- Clicking a past-revision node navigates to `/feature/{revision_id}`.
- If `revision_of === null` and no children: component not rendered (hidden).

---

## §F: Gate Flakiness Analyzer

### Purpose

Distinguish between tests that consistently fail (systemic code problem) and tests that flip between pass/fail across retries (flaky infrastructure). Currently impossible to see from a single gate run snapshot.

### User Job

Operator sees `gate_retry_count = 4` and needs to know: should I fix the code or fix the test infrastructure?

### Data Sources

`GET /api/features/[id]/gate-history` → `GateRunEvidence[]` — all past gate run attempts for this feature in chronological order. Server reads all gate evidence files from `.aop/features/{id}/evidence/`.

### Component: `GateFlakinessSummary.tsx`

Placed in **Gates & QA tab**, above `GateResults`. Visible only when `gate_retry_count >= 1`.

```
┌───────────────────────────────────────────────────────────┐
│  GATE HISTORY  · 4 attempts                               │
│                                                           │
│  Step                    Runs  Pass  Flakiness           │
│  ─────────────────────────────────────────────────────── │
│  npm run lint            ●●●●  4/4   0%     ✓ stable     │
│  npm run typecheck       ●●●●  4/4   0%     ✓ stable     │
│  npm run test:coverage   ●✗●✗  2/4   50%    ⚠ flaky      │
│  npm run validate:arch   ✗✗✗✗  0/4   0%     ✗ failing    │
│                                                           │
│  ● = pass   ✗ = fail  (chronological, left to right)     │
└───────────────────────────────────────────────────────────┘
```

- Each row shows: step name, mini pass/fail sparkline (colored dots, chronological), pass count/total, flakiness percentage.
- Flakiness % = (number of outcome flips / total runs − 1). A step that goes ●✗●✗ flips 3 times out of 3 transitions = 100% flaky.
- Labels: `✓ stable` (0% flaky, all pass), `⚠ flaky` (> 25% flaky), `✗ failing` (0% pass across all runs), `⚡ recovering` (last run passes, earlier runs fail).
- Clicking a step row expands `output_tail` from that step's most recent failure inline.

---

## §G: Diff Annotation for Reviewers

### Purpose

Allow reviewers to leave inline comments on specific diff hunks, exactly as in GitHub code review. Eliminates the current constraint of writing all review feedback into a single textarea with no connection to the code being discussed.

### User Job

Reviewer at `ready_to_merge` wants to say "this line specifically needs to handle the null case" — attached to the actual line, not floating in a text box.

### Data Sources

Client-side only: annotations are accumulated in component state as the reviewer comments on hunks, then bundled with the `review.request_changes` action payload.

New type (client-side only):

```typescript
interface DiffAnnotation {
  file_path: string;
  hunk_header: string; // diff hunk header line (e.g., "@@ -12,4 +12,6 @@")
  line_number: number; // line number in the modified file
  comment: string;
}
```

The `review.request_changes` action body gains an optional `annotations: DiffAnnotation[]` field. The control-plane receives this as part of the existing review action tool call — no schema changes are needed if the annotations are embedded in the review message as structured text (see implementation note below).

### Component: `AnnotatableDiffViewer.tsx`

Replaces `DiffViewer` in `ChangesTab.tsx` for `ready_to_merge` and `qa` phases. For other phases, `DiffViewer` continues to be used unchanged.

```
┌──────────────────────────────────────────────────────────┐
│  src/auth/login.ts                                       │
│  ─────────────────────────────────────────────────────── │
│  @@ -12,4 +12,6 @@                           [+ Comment] │  ← hover reveals
│   function login(user: string) {                         │
│  +  if (!user) throw new Error('missing user');  [+]     │  ← line-level button
│     return db.find(user);                                │
│   }                                                      │
│                                                          │
│  ┌─────────────────────────────────────┐                 │
│  │ 💬 Your comment on line 14:         │                 │
│  │ [What happens if user is empty str?]│                 │
│  │ [Save]  [Cancel]                    │                 │
│  └─────────────────────────────────────┘                 │
│                                                          │
│  📎 3 annotations attached to this review                │
└──────────────────────────────────────────────────────────┘
```

- Each diff line gains a `[+]` comment button on hover (not shown by default to avoid visual noise).
- Clicking `[+]` opens an inline comment popover below that line.
- Saved annotations accumulate in state and are shown as a count badge at the top of the diff: "📎 N annotations."
- When the reviewer submits via `RequestChanges`, the annotations are serialized and prepended to the review message in a structured format:

  ```
  [ANNOTATIONS]
  src/auth/login.ts:14 — What happens if user is empty str?
  src/auth/login.ts:22 — This should be async
  [/ANNOTATIONS]

  {reviewer's free-text message}
  ```

  This requires no backend schema changes — the control-plane receives it as message text.

**Implementation note:** The `AnnotatableDiffViewer` uses `diff2html` for rendering (same as existing `DiffViewer`) with a thin React overlay for the comment buttons. Monaco Editor is not required for this feature.

---

## §H: Command Palette

### Purpose

A keyboard-driven global overlay (Cmd+K / Ctrl+K) that lets power operators navigate between features, switch tabs, and trigger actions without using the mouse.

### User Job

Operator managing 15 concurrent features wants to jump to a specific feature, check its gate status, and approve it — without clicking through the board each time.

### Data Sources

- `DashboardStatusPayload.features: FeatureSummary[]` — all active feature IDs and phases (already fetched).
- Current tab state (from `FocusLayout` context) — for "Switch to [tab]" commands.

No new API endpoints required.

### Component: `CommandPalette.tsx`

Mounted at the root layout level (`app/layout.tsx`) so it is available on every page in the dashboard.

```
┌─────────────────────────────────────────────────────┐
│  ⌘K  > feat/my-feature_                             │  ← fuzzy search input
│  ─────────────────────────────────────────────────── │
│  FEATURES                                            │
│    ● feat/auth-tokens          building   → Open     │
│    ● feat/db-schema            qa         → Open     │
│    ◎ feat/my-feature           ready      → Open     │
│                                                      │
│  ACTIONS  (current feature)                          │
│    Approve feature             ↵                     │
│    Deny feature                                      │
│    Request Changes                                   │
│                                                      │
│  NAVIGATION                                          │
│    Switch to Changes tab                             │
│    Switch to Gates & QA tab                          │
│    Go to Board                                       │
└─────────────────────────────────────────────────────┘
```

**Keyboard behaviour:**

- `Cmd+K` / `Ctrl+K` — open palette
- `Escape` — close
- `↑` / `↓` — move selection
- `Enter` — execute selected command
- Typing filters all sections by fuzzy match against feature IDs, command names, and tab labels

**Command categories:**

1. **Features** — all active features from `DashboardStatusPayload.features`, fuzzy-matched against input. Selecting navigates to `/feature/{id}`.
2. **Actions** — phase-appropriate actions for the currently-viewed feature (Approve, Deny, Request Changes, Retry, Send Message). Only shown when on a `/feature/{id}` page. Executing an action calls the same handler as the button in the sidebar.
3. **Navigation** — Switch to [tab name] for each visible tab on the current feature; Go to Board.

**Implementation:**

- Global `keydown` listener registered in `useEffect` at layout level, cleaned up on unmount.
- Fuzzy matching: simple substring scoring (no dependency — implement inline, < 30 lines).
- Palette is a `position: fixed` overlay with a backdrop. Focus is trapped inside while open (`Tab` cycles through results).
- `aria-role="combobox"` with `aria-expanded`, `aria-activedescendant` for screen reader compatibility.

---

## §I: Per-Role Agent Performance Cards

### Purpose

Show how each agent role (planner, builder, QA) performed in this run compared to historical averages for the same provider/model — catching anomalous runs before they become failures.

### User Job

Operator sees a feature that has been in the `building` phase for 40 minutes and wants to know: is this normal for this provider, or is the builder stuck?

### Data Sources

- `GET /api/features/[id]/worker-events` → `WorkerEventEntry[]` — per-iteration timing and counts, filterable by `role`.
- `GET /api/analytics/provider` → `ProviderAnalytics[]` — historical averages by provider + model.
- `FeatureSummary.cluster: AgentSessionCluster` — session IDs per role.

### Component: `AgentRolePerformanceCard.tsx`

Three instances rendered in **Overview tab** (one per role: planner, builder, qa). Each card is compact — collapsed by default, expandable.

```
┌────────────────────────────────────────────────────────┐
│  🟧 BUILDER  · claude-sonnet-4-6                       │
│  ─────────────────────────────────────────────────────  │
│  Runtime:     12m 04s   (avg: 8m 22s)   ↑ 1.4×        │
│  Requests:    47         (avg: 31)       ↑ 1.5×        │
│  Patches:     8          (avg: 4)        ↑ 2.0×        │
│                                                        │
│  ████░░░  vs historical average                        │
│  ⚠ Running slower and making more requests than average│
└────────────────────────────────────────────────────────┘
```

- Metrics shown: total elapsed time, total request count, patch count, plan submission count (where relevant per role).
- Each metric shows actual value, historical average in parentheses, and a ratio badge.
- Ratio badges: `↑ Nx` in amber if > 1.5× average, `↑ Nx` in red if > 2.5× average, `✓` in green if within 0.75–1.25× average.
- A subtle horizontal bar chart compares this run to the historical average visually.
- Summary sentence: "Running slower and making more requests than average" / "On track" / "Ahead of historical average."
- Card is hidden for roles that have not yet started (`role_status === 'ready'`).

---

## §J: Merge Queue Position

### Purpose

Show where this feature sits in the merge sequence, what is blocking it from merging ahead of other features, and what it is blocking — without navigating to the board.

### User Job

Reviewer approving a `ready_to_merge` feature wants to know: "If I approve this now, when will it actually merge? What's ahead of it?"

### Data Sources

`GET /api/merge-queue` → `MergeQueueEntry[]`:

```typescript
interface MergeQueueEntry {
  feature_id: string;
  position: number; // 1-indexed position in the queue
  phase: string;
  estimated_merge_at: string | null;
  blocked_by: string[]; // feature IDs that must merge first
  blocks: string[]; // feature IDs waiting on this one
  lock_holder: boolean; // whether this feature currently holds the merge lock
}
```

Server derives this from `FeaturesIndex.dep_blocked`, `lock_leases`, and the ordered `active` feature list.

### Component: `MergeQueueCard.tsx`

Placed in the **sidebar**, below the PR status card. Visible for all non-`merged` phases. Hidden if queue has only one entry and no dependencies.

```
┌────────────────────────────────┐
│  MERGE QUEUE  · Position #3   │
│                                │
│  ① feat/auth-tokens  [rtm]   │  ← ahead (links)
│  ② feat/db-schema    [qa]    │
│  ③ YOU ARE HERE      [rtm]   │  ← current feature highlighted
│  ④ feat/route-fix    [bldg]  │  ← behind (dimmed)
│                                │
│  Waiting on: feat/auth-tokens  │  ← dep-block if applicable
│  Est. merge window: ~2h 15m   │  ← based on avg phase durations
└────────────────────────────────┘
```

- Queue positions are rendered as a compact vertical list (max 5 shown; "N more" collapse for longer queues).
- Features ahead of current are shown normally; features behind are dimmed.
- Current feature is bold with a `YOU ARE HERE` label.
- `Waiting on` row appears when `blocked_by` is non-empty.
- `Est. merge window` is omitted when `confidence` is too low (insufficient historical data).
- Each feature ID in the list is a link to `/feature/{id}`.

---

## New API Endpoint Contracts

All new endpoints follow the existing Next.js API route pattern in `packages/web-dashboard/src/app/api/`. Response envelope: `{ ok: true, data: T } | { ok: false, error: string }`.

### `GET /api/features/[id]/files`

Returns the sorted list of file paths modified by this feature's current diff.

```typescript
// Response: string[]
// e.g. ["src/auth/login.ts", "src/auth/session.ts", "test/auth.spec.ts"]
```

Implementation: parse the diff from `FeatureDetail.diff` using the same diff parser already used in `aop-client.ts`. Extract `+++ b/<path>` lines. No filesystem access required beyond what already exists.

### `GET /api/features/[id]/gate-history`

Returns all past gate run evidence for this feature, in chronological order.

```typescript
// Response: GateRunEvidence[]
```

Implementation: reads all `evidence/gate_run_*.json` files from `.aop/features/{id}/evidence/` and returns them sorted by `finished_at`.

### `GET /api/features/[id]/genealogy`

Returns the revision chain for this feature (ancestors and descendants).

```typescript
// Response: GenealogyNode[]
interface GenealogyNode {
  feature_id: string;
  phase: string;
  status: string;
  revision_of: string | null;
  revision_reason: string | null;
  plan_version: number;
  last_updated: string;
}
```

Implementation: traverse `revision_of` links from the features index. Maximum depth: 20 (guard against cycles).

### `GET /api/features/[id]/worker-events`

Returns the worker event ledger for this feature.

```typescript
// Response: WorkerEventEntry[]
```

Implementation: reads from the existing worker event ledger file (same source as the runtime inspector). Filtered to entries with `feature_id === id`.

### `GET /api/analytics/provider`

Returns historical performance analytics aggregated by provider + model.

```typescript
// Response: ProviderAnalytics[]
```

Implementation: aggregates `WorkerEventEntry` data across all features in the index. Can be cached with a 5-minute TTL since this is aggregate historical data.

### `GET /api/merge-queue`

Returns the ordered merge queue with dependency information.

```typescript
// Response: MergeQueueEntry[]
```

Implementation: derived from `FeaturesIndex.active`, `dep_blocked`, and `lock_leases`. Order: lock holder first, then dep-unblocked features in index order, then dep-blocked features last.

### `GET /api/features/[id]/log-files` (§A only)

Returns metadata for raw log files per role, enabling on-demand download links in the transcript view.

```typescript
// Response: RawLogFileMeta[]
```

Implementation: reads log file metadata from the feature's runtime artifact directory. Fetched only on "↓ raw log" click — never eagerly on tab activation.

---

## Component Files (Track B)

| New File                                        | Responsibility                                                                                                 | Approx. Lines |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------- |
| `components/focus/CheckpointScrubber.tsx`       | Timeline slider for scrubbing through checkpoint diffs chronologically                                         | ~140          |
| `components/focus/CollisionRadar.tsx`           | Directory treemap showing cross-feature file collision risk                                                    | ~200          |
| `components/focus/UsageBurnChart.tsx`           | Token-usage-over-time sparkline with projected final usage and optional derived dollar overlay                 | ~130          |
| `components/focus/GenealogyTree.tsx`            | Revision ancestry tree with reason labels                                                                      | ~120          |
| `components/focus/GateFlakinessSummary.tsx`     | Per-gate-step sparkline of pass/fail history with flakiness score                                              | ~150          |
| `components/focus/AnnotatableDiffViewer.tsx`    | Monaco-based diff with click-to-annotate hunk comments                                                         | ~200          |
| `components/CommandPalette.tsx`                 | Global Cmd+K overlay with fuzzy feature search and action dispatch                                             | ~220          |
| `components/focus/AgentRolePerformanceCard.tsx` | Per-role metrics card comparing this run to historical provider averages                                       | ~100          |
| `components/focus/MergeQueueCard.tsx`           | This feature's position in the merge queue with dependency blockers                                            | ~110          |
| `lib/collision-radar.ts`                        | `deriveCollisionRisk()` pure function mapping file paths to risk scores across active features                 | ~80           |
| `lib/usage-burn.ts`                             | `projectUsageBurnRate()` pure function extrapolating token usage/time from current spend and provider averages | ~80           |

---

## Migration Path (Track B Steps)

These steps follow the completion of all Track A migration steps.

11. Implement new API endpoints (listed above) in the Next.js route handlers.
12. Build Track B pure lib functions first (`collision-radar.ts`, `usage-burn.ts`), then leaf components, then tab integrations.
13. Add `CommandPalette` to the root layout (`app/layout.tsx`) so it is available on all pages.
14. Wire `CollisionRadar`, `UsageBurnChart`, `GenealogyTree`, `AgentRolePerformanceCard`, `MergeQueueCard`, and Track B transcript/scrubber enhancements into the appropriate tabs and sidebar locations.
15. Run full test suite and `npm run build`.

---

## Success Criteria

| Feature                 | Criterion                                                            | How Verified                                                                                                                                               |
| ----------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Agent Transcript        | Entries grouped by role into turns; code fences syntax-highlighted   | Unit test: render 10 entries alternating planner/builder, assert 5 bubbles; assert `<code>` block in builder turn                                          |
| Agent Transcript        | Role filter pills hide turns from other roles                        | Unit test: select "Planner" filter, assert builder turns absent from DOM                                                                                   |
| Code Evolution Scrubber | Slider position updates displayed diff without network request       | Unit test: render scrubber with 5 checkpoints, change slider to index 3, assert `DiffViewer` receives `checkpoints[3].diff_snapshot`, assert 0 fetch calls |
| Code Evolution Scrubber | Play mode advances one checkpoint per 1.2s                           | Unit test: mock timers, click Play, advance 2.4s, assert index incremented by 2                                                                            |
| File Collision Radar    | Hidden when no files overlap with other active features              | Unit test: `deriveCollisionRisk(thisFiles, {})` returns `[]`; assert panel absent from DOM                                                                 |
| File Collision Radar    | High-risk files shown first; low-risk collapsed by default           | Unit test: render with 1 high + 2 low risk entries, assert low hidden; click toggle, assert visible                                                        |
| Usage Burn Chart        | `will_exceed_budget === true` turns token budget bar red             | Unit test: `projectUsageBurnRate` with projected tokens > token budget, assert red class on bar                                                            |
| Usage Burn Chart        | `confidence === 'low'` shows disclaimer                              | Unit test: render with 0 worker events, assert disclaimer text present                                                                                     |
| Usage Burn Chart        | Derived USD hidden when token-to-dollar conversion is not configured | Unit test: render with `tokenCostConversion = null`, assert no dollar labels shown                                                                         |
| Genealogy Tree          | Hidden when `revision_of === null` and no children                   | Unit test: render with isolated feature, assert tree absent                                                                                                |
| Genealogy Tree          | Nodes link to correct `/feature/{id}` paths                          | Unit test: click ancestor node, assert navigation to correct URL                                                                                           |
| Gate Flakiness          | Flakiness % correct for alternating pass/fail pattern                | Unit test: 4 runs [pass, fail, pass, fail] = 3 flips / 3 transitions = 100%                                                                                |
| Gate Flakiness          | Stable steps labeled `✓ stable`, flaky labeled `⚠ flaky`             | Unit test: assert label class for each pattern                                                                                                             |
| Diff Annotation         | Annotations serialized as structured text in review message          | Unit test: submit request_changes with 2 annotations, assert message contains `[ANNOTATIONS]` block                                                        |
| Diff Annotation         | `[+]` button only visible on hover                                   | Snapshot test: assert button has `opacity: 0` by default, `opacity: 1` on hover (CSS class)                                                                |
| Command Palette         | Opens on Cmd+K, closes on Escape                                     | Unit test: dispatch `keydown` with metaKey+k, assert palette visible; dispatch Escape, assert hidden                                                       |
| Command Palette         | Feature list filtered by fuzzy match                                 | Unit test: type "auth", assert only features containing "auth" in ID are shown                                                                             |
| Command Palette         | Action commands only shown on `/feature/{id}` pages                  | Unit test: render palette on board page, assert ACTIONS section absent                                                                                     |
| Per-Role Cards          | Cards hidden for roles with `role_status === 'ready'`                | Unit test: render with all roles ready, assert no cards rendered                                                                                           |
| Per-Role Cards          | Ratio > 2.5× shows red badge                                         | Unit test: `elapsed_ms` = 3× provider average, assert red ratio badge                                                                                      |
| Merge Queue             | `YOU ARE HERE` label on current feature                              | Unit test: render queue with current feature at position 3, assert label at index 2                                                                        |
| Merge Queue             | Hidden when queue has one entry and no dependencies                  | Unit test: render with single-entry queue and `blocked_by: []`, assert card absent                                                                         |
