Skip to content

feat(dashboard): Epic-centric dashboard — browse, progress, force-dispatch#152

Merged
thejustinwalsh merged 21 commits into
mainfrom
epic-centric-dashboard
May 25, 2026
Merged

feat(dashboard): Epic-centric dashboard — browse, progress, force-dispatch#152
thejustinwalsh merged 21 commits into
mainfrom
epic-centric-dashboard

Conversation

@thejustinwalsh

@thejustinwalsh thejustinwalsh commented May 25, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an Epic-centric dashboard — the interface around GitHub issue content the build spec's Dashboard section always implied but never modeled. It browses every open Epic in a repo with live sub-issue progress, shows which agent is on each, surfaces the state issue's decision callouts, and force-dispatches a free slot with a chosen agent. Epics is now the default landing view (Dashboard/Queue/Settings remain, demoted).

Why: the prior dashboard mirrored only what middle modeled — runner state + the recommender's ranked output. Nothing enumerated the Epic backlog for a human. This branch adds that model end-to-end.

What changed (by layer)

  • dispatcher — Epic cache: new epics table (migration 005_epics.sql) + epics-cache.ts (refreshEpics/readEpics). github.ts gains listOpenEpics (one paginated call; progress comes from each issue's sub_issues_summary — no per-Epic call). A 60s refresh sweep + post-dispatch refresh in main.ts; vanished Epics are marked closed, never deleted (non-flicker).
  • dispatcher — dispatch seam: DaemonHostContext gains dispatch(repo, n, adapter) and refreshEpics(repo). dispatchEpicManual reuses the same startDispatchImpl + slot/collision gates as POST /control/dispatch (byte-identical 400/429/409 parity — the dashboard can't bypass a guard).
  • dashboard — read + routes: EpicCard wire type; db-deps.listEpics joins the cache (progress) + workflows (which agent, live inFlight) + the state issue (decision callout from needsHumanInput, falling back to blocked; recommended adapter from readyToDispatch) + per-adapter hasFreeSlot. New GET /api/epics/:repo, POST /api/epics/:repo/refresh, POST /api/epics/:repo/:n/dispatch. Dispatch/refresh deps are optional → standalone (non-daemon) dashboard 404s them cleanly and still serves the cache.
  • cli: daemon-entry.ts threads dispatch + refreshEpics into createDbDeps.
  • dashboard — SPA: Epics.tsx (progress bars, agent badge → Inspector, decision callout, force-dispatch control with an adapter picker defaulting to the recommender's pick, disabled when in-flight / no free slot). Repo filter + manual refresh button in App.tsx; live refresh via the repo SSE channel.

How to run

bun install
mm start            # daemon serves the dashboard on http://127.0.0.1:4120/

Open http://127.0.0.1:4120/ — Epics is the default tab.

What to verify

  • Browse: Epics tab lists a repo's open Epics with closed/total progress bars; repo filter switches repos (shown only with >1 tracked repo).
  • Agent on it: an in-flight Epic shows an agent badge (adapter · state); clicking it opens the Inspector. The dispatch button is disabled while in-flight.
  • Force-dispatch: pick an agent (defaults to the recommender's choice), click dispatch → a workflow starts; button disables when no free slot. Server enforces 429 (no slot) / 409 (already in flight) identically to mm dispatch.
  • Decision callouts: an Epic flagged in the state issue's Needs-human or Blocked sections shows a callout.
  • Refresh: the refresh button forces a GitHub re-pull of the repo's Epics.

How to review

  • Gate parity is the load-bearing safety property: compare dispatchEpicManual (packages/dispatcher/src/main.ts) against #handleControlDispatch (packages/dispatcher/src/hook-server.ts) — both funnel through startDispatchImpl.
  • The cache↔view freshness: inFlight is computed live from workflows on every listEpics, so it doesn't depend on cache staleness.
  • Standalone vs daemon: listEpics always works (reads cache); dispatchEpic/refreshEpics are optional seams.

Fragile / extra eyes

  • parseEpicsList relies on GitHub's sub_issues_summary on the issues API.
  • epics.gh_updated_at is reserved/unpopulated (noted inline).
  • Refresh interval is a constant EPICS_REFRESH_INTERVAL_MS = 60_000 (config-ification deferred).

Known-minor (deferred, non-blocking)

  • The adapter picker lists banner adapters (e.g. codex) even when only claude is dispatchable today; selecting a non-dispatchable adapter disables the button (safe), but the option is shown.
  • If a selected repo leaves the tracked set, epicRepo holds a stale slug and the view shows the empty state (no crash); no auto-reselect.

Follow-up (separate spec, agreed)

A sibling Runs/Activity view for recommender + documentation (docs-auditor/fixup) workflow visibility — currently every dashboard view filters to kind = 'implementation'. To be brainstormed/spec'd next.

Test plan

  • bun test — 700 pass, 0 fail
  • bun run typecheck — clean
  • bun run lint — clean
  • Rebased onto main (post recommendation PR); cleanly mergeable
  • Manual smoke per "What to verify" above

Spec: docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md · Plan: docs/superpowers/plans/2026-05-25-epic-centric-dashboard.md

Summary by CodeRabbit

  • New Features
    • Epic-centric dashboard now serves as the default view with live browse of open epics.
    • View epic progress, in-flight runners, and decision information per repo.
    • Manual epic dispatch with adapter selection and free-slot availability checking.
    • Auto-refresh of epic cache on interval and manual trigger support.

Review Change Stack

Adds the `epics` SQLite table (migration 005) and `epics-cache.ts` with
`refreshEpics`/`readEpics`. Epics removed from the open set are marked
closed rather than deleted to prevent mid-refresh flicker. Updates
db.test.ts version expectations from 4→5 and includes `epics` in the
expected-tables list.
listEpics now falls back to a blocked decision callout when an Epic has
a Blocked entry but no Needs-human-input entry in the state issue.
Adds a refresh button next to the repo selector in the Epics view that
calls api.refreshEpics then re-fetches the list via the guard funnel.
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements a complete Epic-centric dashboard feature spanning dispatcher and dashboard packages, enabling operators to browse GitHub Epics with live progress tracking, in-flight runner info, and manual force-dispatch capability. It includes SQLite caching, daemon refresh scheduling, API routes, React UI, and comprehensive tests and documentation.

Changes

Epic-centric Dashboard Feature

Layer / File(s) Summary
Epic cache: database migration, cache operations, and GitHub listing
packages/dispatcher/src/db/migrations/005_epics.sql, packages/dispatcher/src/epics-cache.ts, packages/dispatcher/src/github.ts, packages/dispatcher/CLAUDE.md, packages/dispatcher/test/epics-cache.test.ts, packages/dispatcher/test/github-epics.test.ts, packages/dispatcher/test/db.test.ts
Migration 005 adds epics table with repo+number key and progress metadata; epics-cache.ts implements refreshEpics (upserts open, marks vanished as closed) and readEpics (returns open rows only); github.ts adds listOpenEpics and parseEpicsList to fetch GitHub issues with sub-issue counts via NDJSON parsing. Tests verify cache upsert, closure/reopening, repo scoping, and NDJSON parsing.
Daemon Epic dispatch handler, refresh scheduler, and context wiring
packages/dispatcher/src/main.ts, packages/cli/src/daemon-entry.ts, packages/cli/test/daemon-entry.test.ts, packages/dispatcher/test/host-context.test.ts
DaemonHostContext gains dispatch(repo, epicNumber, adapter) and refreshEpics(repo) callbacks; manual dispatch handler validates inputs, checks slot availability, enqueues dispatch, and triggers cache refresh; refresh scheduler performs initial sweep and starts 60-second periodic refresh loop; daemon-entry wires callbacks via hostExtras; tests verify single-port routing and dispatch callback invocation.
Dashboard Epic dependencies, wire contract, and DB listing implementation
packages/dashboard/src/deps.ts, packages/dashboard/src/wire.ts, packages/dashboard/src/db-deps.ts, packages/dashboard/src/index.ts, packages/dashboard/test/epics-deps.test.ts
DashboardDeps exports listEpics, dispatchEpic?, refreshEpics?; EpicCard wire type combines repo/epic identity, progress, optional runner and decision callout, and dispatch state with per-adapter slot availability; listEpics(repo) joins epic cache with workflow rows, parses state-issue decision (needs-human / blocked / ready), and computes free-slot availability; tests cover decision parsing, in-flight detection, and callback delegation.
Dashboard Epic API routes and request handlers
packages/dashboard/src/api.ts, packages/dashboard/test/epics-api.test.ts
Adds handleEpics router for GET /api/epics/:repo, POST /api/epics/:repo/refresh, and POST /api/epics/:repo/:n/dispatch; validates :repo, :n (positive integer), and request adapter (non-empty string); returns 400/404 JSON errors; gates refresh/dispatch on optional deps; forwards successful responses with status and JSON content-type.
Dashboard React Epics component with progress and dispatch controls
packages/dashboard/src/app/components/Epics.tsx, packages/dashboard/test/epics.test.tsx
Epics component renders epic list or empty state; each card shows title/number, runner agent (or idle), ProgressBar (closed/total %), decision callout with optional link, and DispatchControl; DispatchControl manages adapter selection state, checks slot availability, disables dispatch when in-flight or no free slot, and calls onDispatch; tests verify rendering, dispatch button states, and decision display.
App.tsx Epic view state, polling, dispatch flow, and API client methods
packages/dashboard/src/app/App.tsx, packages/dashboard/src/app/api-client.ts, packages/dashboard/test/app.test.tsx
App defaults to Epics view; state tracks epics list and epicRepo selection (defaults to first repo); polling effect refreshes epics while Epics view is active; event subscriber listens to /events/repos/<epicRepo> for workflow updates; UI adds Epics button and toolbar with repo filter (multi-repo) and manual refresh; api-client adds typed epics(), refreshEpics(), and dispatchEpic() methods; tests verify view default and Epic listing via live server.
Implementation plan, design spec, and documentation governance skills
docs/superpowers/plans/2026-05-25-epic-centric-dashboard.md, docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md, .claude/skills/documenting-the-repo/SKILL.md, .claude/skills/recommending-github-issues/SKILL.md, .codex/skills/documenting-the-repo/SKILL.md, .codex/skills/recommending-github-issues/SKILL.md
Plan doc covers end-to-end implementation layers, contracts, and test steps; design spec details cache schema, GitHub methods, API joins, SPA UI, dispatch wiring, and testing plan. Documentation skills define Diátaxis mode enforcement, voice/style rules, LLM-ism blocklist, and accuracy policies; issue-recommendation skills clarify Epic/PR precedence and in-flight status detection.
Cosmetic import and style formatting updates
packages/dashboard/src/app/styles.css, packages/dashboard/src/bridge.ts, packages/dashboard/test/sse.test.ts, packages/dashboard/src/app/components/Queue.tsx, packages/dashboard/test/queue.test.tsx, packages/dashboard/test/control-client.test.ts
CSS Queue state/chip selectors reformatted to multi-line; import statements collapsed to single lines; Queue empty-state JSX refactored to multi-line block; control-client fetch mock converted to multi-line IIFE.

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant Dashboard as Dashboard UI
  participant API as Dashboard API
  participant DB as SQLite DB
  participant Daemon as Dispatcher Daemon
  participant GitHub as GitHub API

  Note over Daemon,GitHub: Background refresh every 60s
  Daemon->>GitHub: listOpenEpics(repo)
  GitHub-->>Daemon: EpicListItem[]
  Daemon->>DB: refreshEpics (upsert open, mark closed)
  DB-->>Daemon: void

  Operator->>Dashboard: View Epics tab
  Dashboard->>API: GET /api/epics/:repo
  API->>DB: listEpics(repo)
  DB->>API: EpicCard[]
  API-->>Dashboard: EpicCard[]
  Dashboard->>Operator: Render epic cards (progress, runner, decision)

  Operator->>Dashboard: Click dispatch button (epic, adapter)
  Dashboard->>API: POST /api/epics/:repo/:n/dispatch
  API->>Daemon: dispatch(repo, n, adapter)
  Daemon->>DB: Validate slots
  Daemon->>DB: Enqueue workflow
  Daemon->>DB: Schedule auto-dispatch
  Daemon->>GitHub: Trigger cache refresh
  API-->>Dashboard: Workflow result
  Dashboard->>Dashboard: Refresh epics
  Dashboard->>Operator: Show updated dispatch state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • thejustinwalsh/middle#149: Established the daemon-hosted dashboard wiring via hostExtras and DaemonHostContext seam; this PR extends that same path to add Epic-specific dispatch and refreshEpics callbacks.
  • thejustinwalsh/middle#132: Epic dispatch endpoints forward through the existing /control/dispatch path that includes slot-limit validation and auto-dispatch logic introduced in Phase 8.
  • thejustinwalsh/middle#138: Dashboard foundation (API routes, deps injection, SSE/app seams) that this PR extends with new /api/epics/* routes and Epic-specific handlers.

Suggested labels

ready-for-review, docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an Epic-centric dashboard with browse, progress tracking, and force-dispatch capabilities as the default landing view.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
packages/cli/test/daemon-entry.test.ts (1)

69-100: ⚡ Quick win

Add a symmetric route-level passthrough test for refresh wiring.

This file verifies dispatch callback passthrough well; adding the same check for POST /api/epics/:repo/refresh would close the regression gap for the second newly-threaded callback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/test/daemon-entry.test.ts` around lines 69 - 100, Add a
symmetric test to verify the /api/epics/:repo/refresh POST routes through the
host-context refresh callback: create a test similar to "a dispatch POST reaches
the host-context dispatch callback" that sets up a DaemonHostContext with a spy
on refreshEpics (e.g., set a variable when refreshEpics is called), start the
HookServer with dashboardHostExtras(ctx), POST to
`${base}/api/epics/${encodeURIComponent("o/r")}/refresh` with appropriate
headers/body, assert a 200 response and expected body, and assert the spy
captured the repo argument (e.g., ["o/r"]) to ensure refreshEpics was invoked;
reuse the existing db, dispose, server setup/teardown patterns from the dispatch
test and reference refreshEpics, dashboardHostExtras, HookServer, and the route
path to locate where to add the test.
packages/dispatcher/src/db/migrations/005_epics.sql (1)

10-13: ⚡ Quick win

Add DB constraints for Epic state/progress invariants.

state, sub_total, and sub_closed are unconstrained right now. Adding CHECK constraints here prevents invalid cache rows from silently entering the table.

Suggested migration tweak
 CREATE TABLE epics (
   repo           TEXT    NOT NULL,
   number         INTEGER NOT NULL,
   title          TEXT    NOT NULL,
-  state          TEXT    NOT NULL,             -- 'open' | 'closed'
+  state          TEXT    NOT NULL CHECK (state IN ('open', 'closed')),
   labels_json    TEXT    NOT NULL DEFAULT '[]',
-  sub_total      INTEGER NOT NULL DEFAULT 0,
-  sub_closed     INTEGER NOT NULL DEFAULT 0,
+  sub_total      INTEGER NOT NULL DEFAULT 0 CHECK (sub_total >= 0),
+  sub_closed     INTEGER NOT NULL DEFAULT 0 CHECK (sub_closed >= 0 AND sub_closed <= sub_total),
   gh_updated_at  TEXT,                            -- reserved; EpicListItem carries no updated_at yet, so refreshEpics does not populate this
   last_refreshed INTEGER NOT NULL,             -- epoch ms of our last write
   PRIMARY KEY (repo, number)
 );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dispatcher/src/db/migrations/005_epics.sql` around lines 10 - 13,
Add CHECK constraints to the epics table to enforce valid values and invariants:
constrain state to only 'open' or 'closed' (e.g., CHECK (state IN
('open','closed'))), ensure sub_total is non-negative, ensure sub_closed is
between 0 and sub_total (e.g., CHECK (sub_total >= 0) and CHECK (sub_closed >= 0
AND sub_closed <= sub_total)). Name the constraints clearly (e.g.,
chk_epics_state, chk_epics_sub_total_nonneg, chk_epics_sub_closed_range) and add
them alongside the column definitions for state, sub_total, and sub_closed in
the 005_epics.sql migration so invalid rows cannot be inserted.
packages/dashboard/src/app/components/Epics.tsx (1)

68-78: ⚡ Quick win

Add a TSDoc/JSDoc block directly on the exported Epics component.

The module has a file header, but this exported symbol still needs its own API contract doc per repo rules.

📝 Suggested fix
+/**
+ * Renders Epic cards for the selected repository, including runner status,
+ * decision callouts, and force-dispatch controls.
+ * Assumes `epics` all belong to the currently selected repo in `App`.
+ */
 export function Epics({

As per coding guidelines: “Every public export in a module must carry a TSDoc/JSDoc comment. Comments must describe behavior and contracts.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dashboard/src/app/components/Epics.tsx` around lines 68 - 78, Add a
TSDoc/JSDoc comment immediately above the exported Epics function that documents
the component's purpose and the props contract: describe the Epics component
behavior and list each prop (epics: EpicCard[] - what each EpicCard represents,
adapters: string[] - expected adapter identifiers, onDispatch(repo: string,
epicNumber: number, adapter: string) => void - when and how it is called, and
optional onOpenInspector(session?: string) => void) including nullable/optional
behavior and any side effects; ensure the comment is in standard TSDoc/JSDoc
format and sits directly above the `export function Epics(...)` declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.gitignore:
- Line 13: The .middle/ ignore pattern prevents Git from honoring the allowlist
entry !.middle/verify.toml because ignoring a directory hides its contents;
update the .gitignore so the directory itself is not globally ignored (for
example replace the broad ".middle/" entry with a content-only pattern like
".middle/*" and/or add an explicit whitelist entry "!.middle/" before the file
allowlist) so that the existing allowlist rule !.middle/verify.toml takes
effect; references: ".middle/" and "!.middle/verify.toml".

In `@docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md`:
- Line 137: Update the docs so the endpoint contract matches the implemented,
path-based route: replace references to `GET /api/epics?repo=<slug>` with `GET
/api/epics/:repo` (and update any example URLs, parameters, and usage notes
accordingly), ensure the response type `EpicCard[]` and description of what each
card contains remains intact, and remove or convert any guidance that references
the query parameter `repo=` to the path parameter `:repo` so clients use the
shipped route shape.
- Line 86: Update the stale migration filename reference in the spec: replace
the string "packages/dispatcher/src/db/migrations/004_epics.sql" with the
correct migration name "packages/dispatcher/src/db/migrations/005_epics.sql"
wherever it appears in this spec document so readers are pointed to the
implemented migration; ensure the as-built section and any other occurrences now
match "005_epics.sql".

In `@packages/dashboard/src/api.ts`:
- Around line 155-160: The handler currently validates body.adapter with trim()
but passes the original value downstream; normalize the adapter by assigning a
trimmed string (e.g., const adapter = body.adapter.trim()) after the validation
and use that normalized variable when calling deps.dispatchEpic(repo,
epicNumber, adapter) and anywhere else the adapter value is forwarded (refer to
body.adapter and deps.dispatchEpic in this block) so that inputs like " claude "
are dispatched as "claude".

In `@packages/dashboard/src/app/App.tsx`:
- Around line 233-236: refreshEpics (and the other similar refresh functions)
can set stale epics because they unconditionally call setEpics with whatever API
response arrives; modify refreshEpics so it captures the repo arg, performs the
async api.epics(repo) call inside guard, then before calling setEpics verify
that the repo argument still matches the currently selected repo (e.g. compare
to your selected repo state or a currentRepoRef) and only call setEpics when
they match; if you use a ref (currentRepoRef) update it on repo change and
compare to avoid race conditions when multiple fetches overlap.

In `@packages/dashboard/src/app/components/Epics.tsx`:
- Around line 86-87: The Epic card key currently uses only card.number which can
collide across repositories and preserve stale local state (e.g., in
DispatchControl); update the key in Epics.tsx where epics.map is rendering the
<li> to include a repo-scoped identifier (for example combine the repo
prop/identifier with card.number, or use a unique card.id if available) so each
key is globally unique per-repo and per-issue; ensure the same repo-scoped value
is used for data-epic if that data is relied on elsewhere (references:
Epics.tsx, epics.map, card.number, DispatchControl).

In `@packages/dashboard/src/db-deps.ts`:
- Around line 315-321: The code sets decision.link by passing need.link through
extractUrl without checking the URL scheme; update extractUrl (or add a wrapper)
to parse the input (e.g., using URL) and only return the href when the protocol
is "http:" or "https:", otherwise return undefined/null so the spread
...(need.link ? { link: extractUrl(need.link) } : {}) yields no href; apply the
same validation to the other call sites that populate EpicCard["decision"].link
from need.link so invalid schemes are omitted before rendering.

In `@packages/dashboard/test/app.test.tsx`:
- Around line 49-51: The test is using `http://localhost:${server.port}` which
can be flaky because the server binds to 127.0.0.1; update the fetch URL in
packages/dashboard/test/app.test.tsx (the call that constructs
`http://localhost:${server.port}/api/epics/${encodeURIComponent("o/r")}`) to use
`http://127.0.0.1:${server.port}` so the test consistently targets the IPv4
loopback the server is bound to.

In `@packages/dispatcher/src/epics-cache.ts`:
- Line 72: The current per-row mapping in readEpics uses
JSON.parse(r.labelsJson) as string[] which will throw on malformed labels_json
and abort the entire read; change the per-row parsing to a guarded parse inside
readEpics (or the mapping where r is used) by wrapping JSON.parse(r.labelsJson)
in a try/catch (or a small safeParseLabels helper) and return [] on error,
optionally logging a warning including the offending r.id or r.labelsJson;
ensure the resulting property remains typed as string[] (e.g., labels =
safeParseLabels(r.labelsJson) as string[]).

In `@packages/dispatcher/src/main.ts`:
- Line 373: The current call to refreshEpics(db, normalizedRepo,
ghGitHub).catch(() => {}) swallows all errors; change the empty catch to log the
error and context instead so failures are visible but still treated as
best-effort. Replace the catch with something like .catch(err =>
processLogger.error({ err, repo: normalizedRepo }, "refreshEpics failed after
dispatch")) (or console.error if processLogger is not available) so the error
and identifying context from normalizedRepo and the refreshEpics call are
recorded without breaking post-dispatch flow.

---

Nitpick comments:
In `@packages/cli/test/daemon-entry.test.ts`:
- Around line 69-100: Add a symmetric test to verify the
/api/epics/:repo/refresh POST routes through the host-context refresh callback:
create a test similar to "a dispatch POST reaches the host-context dispatch
callback" that sets up a DaemonHostContext with a spy on refreshEpics (e.g., set
a variable when refreshEpics is called), start the HookServer with
dashboardHostExtras(ctx), POST to
`${base}/api/epics/${encodeURIComponent("o/r")}/refresh` with appropriate
headers/body, assert a 200 response and expected body, and assert the spy
captured the repo argument (e.g., ["o/r"]) to ensure refreshEpics was invoked;
reuse the existing db, dispose, server setup/teardown patterns from the dispatch
test and reference refreshEpics, dashboardHostExtras, HookServer, and the route
path to locate where to add the test.

In `@packages/dashboard/src/app/components/Epics.tsx`:
- Around line 68-78: Add a TSDoc/JSDoc comment immediately above the exported
Epics function that documents the component's purpose and the props contract:
describe the Epics component behavior and list each prop (epics: EpicCard[] -
what each EpicCard represents, adapters: string[] - expected adapter
identifiers, onDispatch(repo: string, epicNumber: number, adapter: string) =>
void - when and how it is called, and optional onOpenInspector(session?: string)
=> void) including nullable/optional behavior and any side effects; ensure the
comment is in standard TSDoc/JSDoc format and sits directly above the `export
function Epics(...)` declaration.

In `@packages/dispatcher/src/db/migrations/005_epics.sql`:
- Around line 10-13: Add CHECK constraints to the epics table to enforce valid
values and invariants: constrain state to only 'open' or 'closed' (e.g., CHECK
(state IN ('open','closed'))), ensure sub_total is non-negative, ensure
sub_closed is between 0 and sub_total (e.g., CHECK (sub_total >= 0) and CHECK
(sub_closed >= 0 AND sub_closed <= sub_total)). Name the constraints clearly
(e.g., chk_epics_state, chk_epics_sub_total_nonneg, chk_epics_sub_closed_range)
and add them alongside the column definitions for state, sub_total, and
sub_closed in the 005_epics.sql migration so invalid rows cannot be inserted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bdc91156-30b0-4efd-a14c-3834c1a40c3e

📥 Commits

Reviewing files that changed from the base of the PR and between 27659b3 and 42981ac.

📒 Files selected for processing (37)
  • .claude/skills/documenting-the-repo/SKILL.md
  • .claude/skills/recommending-github-issues/SKILL.md
  • .codex/skills/documenting-the-repo/SKILL.md
  • .codex/skills/recommending-github-issues/SKILL.md
  • .gitignore
  • docs/superpowers/plans/2026-05-25-epic-centric-dashboard.md
  • docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md
  • packages/cli/src/daemon-entry.ts
  • packages/cli/test/daemon-entry.test.ts
  • packages/dashboard/src/api.ts
  • packages/dashboard/src/app/App.tsx
  • packages/dashboard/src/app/api-client.ts
  • packages/dashboard/src/app/components/Epics.tsx
  • packages/dashboard/src/app/components/Queue.tsx
  • packages/dashboard/src/app/styles.css
  • packages/dashboard/src/bridge.ts
  • packages/dashboard/src/db-deps.ts
  • packages/dashboard/src/deps.ts
  • packages/dashboard/src/index.ts
  • packages/dashboard/src/wire.ts
  • packages/dashboard/test/app.test.tsx
  • packages/dashboard/test/control-client.test.ts
  • packages/dashboard/test/epics-api.test.ts
  • packages/dashboard/test/epics-deps.test.ts
  • packages/dashboard/test/epics.test.tsx
  • packages/dashboard/test/queue.test.tsx
  • packages/dashboard/test/sse.test.ts
  • packages/dispatcher/CLAUDE.md
  • packages/dispatcher/src/db/migrations/005_epics.sql
  • packages/dispatcher/src/epics-cache.ts
  • packages/dispatcher/src/github.ts
  • packages/dispatcher/src/index.ts
  • packages/dispatcher/src/main.ts
  • packages/dispatcher/test/db.test.ts
  • packages/dispatcher/test/epics-cache.test.ts
  • packages/dispatcher/test/github-epics.test.ts
  • packages/dispatcher/test/host-context.test.ts

Comment thread .gitignore Outdated
Comment thread docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md Outdated
Comment thread docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md Outdated
Comment thread packages/dashboard/src/api.ts Outdated
Comment thread packages/dashboard/src/app/App.tsx
Comment thread packages/dashboard/src/app/components/Epics.tsx Outdated
Comment thread packages/dashboard/src/db-deps.ts
Comment thread packages/dashboard/test/app.test.tsx
Comment thread packages/dispatcher/src/epics-cache.ts Outdated
Comment thread packages/dispatcher/src/main.ts Outdated
…oped fetch/key, trim adapter, log refresh; docs+test fixes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md (3)

113-113: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align refresh trigger description with as-built constant.

Line 113 describes "config epics.refresh_interval_seconds (default 60)", but the as-built deltas (lines 16-17) document that the refresh cadence is a constant EPICS_REFRESH_INTERVAL_MS = 60_000 in main.ts, not a config setting (config-ification was deferred). For an "implemented" spec, this should reflect what shipped.

Suggested fix
-- **Interval** — config `epics.refresh_interval_seconds` (default **60**).
+- **Interval** — constant `EPICS_REFRESH_INTERVAL_MS = 60_000` in `main.ts`.

Based on learnings: the as-built deltas section states "Refresh cadence is a constant EPICS_REFRESH_INTERVAL_MS = 60_000 in main.ts (config-ification deferred), matching the existing POLLER/WATCHDOG interval style."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md` at line
113, Update the spec text to match the implemented behavior: replace the
config-based description for the refresh interval with the actual constant used
in code by referencing EPICS_REFRESH_INTERVAL_MS in main.ts (current value
60_000 ms) and note that config-ification was deferred; remove mention of config
key `epics.refresh_interval_seconds` and instead state the refresh cadence is
driven by the EPICS_REFRESH_INTERVAL_MS constant consistent with POLLER/WATCHDOG
style.

105-108: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update spec body to reflect as-built N=1 GitHub call optimization.

Lines 105-108 still describe the original 1+N design ("fetches each one's sub-issue progress" and "one listOpenEpics + N subIssueProgress calls"), but the as-built deltas (lines 8-10) document that this was eliminated: listOpenEpics reads each issue's sub_issues_summary directly, making per-Epic subIssueProgress calls unnecessary. For a spec marked "implemented", the body should match what shipped.

Suggested fix to align body with as-built reality
-  fetches each one's sub-issue progress, and **upserts** rows; Epics that vanish
+  and **upserts** rows with progress from each issue's `sub_issues_summary`; Epics that vanish
   from the open set are marked `closed` (not deleted, so a just-closed Epic doesn't
-  flicker out mid-view). One pass = one `listOpenEpics` + N `subIssueProgress` calls.
+  flicker out mid-view). One pass = one `listOpenEpics` call (progress comes inline).

Based on learnings: the as-built deltas section explicitly states "One GitHub call, not 1+N" and "listOpenEpics reads each issue's sub_issues_summary from the single paginated issues call, so per-Epic subIssueProgress calls were dropped."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md` around
lines 105 - 108, The spec body incorrectly describes the implemented design for
refreshEpics(db, repo, github) as a 1+N call pattern; update the prose to match
the as-built behavior: state that listOpenEpics performs a single paginated
GitHub issues API call which reads each issue's sub_issues_summary so per-Epic
subIssueProgress calls were removed (N=1 optimization), and clarify that
refreshEpics now upserts based on that single call and marks vanished Epics
closed.

128-130: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify GitHub methods after per-Epic progress call elimination.

Lines 128-130 list two methods including subIssueProgress(repo, n), but the as-built deltas (lines 8-10) state that "per-Epic subIssueProgress calls were dropped" because progress comes inline from sub_issues_summary. If subIssueProgress was never implemented, remove it from this section; if it exists but is unused, note that it's not called during refresh.

Suggested fix if the method was eliminated
-Two methods on the existing `GithubClient`, both via `gh api`, mirroring the
-recommender's `sub_issues` technique:
+New method on the existing `GithubClient` via `gh api`, mirroring the
+recommender's `sub_issues` technique:
 
-- `listOpenEpics(repo): Promise<{ number; title; state; labels: string[] }[]>` —
-  open issues that have ≥1 sub-issue.
-- `subIssueProgress(repo, n): Promise<{ total: number; closed: number }>`.
+- `listOpenEpics(repo): Promise<{ number; title; state; labels: string[]; subTotal: number; subClosed: number }[]>` —
+  open issues that have ≥1 sub-issue, with inline progress from `sub_issues_summary`.

Based on learnings: the as-built deltas section documents "One GitHub call, not 1+N" and "per-Epic subIssueProgress calls were dropped — progress comes free with the list."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md` around
lines 128 - 130, The docs currently list a GitHub helper `subIssueProgress(repo,
n)` which contradicts the as-built change that per-Epic progress is provided
inline via `sub_issues_summary` and "One GitHub call, not 1+N"; inspect the
codebase for the symbol `subIssueProgress` and either remove it from this
methods list if it was never implemented, or explicitly mark it as
deprecated/unused (e.g., "exists but not called during refresh") if it remains
in code; also update the methods bullet to reflect that `listOpenEpics(repo):
Promise<...>` now returns per-epic progress via the `sub_issues_summary` field
so that readers see progress is included without extra per-epic calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md`:
- Line 113: Update the spec text to match the implemented behavior: replace the
config-based description for the refresh interval with the actual constant used
in code by referencing EPICS_REFRESH_INTERVAL_MS in main.ts (current value
60_000 ms) and note that config-ification was deferred; remove mention of config
key `epics.refresh_interval_seconds` and instead state the refresh cadence is
driven by the EPICS_REFRESH_INTERVAL_MS constant consistent with POLLER/WATCHDOG
style.
- Around line 105-108: The spec body incorrectly describes the implemented
design for refreshEpics(db, repo, github) as a 1+N call pattern; update the
prose to match the as-built behavior: state that listOpenEpics performs a single
paginated GitHub issues API call which reads each issue's sub_issues_summary so
per-Epic subIssueProgress calls were removed (N=1 optimization), and clarify
that refreshEpics now upserts based on that single call and marks vanished Epics
closed.
- Around line 128-130: The docs currently list a GitHub helper
`subIssueProgress(repo, n)` which contradicts the as-built change that per-Epic
progress is provided inline via `sub_issues_summary` and "One GitHub call, not
1+N"; inspect the codebase for the symbol `subIssueProgress` and either remove
it from this methods list if it was never implemented, or explicitly mark it as
deprecated/unused (e.g., "exists but not called during refresh") if it remains
in code; also update the methods bullet to reflect that `listOpenEpics(repo):
Promise<...>` now returns per-epic progress via the `sub_issues_summary` field
so that readers see progress is included without extra per-epic calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c7e09ad-9134-4da9-8be8-88d433b4340b

📥 Commits

Reviewing files that changed from the base of the PR and between 42981ac and 7722e35.

📒 Files selected for processing (9)
  • docs/superpowers/specs/2026-05-25-epic-centric-dashboard-design.md
  • packages/dashboard/src/api.ts
  • packages/dashboard/src/app/App.tsx
  • packages/dashboard/src/app/components/Epics.tsx
  • packages/dashboard/src/db-deps.ts
  • packages/dashboard/test/app.test.tsx
  • packages/dashboard/test/epics-deps.test.ts
  • packages/dispatcher/src/epics-cache.ts
  • packages/dispatcher/src/main.ts

@thejustinwalsh thejustinwalsh merged commit bc7b447 into main May 25, 2026
1 check passed
@thejustinwalsh thejustinwalsh deleted the epic-centric-dashboard branch May 25, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant