# Feature Spec: Dashboard UX Improvements (AOP)

> **Purpose of this document**: Define implementation-ready improvements to the AOP web dashboard (`packages/web-dashboard`) to make feature progress understandable at a glance, eliminate hidden information, and support the full operational workflow without requiring CLI fallback for routine tasks.

**Version:** 1.0
**Date:** 2026-03-05
**Status:** Draft
**Roadmap Mapping:** M44

---

## 0. Scope and Standards

### 0.1 Background and Motivation

The current dashboard (`packages/web-dashboard/src/app/page.tsx`) is a single-file, inline-styled React component that renders a Kanban board with six phase columns and a right-side detail panel. It is functional but has significant usability deficits:

- **No aggregate situational awareness**: A developer must scan all columns to understand system health.
- **Feature cards are opaque**: Cards show only `feature_id`, a `status` badge, and an `activity_state` badge. Gate results, PR CI status, last-activity time, and lock state are invisible until a card is clicked.
- **Detail panel is phase-agnostic**: The approve/deny review form renders for all features regardless of phase. A feature in `building` never needs an approval token; showing one adds noise and invites misuse.
- **Plan is unreadable**: `plan.json` is not exposed in the panel at all; its structure (goal, tasks, affected files) is left inaccessible.
- **Diff is unstyled**: The diff section is a raw `<pre>` dump with no syntax highlighting, no line numbers, and no file grouping.
- **Evidence is just a link list**: Artifacts are rendered as unstyled `<a>` tags with no inline preview and no indication of which artifact matters most.
- **Action feedback is inline text**: `actionResult` is a single static string appended below the button grid; it does not differentiate success from failure and does not auto-clear.
- **Blocked collision context is missing**: The `blocked_queue` in the index contains collision fingerprints, but the dashboard never surfaces this information.
- **Human input is invisible**: When `activity_state === 'waiting_input'` the developer has no dashboard path to respond; they must drop to the CLI.
- **No search or filter**: With many active features, there is no way to narrow the board.
- **No keyboard navigation**: All interaction requires mouse clicks.
- **Entire UI is inline-styled**: All CSS is authored as JS object literals inside JSX, making systematic visual consistency impossible.

### 0.2 Required Standards

All implementation MUST:

- Preserve the existing SSE live-update behavior (`/api/events`).
- Preserve all existing API contracts (`/api/status`, `/api/actions`, `/api/features/:id`, `/api/features/:id/evidence/:name`, `/api/features/:id/checkout`).
- Not break the existing Next.js app structure or build pipeline.
- Maintain TypeScript strict compliance and pass `npm run typecheck`.
- Pass `npm run lint` with zero warnings.
- Maintain or improve test coverage thresholds (Lines ≥ 90%, Branches ≥ 90%, Functions ≥ 90%, Statements ≥ 90%).
- Follow the design principles in `CLAUDE.md`: KISS, SRP, composition over inheritance.

### 0.3 Implementation Approach

The dashboard is a Next.js app. New UI features should be implemented as:

- **Separate React components** per concern (not monolithic additions to `page.tsx`).
- **A shared CSS module or Tailwind** rather than inline styles, so visual tokens are defined once.
- **New API route files** for any new server-side capabilities (e.g., `/api/features/:id/log`).
- Additions to `src/lib/types.ts` when new data shapes are needed.
- Additions to `src/lib/aop-client.ts` when new file reads are needed.

---

## 1. Improvements

### UX-01 — Command Center Summary Bar

**What**: Add a persistent top-of-page summary bar that shows, in a single glance: total active features, features blocked, features awaiting human input, features ready to merge, and features with failing CI.

**Why it matters**: The current layout requires scanning six columns and mentally counting cards. Developers frequently need to answer "Is anything on fire right now?" The summary bar answers this instantly, surfacing the two highest-urgency states — blocked and awaiting_input — prominently before a developer even reads the board.

**Specification**:

- Five stat tiles: `Active`, `Blocked`, `Awaiting Input`, `Ready to Merge`, `CI Failing`.
- Each tile shows a count and a label.
- Tiles for `Blocked`, `Awaiting Input`, and `CI Failing` render in an alert color when count > 0 (e.g., red/amber); tiles with zero stay in neutral styling.
- Clicking a tile applies the corresponding column/filter as a pre-set quick filter (see UX-09).
- Counts are derived from the existing SSE-pushed `DashboardStatusPayload`; no new API calls.

---

### UX-02 — Rich Feature Cards

**What**: Expand each Kanban card to show: feature_id (truncated to 24 chars with tooltip), phase-color left-border (already present), last_updated as a relative time string ("3m ago"), gate status icon row (fast/full/merge: ✅/❌/—), PR CI status icon, and activity state dot.

**Why it matters**: A card currently shows only the raw `feature_id` string and two verbose badges. Developers must click every card to see if its tests are passing. Surfacing gate results and CI status on the card eliminates the most common "click to check" interaction pattern and makes the board scannable at a glance.

**Specification**:

- Relative time: compute from `last_updated` ISO string using a simple `formatRelativeTime` utility.
- Gate row: map `feature.gates` (fast/full/merge → pass/fail/na) to three small icons or colored dots (green/red/grey). If `gates` is absent, omit the row silently.
- PR CI icon: map `feature.pr.ci_status` to a small icon (✅ success, ❌ failure, 🕐 pending, — none).
- Activity dot: a small colored circle beside the feature_id (green = active, amber = idle, blue = waiting_input, red = blocked, grey = exited).
- Cards remain clickable; clicking selects the feature for detail view.

---

### UX-03 — Phase-Aware Detail Panel

**What**: The right-side detail panel should render context-appropriate controls for the feature's current phase instead of always showing the full review form.

**Why it matters**: Showing an approval token input and Approve/Deny buttons for a `building` feature creates confusion and invites accidental actions. Each phase has a distinct set of meaningful actions; surfacing only those actions reduces cognitive load and mistakes.

**Specification**:

| Phase            | Shown controls                                                                                                 |
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `planning`       | View plan (UX-04), no action buttons                                                                           |
| `building`       | View plan (UX-04), view diff (UX-05), "Request Changes" (sends message to agent)                               |
| `qa`             | View gate results (UX-06), view diff (UX-05), evidence viewer (UX-07), "Request Changes"                       |
| `ready_to_merge` | Full review form: approve (with token + merge strategy selector), deny (with required reason), request changes |
| `blocked`        | Block reason display, gate results, diff view, "Retry" button (calls `aop retry`)                              |
| `merged`         | Read-only: PR link, merge commit hash                                                                          |

- The "Review Actions" section renders only when `phase === 'ready_to_merge'`.
- All other phases show phase-appropriate read-only data or limited action buttons.

---

### UX-04 — Structured Plan Viewer

**What**: Render `plan.json` as a structured, human-readable section rather than ignoring it entirely. Show: goal statement, task list with completion indicators, affected file count, gate profile, plan version.

**Why it matters**: The plan is the primary specification of what an agent is doing. It is currently inaccessible from the dashboard even though it's fetched as part of `FeatureDetail`. Developers reviewing a feature before approving it need to understand what was planned.

**Specification**:

- Read `detail.plan` from existing `FeatureDetail` (already fetched, already typed as `Record<string, unknown>`).
- Render as a collapsible `<details>` section labeled "Plan".
- Extract and render: `plan.goal` (string), `plan.tasks` (array of objects with at minimum a `description` field), `plan.files` (array of file paths), `plan.gate_profile` (string), `plan.plan_version` (number).
- Tasks render as a list; if a task has `status: 'done'` render with a checkmark; if `status: 'pending'` render as open circle.
- If `detail.plan` is null, show "No plan available".
- All field extraction must be safe (fall back to "—" or omit section if field is absent).

---

### UX-05 — Diff Viewer with Syntax Highlighting

**What**: Replace the raw `<pre>` diff dump with a syntax-highlighted unified diff viewer that shows file headers, line numbers, green additions, and red deletions.

**Why it matters**: Reading a raw patch file in a mono-colored `<pre>` block is genuinely painful for any diff longer than a few lines. Color-coded additions and deletions with file grouping is the industry baseline for code review UX.

**Specification**:

- Parse the raw `diff` string from `FeatureDetail.diff` into a structured representation:
  - Split on `diff --git` to extract per-file hunks.
  - For each hunk, split on `@@` to identify change ranges.
  - Lines starting with `+` (not `+++`) render green; lines starting with `-` (not `---`) render red; context lines render in default color.
- Render as a scrollable block with a fixed max-height (e.g., 400px), horizontal scroll for long lines.
- Add a file-level header row for each `diff --git` section showing the file path.
- Add line numbers in a non-selectable gutter column.
- Do not add a third-party dependency for this; the parsing is simple enough to implement locally.
- If `detail.diff` is empty, show "No diff available."

---

### UX-06 — Gate Results Visualization

**What**: In the detail panel, render the `gates` map (fast/full/merge → pass/fail/na) as a structured table or badge row with pass rates and the active gate profile shown.

**Why it matters**: Gate results determine whether a feature can advance. They are stored in the state frontmatter but completely hidden from the dashboard UI.

**Specification**:

- Read `detail.feature.gates` (object) and `detail.feature.gate_profile` (string).
- Render as a small table or badge row: one row/badge per gate mode.
- Color coding: pass = green, fail = red, na = grey.
- Show which gate profile is active ("Profile: `fast`").
- If no gate data is present, show "No gate results recorded."

---

### UX-07 — Inline Evidence Viewer

**What**: Replace the plain link list in the "Evidence" section with an expandable inline viewer. Clicking an artifact opens its text content below the link row.

**Why it matters**: Currently evidence artifacts are only accessible by following a URL, which downloads or opens the file in a new browser tab. For gate output, test summaries, or coverage reports, developers want to see the content inline without losing their place in the dashboard.

**Specification**:

- Evidence list renders as before with artifact name links.
- Each artifact row gains an "Expand" toggle button.
- When expanded, fetch the artifact content from `/api/features/:id/evidence/:name` (already implemented).
- Render the content in a scrollable `<pre>` block (max-height 300px) below the row.
- For `.json` artifacts, pretty-print with `JSON.stringify(parsed, null, 2)`.
- Only one artifact can be expanded at a time to avoid clutter.

---

### UX-08 — Human Input Response Panel

**What**: When `feature.activity_state === 'waiting_input'`, show a dedicated prominent section in the detail panel with a text input and "Send Answer" button that invokes the `feature.send_message` tool.

**Why it matters**: The M43 human-input protocol spec defines a first-class question/answer lifecycle. Without dashboard support, every agent question requires dropping to the CLI (`aop answer` or `aop send`). This creates a friction point that breaks flow, especially for developers who are monitoring the dashboard.

**Specification**:

- Render only when `feature.activity_state === 'waiting_input'`.
- Show a distinct banner at the top of the detail panel: "⏳ Awaiting your input".
- A labeled `<textarea>` for the answer with placeholder "Type your response…".
- A "Send Answer" button that POSTs to `/api/actions` with `action: 'feature.send_message'`, `feature_id`, and `message`.
- After success, refresh the feature detail (triggers SSE update).
- If M43 tools (`feature.question_list`, `feature.question_answer`) are later implemented, this panel should call them instead of `feature.send_message`.

---

### UX-09 — Search and Filter Bar

**What**: Add a filter bar below the summary bar allowing developers to filter the Kanban board by: free-text search on feature_id, phase (multi-select), activity_state (multi-select), gate status (has failures), and CI status.

**Why it matters**: In a large repository with many concurrent features, the board becomes noise-heavy. Search and filter is a basic usability requirement for any list or board view with more than 10 items.

**Specification**:

- Text search box: filters cards where `feature_id.includes(searchText)`.
- Phase filter: checkbox or multi-select dropdown; default = all phases shown.
- Activity filter: checkbox or multi-select; default = all.
- Gate failures toggle: when on, show only features where any gate has `fail` status.
- CI failures toggle: when on, show only features where `pr.ci_status` is not `success`.
- Filter state is local React state; no URL persistence required for v1.
- When filters are active, columns that would be empty due to filtering show an empty state message (see UX-14) rather than disappearing.
- A "Clear Filters" button resets all filters.

---

### UX-10 — Toast Notification System

**What**: Replace the inline `actionResult` string below the button grid with a proper toast notification system: ephemeral banners that auto-dismiss after 4 seconds, positioned in a corner, with distinct success (green) and error (red) visual styles.

**Why it matters**: A single `actionResult` string has no persistence, no visual priority differentiation between success and error, and never auto-clears. Developers have to actively look for it after taking an action. Toast notifications are the industry standard mechanism for asynchronous operation feedback.

**Specification**:

- Implement a `ToastNotification` component and `useToast` hook in a new `src/components/toast.tsx` file.
- Notifications have: `id` (uuid), `message` (string), `type` ('success' | 'error' | 'info'), `timestamp`.
- Each notification auto-dismisses after 4 seconds.
- Notifications are stacked vertically in the bottom-right corner, z-index above all content.
- Success toasts: green background; error toasts: red background; info: blue.
- Replace all `setActionResult(...)` calls in `page.tsx` with `addToast(...)` calls.

---

### UX-11 — Confirmation Dialogs for Destructive Actions

**What**: "Deny" and "Request Changes" actions should require a confirmation step before being submitted. Currently they fire on first click.

**Why it matters**: Deny is irreversible in its current form (sets status to `blocked`, logs the denial). An accidental click has real consequences. A confirmation step — "Deny feature X? This will block the feature. Reason: [shown]" — protects against fat-finger errors. This is a basic safety affordance for any destructive action in a UI.

**Specification**:

- "Deny" click opens a confirmation modal: shows feature_id, shows the entered reason (or error if empty), has "Confirm Deny" and "Cancel" buttons.
- "Request Changes" click opens a confirmation modal: shows feature_id, shows the message, has "Confirm" and "Cancel" buttons.
- "Approve" does NOT require confirmation (it's already gated by the approval token, which serves as the confirmation mechanism).
- Modals are rendered via a `ConfirmationModal` component in `src/components/confirmation-modal.tsx`.
- Pressing Escape or clicking outside the modal cancels the action.

---

### UX-12 — Collision Queue Panel

**What**: When the `index.blocked_queue` array is non-empty, show a dedicated "Collision Queue" section at the bottom of the board or as a banner. Display which features are queued, their `detected_at` timestamp, and their `collision_fingerprint`.

**Why it matters**: The blocked queue is a critical operational signal — it means features are waiting because of resource conflicts. This information is completely invisible in the current dashboard even though it's included in `FeaturesIndex`. Developers may not realize features are stuck in queue versus actively blocked.

**Specification**:

- Render only when `payload.index.blocked_queue?.length > 0`.
- Show as a horizontal banner or a dedicated panel below the Kanban columns.
- Each queue entry shows: `feature_id`, `detected_at` (relative time), `collision_fingerprint` (truncated to 32 chars with tooltip showing full value).
- A count in the summary bar tile (UX-01) links to this section.

---

### UX-13 — PR Status Card

**What**: When `feature.pr` is non-null, render a dedicated "Pull Request" sub-section in the detail panel that uses visual indicators (icons, color) rather than raw text.

**Why it matters**: Currently PR data is rendered as: `PR: #42 | CI: success | Review: none | Merge Score: 85`. This is dense and requires mental parsing. Visual status indicators (checkmarks, X icons, warning triangles) communicate status faster and are less error-prone to scan.

**Specification**:

- Render as a card/section: "Pull Request #42" header with a link to `pr.url` (if present).
- CI status: rendered as a labeled icon row (✅ / ❌ / ⏳ / —).
- Review decision: rendered as a labeled icon row (✅ approved / ❌ changes requested / ⏳ pending / — none).
- Merge ready: shown as a prominent green/red indicator.
- Has conflicts: if `pr.has_conflicts`, show a red "⚠ Conflicts detected" badge.
- Pending review threads: if > 0, show "💬 N thread(s) pending" badge.
- Merge score: shown as a numeric score with a simple color-coded bar (green ≥ 80, amber 50–79, red < 50).

---

### UX-14 — Empty State Design

**What**: When a Kanban column has no features (naturally or due to filtering), show a meaningful empty state message instead of a blank card area.

**Why it matters**: Empty white space leaves ambiguity: is this column empty because there are no features in this phase, or because of a data loading error, or because filters are hiding them? A brief label removes this ambiguity and can provide guiding instructions for first-time users.

**Specification**:

- Default empty state (no features, no filters): column body shows a centered, muted message.
  - `Planning`: "Run `aop run --goal '…'` to start a feature."
  - `Building`, `QA`, `Ready to Merge`, `Merged`: "No features in this phase."
  - `Blocked`: "No blocked features." (positive framing)
- Filtered empty state (features exist but filtered out): "No features match current filters."
- No icon or illustration required; plain text is sufficient.

---

### UX-15 — Independent Column Scrolling

**What**: Each Kanban column should scroll its card list independently when there are more cards than fit in the visible viewport. The column headers should remain visible (sticky) while the card lists scroll.

**Why it matters**: Currently, a column with many features causes the entire page to grow vertically, breaking the Kanban mental model. In a Kanban board, columns should have fixed height with internal scroll so all columns remain visible simultaneously.

**Specification**:

- Give each column a fixed or max-height (e.g., `calc(100vh - 200px)`) with `overflow-y: auto`.
- Column headers (`<header>` with column title and count) must be `position: sticky; top: 0` within the column.
- This change affects only layout/CSS; no data model changes required.

---

### UX-16 — Real-Time Activity Indicators

**What**: For features with `activity_state === 'active'`, show a subtle animated pulse on the card and in the detail panel to indicate live ongoing work. Show elapsed time in the current phase.

**Why it matters**: With SSE updating every 2 seconds, the board is live. But there is no visual indication of which features are actively changing right now versus which have been sitting idle in a phase. An animated pulse on active cards makes the "live" quality of the board visually apparent and helps developers know where to focus attention.

**Specification**:

- Active features get a pulsing animation on their left-border color (CSS `@keyframes pulse`).
- Idle and other non-active features do not animate.
- In the detail panel, show "Time in phase: Xh Ym" computed from `last_updated` to now.
- The elapsed time updates in real-time via a local `setInterval(1000)` in the detail panel component (not via API calls).
- Cap elapsed time display at 99h; show ">" prefix if exceeded.

---

### UX-17 — Merge Strategy Selector

**What**: The approval form's merge strategy field currently defaults to `merge_commit` with no UI control. Add a select dropdown with the three supported options: `merge_commit`, `squash`, `rebase`.

**Why it matters**: Different teams and repositories have different merge conventions. Squash merges are common for feature branches. Forcing users to accept the `merge_commit` default means they either get the wrong merge type for their workflow or must use the CLI to specify an alternative.

**Specification**:

- Add a labeled `<select>` element for `merge_strategy` in the approve section of the review panel.
- Default value: `merge_commit`.
- Options: `merge_commit` ("Merge Commit"), `squash` ("Squash and Merge"), `rebase` ("Rebase and Merge").
- Pass selected value to the existing `POST /api/actions` payload as `merge_strategy`.
- This change is purely additive to the existing `ActionsRequest` interface (field already defined).

---

### UX-18 — Keyboard Navigation

**What**: Allow developers to navigate between feature cards using keyboard alone and trigger common actions with shortcuts.

**Why it matters**: Developers who live in terminals and editors are accustomed to keyboard-first UX. Making the dashboard keyboard-navigable reduces context switching friction and allows quick scanning of feature states without reaching for the mouse.

**Specification**:

- `↑` / `↓` arrows: navigate between feature cards within the currently focused column.
- `←` / `→` arrows: move focus between columns.
- `Enter`: open the detail panel for the focused feature (same as click).
- `Escape`: close the detail panel / dismiss modal.
- `Cmd/Ctrl + F` or `/`: focus the search box (UX-09).
- Visual focus indicator: focused card gets a clearly visible focus ring (keyboard-only, not hover).
- Focus state managed in React state alongside `selectedFeatureId`.
- No additional library needed; native `tabIndex`, `onKeyDown` handlers, and `useRef` are sufficient.

---

### UX-19 — Component Architecture Refactor

**What**: Extract the monolithic `page.tsx` into focused, independently-testable React components. Move all inline styles to a CSS module or a `globals.css` design-token approach.

**Why it matters**: The current single-file architecture with ~457 lines of JSX and no CSS classes violates SRP and makes every improvement in this spec riskier to implement safely. Extracting components creates natural seams for testing, enables parallel implementation of improvements, and eliminates the fragile inline-style soup.

**Specification**:

Extract the following components (minimum):

- `src/components/summary-bar.tsx` — implements UX-01
- `src/components/kanban-board.tsx` — board layout + column rendering
- `src/components/feature-card.tsx` — implements UX-02
- `src/components/detail-panel.tsx` — right-side detail, orchestrates sub-components
- `src/components/plan-viewer.tsx` — implements UX-04
- `src/components/diff-viewer.tsx` — implements UX-05
- `src/components/gate-results.tsx` — implements UX-06
- `src/components/evidence-viewer.tsx` — implements UX-07
- `src/components/human-input-panel.tsx` — implements UX-08
- `src/components/pr-status-card.tsx` — implements UX-13
- `src/components/filter-bar.tsx` — implements UX-09
- `src/components/toast.tsx` — implements UX-10
- `src/components/confirmation-modal.tsx` — implements UX-11
- `src/components/collision-queue.tsx` — implements UX-12

CSS approach:

- Replace all inline `style={{...}}` objects with CSS classes defined in a `src/styles/dashboard.module.css` file (Next.js CSS Modules) or extend `globals.css` with named CSS custom properties for color tokens.
- Design tokens defined once: `--color-planning`, `--color-building`, `--color-qa`, `--color-ready`, `--color-blocked`, `--color-merged`, `--bg-card`, `--bg-panel`, `--bg-page`, `--text-primary`, `--text-muted`.

---

### UX-20 — Feature State History / Activity Log

**What**: Add a collapsible "History" section to the detail panel that shows a chronological list of state transitions and logged events for the selected feature.

**Why it matters**: Currently there is no way from the dashboard to understand _how_ a feature reached its current state. Was it previously blocked? Did gates fail twice before passing? What messages were sent? The state file's `state.md` body and the operation ledger contain this history, but it is completely inaccessible.

**Specification**:

- Add a new API route `GET /api/features/:id/log` (alongside the existing routes).
- Server-side: read and parse the `state.md` body (below the frontmatter) and return it as structured log entries. Parse lines of the form `[timestamp] message` or return the raw body if structured parsing is not possible.
- Optionally also read `.aop/features/:id/evidence/` files timestamped metadata as log events.
- Return type: `{ entries: Array<{ timestamp: string; message: string; type: 'state_change' | 'log' | 'evidence' }> }`.
- In the detail panel, render as a collapsible `<details>` section labeled "History" (collapsed by default).
- Entries show relative timestamp and message text in reverse-chronological order.
- Maximum 50 entries shown; a "Show all" link if more exist.

---

## 2. Implementation Priorities

### 2.1 Priority Tiers

**Tier 1 — Foundational (do first, enables everything else):**

- UX-19: Component architecture refactor — this is a prerequisite for cleanly implementing all other improvements.
- UX-10: Toast notification system — needed for feedback in all action-triggering improvements.
- UX-14: Empty state design — required by UX-09.

**Tier 2 — High-Impact Visibility:**

- UX-01: Summary bar
- UX-02: Rich feature cards
- UX-06: Gate results visualization
- UX-15: Independent column scrolling

**Tier 3 — Workflow Improvements:**

- UX-03: Phase-aware detail panel
- UX-08: Human input response panel
- UX-09: Search and filter bar
- UX-11: Confirmation dialogs
- UX-12: Collision queue panel
- UX-13: PR status card

**Tier 4 — Depth and Polish:**

- UX-04: Structured plan viewer
- UX-05: Diff viewer with syntax highlighting
- UX-07: Inline evidence viewer
- UX-16: Real-time activity indicators
- UX-17: Merge strategy selector
- UX-18: Keyboard navigation
- UX-20: Feature state history / activity log

---

## 3. API Additions Required

The following new API routes are needed to support this spec:

| Route                   | Method | Purpose                             | Spec  |
| ----------------------- | ------ | ----------------------------------- | ----- |
| `/api/features/:id/log` | GET    | Return parsed state history entries | UX-20 |

All other improvements use existing API contracts without modification.

---

## 4. Data Shape Additions Required

No new MCP tools or backend data model changes are required for this spec. All data needed by these improvements (gates, pr, last_updated, activity_state, locked_queue, plan, diff, evidence) is already fetched by existing API routes and returned in existing types.

The following additions to `src/lib/types.ts` may be helpful:

```typescript
// For UX-20
export interface FeatureLogEntry {
  timestamp: string;
  message: string;
  type: 'state_change' | 'log' | 'evidence';
}

export interface FeatureLog {
  entries: FeatureLogEntry[];
  total: number;
}
```

---

## 5. Acceptance Criteria

Each improvement is accepted when:

1. The feature renders correctly in `npm run dev` (hot reload mode).
2. `npm run build` succeeds without TypeScript or lint errors.
3. Any new component has a corresponding test file in `packages/web-dashboard/src/` or the project's test directory.
4. No existing API contract is broken (verified by `npm run validate:mcp-contracts`).
5. No architecture boundary violations introduced (verified by `npm run validate:architecture`).

---

## 6. Out of Scope

- Mobile/responsive layout (beyond what column scroll in UX-15 provides).
- Dark/light theme toggle (the dark theme is canonical; do not add a switcher).
- Dashboard authentication/authorization — covered by the separate M37 enterprise governance spec.
- External metric dashboards or Grafana-style time-series charts.
- Historical trend analytics (feature throughput, cycle time over time).
- Drag-and-drop Kanban reordering (features advance through phases via orchestrator logic, not human drag).
