feat(web): M19-4 grid view — issues × stages heat-map #193

Merged
code-lead merged 2 commits from dev/177 into main 2026-04-20 22:14:32 +00:00
Collaborator

Summary

  • Adds /app/monitor/grid route with a sticky-header heat-map table: rows = issues, columns = canonical pipeline stages in flow order
  • "Grid" tab added to AppShell nav (between Pipeline and Tasks)
  • Filter bar shared with the Pipeline list (M19-2) — same URL search params, so switching tabs preserves active filters
  • Sort controls: Activity (updated_at desc, default), Age (issue_number asc), Stalled (stalled-stage count desc, ties broken by activity)
  • Column headers sticky at top; Issue # + Title columns sticky at left for horizontal scroll
  • Each cell renders a compact StagePill when the stage has been reached; empty otherwise (skipped stages still render their pill)
  • Live SSE pipeline_stage updates patch the shared ["pipeline", ...] TanStack Query cache — same key as the list view
  • pipeline-grid.test.tsx: 10-issue fixture covering all stage states; asserts column alignment, default/age/stalled sort order, aria-pressed toggle state, empty/loading/error states

Test plan

  • All 61 web tests pass (bun run --cwd apps/web test --run)
  • biome check clean on all changed files
  • Navigate to /app/monitor/grid — grid renders with sticky headers
  • Set a filter on /app/monitor and navigate to /app/monitor/grid — filter preserved in URL
  • Click "Stalled" sort — issues with stalled cells rise to top
  • SSE event arrives — affected cell animates state transition without full page refresh

Closes #177

🤖 Generated with Claude Code

## Summary - Adds `/app/monitor/grid` route with a sticky-header heat-map table: rows = issues, columns = canonical pipeline stages in flow order - "Grid" tab added to `AppShell` nav (between Pipeline and Tasks) - Filter bar shared with the Pipeline list (M19-2) — same URL search params, so switching tabs preserves active filters - Sort controls: **Activity** (updated_at desc, default), **Age** (issue_number asc), **Stalled** (stalled-stage count desc, ties broken by activity) - Column headers sticky at top; Issue # + Title columns sticky at left for horizontal scroll - Each cell renders a compact `StagePill` when the stage has been reached; empty otherwise (skipped stages still render their pill) - Live SSE `pipeline_stage` updates patch the shared `["pipeline", ...]` TanStack Query cache — same key as the list view - `pipeline-grid.test.tsx`: 10-issue fixture covering all stage states; asserts column alignment, default/age/stalled sort order, aria-pressed toggle state, empty/loading/error states ## Test plan - [ ] All 61 web tests pass (`bun run --cwd apps/web test --run`) - [ ] `biome check` clean on all changed files - [ ] Navigate to `/app/monitor/grid` — grid renders with sticky headers - [ ] Set a filter on `/app/monitor` and navigate to `/app/monitor/grid` — filter preserved in URL - [ ] Click "Stalled" sort — issues with stalled cells rise to top - [ ] SSE event arrives — affected cell animates state transition without full page refresh Closes #177 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): M19-4 grid view — issues × stages heat-map at /monitor/grid
Some checks failed
qa / qa (pull_request) Failing after 2m35s
qa / dockerfile (pull_request) Successful in 8s
25881c58fb
Adds a sticky-header table where rows are issues and columns are the
canonical pipeline stages. Each cell renders a compact StagePill when
the issue has reached that stage; absent stages are empty.

- Accessible via the "Grid" nav tab added to AppShell
- Filter bar shared with the Pipeline list (same URL search params —
  filters preserved when switching between /monitor and /monitor/grid)
- Sort controls: Activity (updated_at desc, default), Age
  (issue_number asc), Stalled (stalled-count desc, ties by activity)
- Column headers + row headers (Issue # + Title) sticky on both axes
- Live SSE pipeline_stage updates patch the shared TanStack Query cache
- pipeline-grid.test.tsx: 10-issue fixture; asserts column alignment,
  default + age + stalled sort order, aria-pressed toggle state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ci): register /monitor/grid in routeTree.gen.ts so TanStack Router types resolve
All checks were successful
qa / qa (pull_request) Successful in 3m20s
qa / dockerfile (pull_request) Successful in 7s
4d706f2156
The generated route tree was never updated when monitor.grid.tsx was added,
causing TypeScript to type prev as never in the search callback and fail
the typecheck step in qa.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-20 22:09:45 +00:00
reviewer approved these changes 2026-04-20 22:13:59 +00:00
reviewer left a comment

Review — M19-4 Grid View

CI is green (run #1800, 3m27s). All acceptance criteria from issue #177 verified. Approving.


Acceptance criteria check

AC Status Notes
/app/monitor/grid route accessible Route + nav tab added
Fixed stage columns in canonical order FLOW_ORDER matches server pipeline.ts order including design track
Sort by activity (default) / age / stalled sortIssues() + sort bar with aria-pressed
Compact stage pill per cell, empty if not reached byStage Map + conditional <StagePill compact />
Column header sticky at top sticky top-0 z-20 on <thead>
Issue # + Title columns sticky at left sticky left-0 z-10 and sticky left-[80px] z-10
Click a cell → task-event / external link Delegated to StagePill (same component as mini-pipeline)
Filter bar shared with M19-2 Same FilterBar import, same URL search param schema
Live SSE pipeline_stage updates onPipelineStage patches ["pipeline"] cache in-place
pipeline-grid.test.tsx — 10-issue fixture 411 lines, all states covered
Column alignment + sort order asserted data-issue attribute + getAllByTestId ordering
Sort-by-stalled → amber cells rise to top Issue #8 (2 stalled) leads; #7/#9 (1 each) follow

Code quality notes (non-blocking)

patchPipelineStage import from a sibling route file (apps/web/src/routes/monitor.grid.tsx:6):

import { patchPipelineStage } from "@/routes/monitor.index";

Importing a utility from a route module creates route-to-route coupling — if monitor.index.tsx is restructured this breaks silently. Not a blocker here since it's a pure function and both routes genuinely share the same logic, but the long-term home for this helper is @/lib/pipeline alongside fetchPipeline. Worth extracting when M19-5/6 land and more routes need it.

left-[80px] sticky offset hard-codes the Issue column width (pipeline-grid.tsx, Title <td> and <th>). The offset is structurally coupled to min-w-[80px] on the Issue column. The current values are consistent, so it works — just something to keep in sync if the Issue column ever widens.


Both notes are informational; no correctness issues. The implementation is clean, test coverage is solid, and the SSE/query pattern is consistent with the rest of the monitor surface.

## Review — M19-4 Grid View CI is green (run #1800, 3m27s). All acceptance criteria from issue #177 verified. Approving. --- ### Acceptance criteria check | AC | Status | Notes | |---|---|---| | `/app/monitor/grid` route accessible | ✅ | Route + nav tab added | | Fixed stage columns in canonical order | ✅ | `FLOW_ORDER` matches server `pipeline.ts` order including design track | | Sort by activity (default) / age / stalled | ✅ | `sortIssues()` + sort bar with `aria-pressed` | | Compact stage pill per cell, empty if not reached | ✅ | `byStage` Map + conditional `<StagePill compact />` | | Column header sticky at top | ✅ | `sticky top-0 z-20` on `<thead>` | | Issue # + Title columns sticky at left | ✅ | `sticky left-0 z-10` and `sticky left-[80px] z-10` | | Click a cell → task-event / external link | ✅ | Delegated to `StagePill` (same component as mini-pipeline) | | Filter bar shared with M19-2 | ✅ | Same `FilterBar` import, same URL search param schema | | Live SSE `pipeline_stage` updates | ✅ | `onPipelineStage` patches `["pipeline"]` cache in-place | | `pipeline-grid.test.tsx` — 10-issue fixture | ✅ | 411 lines, all states covered | | Column alignment + sort order asserted | ✅ | `data-issue` attribute + `getAllByTestId` ordering | | Sort-by-stalled → amber cells rise to top | ✅ | Issue #8 (2 stalled) leads; #7/#9 (1 each) follow | --- ### Code quality notes (non-blocking) **`patchPipelineStage` import from a sibling route file** (`apps/web/src/routes/monitor.grid.tsx:6`): ```ts import { patchPipelineStage } from "@/routes/monitor.index"; ``` Importing a utility from a route module creates route-to-route coupling — if `monitor.index.tsx` is restructured this breaks silently. Not a blocker here since it's a pure function and both routes genuinely share the same logic, but the long-term home for this helper is `@/lib/pipeline` alongside `fetchPipeline`. Worth extracting when M19-5/6 land and more routes need it. **`left-[80px]` sticky offset hard-codes the Issue column width** (`pipeline-grid.tsx`, Title `<td>` and `<th>`). The offset is structurally coupled to `min-w-[80px]` on the Issue column. The current values are consistent, so it works — just something to keep in sync if the Issue column ever widens. --- Both notes are informational; no correctness issues. The implementation is clean, test coverage is solid, and the SSE/query pattern is consistent with the rest of the monitor surface.
code-lead deleted branch dev/177 2026-04-20 22:14:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 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!193
No description provided.