Skip to content

T-UUMK-port-dashboard-four-source-view

Status: closed/done · Impact: high · Complexity: large

Per D-0013-dashboard-app, v1 of the dashboard is the “active work, locally and on Git/GitHub” view over four data sources. Two already exist in the server (lease refs + task/milestone entities); two are net-new (this checkout’s local working state, and GitHub PR status). This task builds the four-source view in the Vite+Vue SPA and extends the dashboard JSON API with the two net-new sources, so an operator sees in-flight local work, leases, PRs, and tasks in one place.

LocationRole today
plugin/lib/services/dashboard/server.ts#buildStateAssembles state from two sources: lease refs (readLocalLeases over refs/sdlc/tasks/* + archive) and task/milestone entities (readTasks, readSimpleEntities). Returns project_root, generated_at, network_enabled, tasks, active_leases, archived_leases, milestones, summary. No local-working-state or PR-status source.
plugin/lib/services/dashboard/server.ts#makeAppHono app: GET / serves the INDEX_HTML string; GET /api/state returns buildState; POST /api/refresh fetches lease refs from origin, gated on networkEnabled.
plugin/lib/services/dashboard/server.ts#INDEX_HTML~180-line embedded HTML/CSS/JS string — the entire current UI, rendering leases/tasks/milestones. Retired by T-JDEV, not here.
plugin/lib/util/git.ts#listWorktreeBasenamesReturns sorted basenames of .sdlc/worktrees/<name> dirs for a checkout. Reusable local-working-state primitive.
plugin/lib/services/project/ops/_worktree-common.ts#probeDirtyRuns git status --porcelain; returns { dirty, porcelain[] }. Reusable git-status primitive.
plugin/lib/model/entities/task/ops/inflight.tssdlc task inflight op: pairs .sdlc/worktrees/<basename> with task/<basename> branches, reads each task’s status, queries gh pr list for an open PR, and categorises implementing/awaiting-review/stale/other. --no-gh skips the gh queries. This is the in-flight task-work source.
plugin/lib/model/entities/task/ops/_probe_core.tsShared probes: listLocalBranches, worktreePath, openPrForBranch (branch→open-PR number via gh pr list --search head:<branch>).
plugin/lib/services/pr/ops/classify.tssdlc pr classify <n> op: classifies a PR into MERGED/CLOSED/CONFLICTS/CI-FAILED/NEEDS-RESPONSE/CLEAN/ERROR via gh pr view --json. Reusable PR-status source.
plugin/lib/services/dashboard/dashboard-service.ts:115Parses --no-network into networkEnabled and threads it through detach/servebuildState/makeApp. The network gate this task’s PR source must honor.

buildState returns the two existing sources plus two net-new keys:

  • local_work — this machine’s active work: one entry per .sdlc/worktrees/<basename> paired with a task/<basename> branch, each carrying basename, worktree_path, branch, task_status, the in-flight category (implementing/awaiting-review/stale/other), open_pr_number, and the worktree’s git-status dirty flag + porcelain lines. The root checkout’s own git status dirty flag is included too.
  • pull_requests — GitHub PR status for the in-flight tasks’ branches: per open PR a pr_number, branch/task_id, verdict (the pr classify enum), and one-line reason. Suppressed under --no-network: when networkEnabled is false the key is present but its rows carry no network-derived data (e.g. { network_disabled: true, prs: [] }), exactly as POST /api/refresh already 409s offline.

summary gains counts for the two new sources (e.g. local_worktrees, dirty_worktrees, open_prs). The Vite+Vue SPA renders all four sources as the unified “active work” view: a task being worked locally surfaces in local_work, its lease in active_leases, its PR in pull_requests, and its entity row in tasks, cross-linked by task_id/basename.

  1. Extend buildState with local_work. Reuse listWorktreeBasenames (plugin/lib/util/git.ts) and the in-flight categoriser logic from inflight.ts / _probe_core.ts (listLocalBranches, worktreePath, classify) to build the worktree↔branch↔task roster. For each worktree run probeDirty (_worktree-common.ts) to attach the git-status dirty flag + porcelain. Do not reimplement worktree/branch/status scanning — call the existing helpers. The gh-dependent open_pr_number lookup (via openPrForBranch) runs only when networkEnabled.
  2. Extend buildState with pull_requests. For each in-flight branch with an open PR, classify it by reusing the pr classify logic (plugin/lib/services/pr/ops/classify.tsfetchLiveState + classify) to attach verdict/reason. Gate the whole source on networkEnabled: when false, emit the suppressed shape and make zero gh calls.
  3. Thread the network gate. buildState already receives networkEnabled; both net-new sources read it so --no-network fully suppresses every gh call (matching the existing POST /api/refresh 409 behavior). Add the new summary counts.
  4. Build the SPA view. In apps/dashboard/src/ (scaffolded by T-CW4K) add Vue components that render the four sources as the “active work” view, consuming GET /api/state through the generated typed client from T-JZL4 (no hand-written request plumbing). Cross-link rows by task_id/basename so one task’s local-work / lease / PR / entity rows associate.
  5. Tests. Extend plugin/lib/services/dashboard/tests/dashboard.test.ts to assert the new keys are present, that --no-network suppresses the PR source, and that a worktree+branch pair surfaces in local_work.
LocationKindChange
plugin/lib/services/dashboard/server.ts#buildStatemodifyAdd local_work + pull_requests sources and new summary counts; honor networkEnabled for both.
plugin/lib/services/dashboard/tests/dashboard.test.tsmodifyAssert the four-source /api/state shape and --no-network PR suppression.
apps/dashboard/src/components/ActiveWorkView.vuenewTop-level “active work” view composing the four source panels, fed by the typed client.
apps/dashboard/src/components/LocalWorkPanel.vuenewRenders local_work (worktrees, dirty status, in-flight category).
apps/dashboard/src/components/PullRequestsPanel.vuenewRenders pull_requests (verdict + reason), with offline/suppressed state.
apps/dashboard/src/api/state.tsnewThin call wrapper around the generated typed client’s /api/state accessor for the view to consume.
  • AC-1: GET /api/state returns all four sources — active_leases + tasks/milestones (existing) and local_work + pull_requests (net-new) — as top-level keys.
  • AC-2: When the server runs with --no-network, GET /api/state’s pull_requests source carries no network-derived PR data (suppressed shape) and no gh process is spawned for it.
  • AC-3: A task with a live .sdlc/worktrees/<basename> worktree and a task/<basename> branch appears in local_work with its category, task_status, and git-status dirty flag.
  • AC-4: With network enabled, an in-flight branch that has an open PR appears in pull_requests with a verdict from the pr classify enum and a one-line reason.
  • AC-5: The Vite+Vue SPA renders the unified “active work” view showing local worktrees, active leases, PRs, and tasks, consuming the data through the generated typed client (no hand-written fetch plumbing).
  • AC-6: dashboard.test.ts covers the four-source state shape and the --no-network PR suppression, and passes.
  • The build-into-plugin pipeline (committed drift-gated web-dist/) and retiring INDEX_HTML / repointing GET /T-JDEV.
  • Generating the typed API client itself — T-JZL4 (this task consumes it).
  • Scaffolding apps/dashboard (Vite + Vue 3) and the one-command dev startup — T-CW4K.
  • Re-keying or reshaping the existing lease / task / milestone sources beyond adding cross-links.
  • T-CW4K — must close first: provides the apps/dashboard Vite+Vue scaffold the view components live in.
  • T-JZL4 — must close first: provides the generated typed API client the SPA consumes.

Captured by /sdlc:task-work on 2026-06-28. PR: pending.

  • AC-1: auto — dashboard.test.ts “buildState four-source state” asserts active_leases, tasks, milestones, local_work, pull_requests are all top-level keys.
  • AC-2: auto — dashboard.test.ts “—no-network” asserts pull_requests is the suppressed { network_disabled: true, prs: [] } shape and the injected gh runner records zero calls.
  • AC-3: auto — dashboard.test.ts network-enabled case asserts a worktree+branch pair surfaces in local_work with category, task_status, and the git-status dirty flag/porcelain.
  • AC-4: auto — dashboard.test.ts asserts an in-flight branch with an open PR appears in pull_requests with verdict from the pr classify enum and a non-empty reason.
  • AC-5: agent-manual — bunx vue-tsc --noEmit and bunx vite build pass (the bundle includes the generated typed client); the SPA’s only data path is src/api/state.tscreateDashboardClient().getState(), and App.vue’s prior hand-written fetch was removed. Live in-browser render against a running backend is deferred-user.
  • AC-6: auto — bun test plugin/lib/services/dashboard/tests/dashboard.test.ts → 9 pass, 0 fail.
  • Reusing the existing probes (listWorktreeBasenames, inflight classify, probeDirty, openPrForBranch, fetchLiveState/classify) made both net-new sources thin assembly over proven primitives — no scanning logic re-implemented.
  • The contract.tsgen-client.ts seam kept server and typed client in lockstep: extend the zod contract, regenerate, and moon run dashboard-client:check stays green.
  • The baseline-gated quality run cleanly separated this branch’s drift from the ~538 pre-existing findings, so the only real new-drift surfaced was self-inflicted and fixable.
  • The ## Files to touch table omitted contract.ts and the client regeneration even though the T-JZL4 typed-client seam makes the contract the source of truth for any new /api/state key — a task adding API surface to a contract-backed endpoint should require Files-to-touch rows for the contract schema + the generated client. → T-1Y4A-task-specs-cite-contract-and-client
  • apps/dashboard is outside the root workspaces glob (packages/ts/* only), so the typed client had to be wired via a Vite resolve.alias + tsconfig paths + file: dep rather than workspace:* — an apps/* consumer of a packages/ts/* library hits this every time; the pattern should be documented (a standard) or the glob widened. → T-VY9S-workspaces-glob-covers-apps-consumers
  • The readiness gate’s path claim-resolver flags shorthand citations but offers no reflow, so expanding util/git.tsplugin/lib/util/git.ts pushed two Approach lines past MD013’s 100-char limit, caught only by the later quality gate — the citation-fix path should reflow. → T-WML6-citation-fix-reflows-overlength-lines
  • The project quality gate’s bun test includes a network-dependent test (task-auto-define shelling git fetch) that cannot pass in a no-network sandbox, so quality run never reports a headline OK here; baseline-gating absorbs it but the top-line status is misleading. → T-0PQK-task-auto-define-test-no-network

T-CW4K, T-JZL4


← Back to Tasks