Skip to content

feat(dashboard): task & skill ledger views (mcp-browser-use adoption F) #865

@shaun0927

Description

@shaun0927

Why

The terminal dashboard (src/dashboard/) already renders main-view, sessions-view, tabs-view, and connect-view against the live activity tracker. Once the task ledger from issue A (#855) and the skill-replay results from issue B (#856) exist, an operator running a long-lived daemon needs to see them at a glance rather than greping JSON files. Without a dashboard surface, the new persistent state remains invisible during day-to-day operation, which significantly reduces the perceived value of the underlying ledger / replay work.

The dashboard is a TERMINAL ANSI renderer (src/dashboard/renderer.ts), not web. Adding two more views matches an established pattern (each existing view file is ~100-300 lines and read-only over a typed data source).

What

Add two new ANSI views to the existing terminal dashboard:

  1. src/dashboard/views/tasks-view.ts — read-only display of the task ledger (issue A (feat(core): oc_task_ledger — persistent async task table with cancel & wait (mcp-browser-use adoption A) #855)). Lists active and recent tasks with kind, status, age, and live progress (from issue D (feat(core): MCP progress notifications for long-running tools (mcp-browser-use adoption D) #863)).
  2. src/dashboard/views/skills-view.ts — read-only display of ~/.openchrome/skill-memory/<domain>/skills.json (existing storage from feat(skill-memory): MCP tools oc_skill_record + oc_skill_recall (read-only / explicit-write core API) #785). Lists skills per domain with usage counts and last-replay status (from issue B (feat(pilot): oc_skill_replay — deterministic CDP-step replay with oc_assert contract gate (mcp-browser-use adoption B) #856)).

Boundary:

  • Two new files under src/dashboard/views/.
  • src/dashboard/keyboard-handler.ts extended with t (tasks view) and k (skills view) hotkeys; existing key bindings unchanged.
  • src/dashboard/index.ts register the two new views in the view router.
  • Read paths only — neither view mutates state. Cancel, replay, etc. remain MCP-tool actions, not dashboard actions (avoids state-modification UX scope here).
  • No new dependencies. Uses existing ANSI, formatTime, formatDuration, pad, truncate, BOX helpers from src/dashboard/ansi.ts.

Contract

// src/dashboard/views/tasks-view.ts
export interface TasksViewData {
  tasks: TaskMeta[];           // from src/core/task-ledger/types.ts (#A)
  liveProgress: Record<string, { progress: number; total?: number; message?: string }>; // (#D)
  refreshedAt: number;
  spinnerFrame: number;
}

export class TasksView {
  constructor(renderer: Renderer);
  render(data: TasksViewData, size: ScreenSize): string[];
}

// src/dashboard/views/skills-view.ts
export interface SkillsViewData {
  perDomain: Array<{
    domain: string;
    skills: Array<{
      skill_id: string;
      name: string;
      usage_count: number;
      last_replay_passed_at?: number;
      last_replay_failed_at?: number;
    }>;
  }>;
  refreshedAt: number;
}

export class SkillsView {
  constructor(renderer: Renderer);
  render(data: SkillsViewData, size: ScreenSize): string[];
}

Invariants:

  1. Both views are READ-ONLY. No code path inside src/dashboard/ writes to the task ledger or skill-memory store.
  2. Data is fetched on view activation and on a periodic refresh (≥1 s, ≤2 s) — never per-keystroke. The refresh uses the same ledger / store APIs as the MCP tools (no shadow read paths).
  3. When the task-ledger root or skill-memory root is missing or empty, the view renders an empty-state line ((no tasks) / (no recorded skills)) without throwing.
  4. Live progress overlay: a row whose task_id has an entry in liveProgress shows progress/total (or progress if total is undefined) appended to the status column.
  5. Hotkey t selects tasks view, k selects skills view, m returns to main (existing). Hotkey conflicts MUST be checked against existing bindings before merge.
  6. Rendering uses only ANSI-safe characters; no truecolor sequences (consistent with existing views).
  7. last_replay_failed_at > last_replay_passed_at rows render with the existing ANSI.yellow (NOT red — failure here is a soft demotion signal, not an error).
  8. Both views fall back gracefully if the dependencies (#A, #B) are not yet merged at the time this issue lands (loader silently no-ops; views render the empty-state line).

Acceptance criteria

  • src/dashboard/views/tasks-view.ts lands with the documented TasksViewData contract and renders correctly at three terminal sizes (80×24, 120×40, 200×60) — fixture-based snapshot tests under tests/dashboard/tasks-view.test.ts.
  • src/dashboard/views/skills-view.ts lands with the documented SkillsViewData contract and the same three-size snapshot test.
  • t and k hotkeys wired in src/dashboard/keyboard-handler.ts; conflict-with-existing-binding test in tests/dashboard/key-bindings.test.ts.
  • Empty-state test: when ~/.openchrome/tasks/ does not exist, tasks view renders (no tasks) and does NOT throw.
  • Empty-state test: when no skill files exist under ~/.openchrome/skill-memory/, skills view renders (no recorded skills).
  • Refresh-rate test: with one running task, the tasks view's progress column updates at least every 2 s (timing test under tests/dashboard/tasks-view-refresh.test.ts).
  • Read-only invariant test: instrumented mock of the task ledger and skill store rejects all write calls during a 30 s dashboard run.
  • No new dependencies in package.json (zero diff).
  • npm run build && npm test green.

Real verification (post-merge, via openchrome MCP)

  1. Launch openchrome with --pilot and the dashboard enabled.
  2. Kick off a long-running task: mcp__openchrome__oc_task_start with { kind: "crawl", args: { url: "https://news.ycombinator.com/", maxPages: 10, sameOrigin: true } }.
  3. Press t in the dashboard → assert the tasks view shows one row with kind=crawl, status=RUNNING, a non-empty progress column updating over time.
  4. Cancel via mcp__openchrome__oc_task_cancel → within ≤2 s the row's status flips to CANCELLED in the dashboard without manual refresh.
  5. Record a skill (per issue B (feat(pilot): oc_skill_replay — deterministic CDP-step replay with oc_assert contract gate (mcp-browser-use adoption B) #856) verification): mcp__openchrome__oc_skill_record against https://the-internet.herokuapp.com/login.
  6. Press k in the dashboard → assert the skills view shows a row for domain the-internet.herokuapp.com with the recorded skill name and usage_count >= 1.
  7. Run mcp__openchrome__oc_skill_replay against that skill → after replay, the skills view row shows the new last_replay_passed_at (or last_replay_failed_at for the tampered fixture) within ≤2 s.
  8. Resize the terminal mid-session (80→200 columns); both views re-render without artifacts (manual visual check; record a snapshot in the PR).

A reproducer script lives at scripts/verify/dashboard-tasks-skills.mjs (drives the dashboard via tmux send-keys + screencap diff).

Out of scope

  • Web dashboard (this is a terminal dashboard — explicitly noted to forestall scope drift).
  • Mutating actions in the dashboard (cancel, replay, delete). Stay read-only; mutations remain MCP-tool calls.
  • Real-time streaming via WebSockets / SSE inside the dashboard (periodic refresh is sufficient).
  • New skill-memory or task-ledger query APIs beyond what #A / #B already expose.

Dependencies

Effort

M (~3-5 dev days). Two view files (~150-250 lines each), keyboard wiring, snapshot tests, refresh-rate tests, dashboard reproducer.

References

Revision history

  • 2026-05-12 r1: Original draft.
  • 2026-05-12 r2: Critic-driven revision. Added explicit "TERMINAL dashboard, not web" guard against scope drift. Forced read-only invariant with an instrumented test. Specified soft-dep degradation behaviour so this issue can land in the same release cycle as B/D without a serialised merge order. Constrained refresh rate to a bounded interval (1-2 s) — the original draft's "live" wording was ambiguous. Added terminal-resize verification step.

Curated scope, overlap handling, and verification checklist

Scope classification

  • Canonical lane: terminal dashboard observability.
  • Primary deliverable: terminal dashboard task ledger and skill ledger views over existing persisted state.
  • Open PR: feat(dashboard): task & skill ledger views (#865) #955 (feat/865-dashboard-ledger-views). Continue there; avoid duplicate work.
  • Non-goal: web UI, mutating ledger controls, implementing task ledger/skill replay themselves, or requiring dashboard to run for core behavior.

Overlap and conflict resolution

Implementation checklist

  • Add task ledger view with status, IDs, progress, start/update times, and evidence pointers from existing ledger.
  • Add skill ledger/replay view with skill IDs, domain, promotion/replay state, and last evidence where available.
  • Handle missing source data gracefully with empty states.
  • Add renderer/state tests for task list, skill list, empty/error states, and terminal width truncation.
  • Document keyboard/navigation behavior if added.

Success criteria

  • Operators can see long-running task and skill state without grepping JSON.
  • Dashboard remains read-only for these views.
  • Missing ledger files do not crash dashboard.
  • Underlying ledger/replay implementations remain separate.

Post-merge OpenChrome live verification checklist

  • Create fixture ledger/skill state and launch dashboard or renderer test mode.
  • Verify task and skill views show IDs/status/evidence pointers.
  • Verify empty/missing state displays cleanly.
  • Capture terminal transcript/screenshot or renderer output in merge notes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions