feat(pipeline): render issue dependency DAG on per-issue Monitor page #251

Merged
code-lead merged 4 commits from dev/234 into main 2026-04-21 17:14:12 +00:00
Collaborator

Summary

  • Adds GET /issues/deps?repo=…&number=N&depth=k endpoint with 30 s server-side cache; BFS in both directions (blockers + dependents) up to depth 1–5, backed by the existing getIssueBlockers / getIssueBlocks wrappers
  • New IssueDepsGraph / IssueDepsNode / IssueDepsEdge shared types in @claude-hooks/shared
  • New <DependencyGraph> component on the per-issue Monitor route — three swim-lanes (blockers → focal → dependents), click-navigates, hover tooltip
  • Compact inline ⬡ Deps expand toggle on each pipeline list row; lazy-fetches the graph only when expanded, sharing the TanStack Query cache with the detail page
  • Unit tests for buildIssueDepsGraph: A→B→C chain, isolated focal, depth clamping
  • Playwright spec: asserts graph renders with blocker + dependent nodes and click-navigates

Closes #234

Test plan

  • bun test apps/server/src/deps.test.ts — 61 tests pass (includes 3 new buildIssueDepsGraph cases)
  • GET /issues/deps?repo=charles/claude-hooks&number=234&depth=2 returns a valid JSON DAG
  • Navigate to /app/monitor/issue/charles/claude-hooks/234 — dependency panel renders below the pipeline graph
  • Click a node card — router navigates to that issue's monitor page
  • Click ⬡ Deps on a pipeline list row — inline panel expands and shows the graph
  • Playwright: bun x playwright test apps/web/e2e/monitor.spec.ts

🤖 Generated with Claude Code

## Summary - Adds `GET /issues/deps?repo=…&number=N&depth=k` endpoint with 30 s server-side cache; BFS in both directions (blockers + dependents) up to depth 1–5, backed by the existing `getIssueBlockers` / `getIssueBlocks` wrappers - New `IssueDepsGraph` / `IssueDepsNode` / `IssueDepsEdge` shared types in `@claude-hooks/shared` - New `<DependencyGraph>` component on the per-issue Monitor route — three swim-lanes (blockers → focal → dependents), click-navigates, hover tooltip - Compact inline `⬡ Deps` expand toggle on each pipeline list row; lazy-fetches the graph only when expanded, sharing the TanStack Query cache with the detail page - Unit tests for `buildIssueDepsGraph`: A→B→C chain, isolated focal, depth clamping - Playwright spec: asserts graph renders with blocker + dependent nodes and click-navigates Closes #234 ## Test plan - [ ] `bun test apps/server/src/deps.test.ts` — 61 tests pass (includes 3 new `buildIssueDepsGraph` cases) - [ ] `GET /issues/deps?repo=charles/claude-hooks&number=234&depth=2` returns a valid JSON DAG - [ ] Navigate to `/app/monitor/issue/charles/claude-hooks/234` — dependency panel renders below the pipeline graph - [ ] Click a node card — router navigates to that issue's monitor page - [ ] Click `⬡ Deps` on a pipeline list row — inline panel expands and shows the graph - [ ] Playwright: `bun x playwright test apps/web/e2e/monitor.spec.ts` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(pipeline): render issue dependency DAG on per-issue Monitor page (#234)
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
055fb2e357
Adds GET /issues/deps endpoint (30 s cache, depth 1–5) backed by the
existing getIssueBlockers / getIssueBlocks wrappers. Shared type
IssueDepsGraph propagates via @claude-hooks/shared to the web app.

Web: new <DependencyGraph> component on the per-issue monitor route
(blockers → focal → dependents swim-lane layout, click-navigates,
hover tooltip). Compact inline "⬡ Deps" expand toggle on each pipeline
list row lazy-fetches the graph only when expanded. Both surfaces share
the TanStack Query cache keyed on ["deps", repo, issueNumber].

Tests: unit tests for buildIssueDepsGraph covering the A→B→C chain,
isolated focal, and depth clamping. Playwright spec asserts the graph
renders and click-navigates on the per-issue route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev force-pushed dev/234 from 055fb2e357
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to 5027360bb4
Some checks failed
qa / qa (pull_request) Failing after 2m57s
qa / dockerfile (pull_request) Successful in 8s
2026-04-21 15:26:22 +00:00
Compare
fix(ci): resolve 7 Biome format violations in #234 feature files
Some checks failed
qa / qa (pull_request) Failing after 3m0s
qa / dockerfile (pull_request) Successful in 8s
8c0b84e2c5
- dependency-graph.tsx: collapse two single-expression <span> elements
  (blocker/dependent count badges) to single lines; expand two <path>
  SVG elements whose props exceed the 120-char line width limit
- api.ts: expand the new IssueDepsGraph import to multi-line (124 chars
  → fits in 120)
- deps.test.ts: expand two on() call-sites whose inline Response.json
  arguments exceeded the line-width budget

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ci): collapse 6 more multi-line JSX elements in dependency-graph.tsx
Some checks failed
qa / qa (pull_request) Failing after 2m54s
qa / dockerfile (pull_request) Successful in 7s
50a230de1e
Biome flags inline-safe JSX as format errors when they are spread across
multiple lines but fit on one line within the 120-char limit:

- NodeCard: {node.assignee && (...)} → inline (101 vis)
- NodeCard: <span key={l}>{l}</span> in label map → single line (117 vis)
- NodeCard: {node.labels.length > 3 && (...)} → inline (115 vis)
- DependencyGraph: <h2>Dependencies</h2> → single line (112 vis)
- DependencyGraph: {blockers.map((n) => (...))} → inline arrow (103 vis)
- DependencyGraph: {dependents.map((n) => (...))} → inline arrow (105 vis)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
charles force-pushed dev/234 from 50a230de1e
Some checks failed
qa / qa (pull_request) Failing after 2m54s
qa / dockerfile (pull_request) Successful in 7s
to 478f17b76c
Some checks failed
qa / qa (pull_request) Failing after 2m58s
qa / dockerfile (pull_request) Successful in 11s
2026-04-21 16:11:09 +00:00
Compare
dev force-pushed dev/234 from 478f17b76c
Some checks failed
qa / qa (pull_request) Failing after 2m58s
qa / dockerfile (pull_request) Successful in 11s
to 9d6ed8ac80
Some checks failed
qa / qa (pull_request) Failing after 2m57s
qa / dockerfile (pull_request) Successful in 6s
2026-04-21 16:20:03 +00:00
Compare
charles force-pushed dev/234 from 9d6ed8ac80
Some checks failed
qa / qa (pull_request) Failing after 2m57s
qa / dockerfile (pull_request) Successful in 6s
to 67fd266959
All checks were successful
qa / qa (pull_request) Successful in 3m59s
qa / dockerfile (pull_request) Successful in 7s
2026-04-21 17:06:35 +00:00
Compare
dev requested review from reviewer 2026-04-21 17:10:45 +00:00
reviewer approved these changes 2026-04-21 17:13:44 +00:00
reviewer left a comment

Review — APPROVED

CI is green (run #1924, sha 67fd266). All acceptance criteria from issue #234 are met. The implementation is solid — clean BFS traversal, correct shared types, well-tested server logic, and the UI components are properly wired up.

Two minor findings, neither blocks merge:


1. DepsBadge is exported but never used

File: apps/web/src/components/dependency-graph.tsx

DepsBadge (exported at the bottom of the file) is designed to show inline blocker/dependent counts (⬆N ⬇M) in the pipeline list row. However, pipeline-list.tsx never imports it — the row uses a plain ⬡ Deps toggle button instead. The badge is dead-exported code.

This is fine if DepsBadge is scaffolding for a future "show counts without fetching" path (it requires explicit blockerCount/dependentCount props, which IssuePipeline doesn't carry today). If so, a short comment saying it's reserved for future use would clarify intent. If it's not needed, it can be removed to keep the exported API surface clean.


2. _depsCache never evicts stale entries

File: apps/server/src/deps.ts (the _depsCache Map at module level)

The 30 s TTL is checked on read, but entries are never removed from the Map — they accumulate indefinitely. For a long-running service with many repo/issue/depth combinations this will slowly grow in memory. The existing _pipelineCache in pipeline.ts has the same pattern, so this is consistent and unlikely to be a practical problem at current ops volume. Worth noting for a future sweeper pass.


Everything else looks correct:

  • BFS explores both directions, deduplicates edges, clamps depth 1–5. ✓
  • buildIssueDepsGraph cache key correctly includes (repo, focal, depth). ✓
  • handleIssueDeps validates required params, guards against non-finite focal, surfaces 400/503/502 cleanly. ✓
  • DependencyGraph three swim-lane layout (blockers → focal → dependents) matches the spec. ✓
  • PipelineRow lazy-fetches the graph only when the expand toggle is clicked (enabled: depsExpanded). ✓
  • Unit tests cover the A→B→C chain, isolated focal, and depth clamping. ✓
  • Playwright spec asserts graph renders + click navigates to blocker's issue page. ✓
## Review — APPROVED CI is green (run #1924, sha `67fd266`). All acceptance criteria from issue #234 are met. The implementation is solid — clean BFS traversal, correct shared types, well-tested server logic, and the UI components are properly wired up. Two minor findings, neither blocks merge: --- ### 1. `DepsBadge` is exported but never used **File:** `apps/web/src/components/dependency-graph.tsx` `DepsBadge` (exported at the bottom of the file) is designed to show inline blocker/dependent counts (`⬆N ⬇M`) in the pipeline list row. However, `pipeline-list.tsx` never imports it — the row uses a plain `⬡ Deps` toggle button instead. The badge is dead-exported code. This is fine if `DepsBadge` is scaffolding for a future "show counts without fetching" path (it requires explicit `blockerCount`/`dependentCount` props, which `IssuePipeline` doesn't carry today). If so, a short comment saying it's reserved for future use would clarify intent. If it's not needed, it can be removed to keep the exported API surface clean. --- ### 2. `_depsCache` never evicts stale entries **File:** `apps/server/src/deps.ts` (the `_depsCache` Map at module level) The 30 s TTL is checked on read, but entries are never removed from the Map — they accumulate indefinitely. For a long-running service with many repo/issue/depth combinations this will slowly grow in memory. The existing `_pipelineCache` in `pipeline.ts` has the same pattern, so this is consistent and unlikely to be a practical problem at current ops volume. Worth noting for a future sweeper pass. --- Everything else looks correct: - BFS explores both directions, deduplicates edges, clamps depth 1–5. ✓ - `buildIssueDepsGraph` cache key correctly includes `(repo, focal, depth)`. ✓ - `handleIssueDeps` validates required params, guards against non-finite focal, surfaces 400/503/502 cleanly. ✓ - `DependencyGraph` three swim-lane layout (blockers → focal → dependents) matches the spec. ✓ - `PipelineRow` lazy-fetches the graph only when the expand toggle is clicked (`enabled: depsExpanded`). ✓ - Unit tests cover the A→B→C chain, isolated focal, and depth clamping. ✓ - Playwright spec asserts graph renders + click navigates to blocker's issue page. ✓
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!251
No description provided.