feat(web): merge Grid route into Pipeline as view toggle (UC-2) #263

Closed
opened 2026-04-21 20:31:18 +00:00 by code-lead · 0 comments
Collaborator

As an operator, I want the Pipeline grid to be a view mode of the Pipeline page rather than its own nav entry, so that I can flip between list and grid without losing my filter state and without an extra tab in the nav.

Acceptance criteria

Route consolidation

  • /monitor gains a view?: "list" | "grid" search param (default list). validateSearch accepts either value and drops anything else.
  • Route component renders <PipelineList> when view === "list" and <PipelineGrid> when view === "grid". Both components already accept the same PipelineFilters + IssuePipelineResponse props; no prop-reshaping required.
  • /monitor/grid route file is deleted. The file-based router resolves its old path via a redirect to /monitor?view=grid&<other-params> preserving every existing search param (repo, milestone, assignee, label, state, risk).
  • The duplicated PipelineSearch validator in monitor.grid.tsx is removed and the single remaining validator in monitor.index.tsx is the sole source of truth.

Toggle UI

  • Two small segmented-control buttons live inside the shared FilterBar (exported from pipeline-list.tsx) — one List, one Grid. Selected state uses bg-accent text-accent-foreground per token rules; non-selected uses bg-surface text-muted.
  • Toggling a view updates the URL (replace: true) so the back button doesn't stack a history entry per toggle.
  • The toggle is keyboard-accessible (Tab focus, Enter/Space to activate) and carries a role="tablist" with aria-selected on the active item.

SSE + cache

  • ["pipeline", ...] query-key invalidation stays the way it is — both views already write through the same key, so no dedup logic is needed.
  • pipeline_stage SSE patching logic is lifted into a shared hook (e.g. usePipelineSSE(queryClient)) so the single route doesn't carry two copies of the onPipelineStage callback.

Nav

  • components/app-shell.tsx drops the Grid entry from both top-nav and bottom-tab arrays. Nav-label tests updated.

Tests

  • Vitest: /monitor?view=grid renders PipelineGrid; /monitor (no view) renders PipelineList; malformed view=xyz falls back to list.
  • Vitest: toggling the control updates the URL search param and swaps the rendered component.
  • Playwright: existing /app/monitor/grid?repo=…&risk=1 URL lands on /app/monitor?view=grid&repo=…&risk=1 with filters preserved and the grid rendered.
  • Existing pipeline-list.test.tsx + pipeline-grid.test.tsx stay green — component APIs are unchanged.

Out of scope

  • Any change to PipelineGrid or PipelineList rendering — the merge is routing-only.
  • Changes to /issues/pipeline server contract or SSE envelopes.
  • Per-view filter state (e.g. grid-only sort) — sort stays local component state.

References

  • Spec: specs/ui-consolidation.md § UC-2 (Merge Grid into Pipeline as a view toggle)
  • Tracking: #259
  • apps/web/src/routes/monitor.grid.tsx (deleted)
  • apps/web/src/routes/monitor.index.tsx (extended)
  • apps/web/src/components/pipeline-list.tsx (FilterBar extended)
  • apps/web/src/components/app-shell.tsx (nav prune)

Dependencies

  • Blocked by: #262 (UC-1) — reuses the /app/specs/app/planner?spec=… redirect helper pattern. Otherwise independent.
As an operator, I want the Pipeline grid to be a view mode of the Pipeline page rather than its own nav entry, so that I can flip between list and grid without losing my filter state and without an extra tab in the nav. ## Acceptance criteria ### Route consolidation - [ ] `/monitor` gains a `view?: "list" | "grid"` search param (default `list`). `validateSearch` accepts either value and drops anything else. - [ ] Route component renders `<PipelineList>` when `view === "list"` and `<PipelineGrid>` when `view === "grid"`. Both components already accept the same `PipelineFilters` + `IssuePipelineResponse` props; no prop-reshaping required. - [ ] `/monitor/grid` route file is deleted. The file-based router resolves its old path via a redirect to `/monitor?view=grid&<other-params>` preserving every existing search param (`repo`, `milestone`, `assignee`, `label`, `state`, `risk`). - [ ] The duplicated `PipelineSearch` validator in `monitor.grid.tsx` is removed and the single remaining validator in `monitor.index.tsx` is the sole source of truth. ### Toggle UI - [ ] Two small segmented-control buttons live inside the shared `FilterBar` (exported from `pipeline-list.tsx`) — one **List**, one **Grid**. Selected state uses `bg-accent text-accent-foreground` per token rules; non-selected uses `bg-surface text-muted`. - [ ] Toggling a view updates the URL (`replace: true`) so the back button doesn't stack a history entry per toggle. - [ ] The toggle is keyboard-accessible (Tab focus, Enter/Space to activate) and carries a `role="tablist"` with `aria-selected` on the active item. ### SSE + cache - [ ] `["pipeline", ...]` query-key invalidation stays the way it is — both views already write through the same key, so no dedup logic is needed. - [ ] `pipeline_stage` SSE patching logic is lifted into a shared hook (e.g. `usePipelineSSE(queryClient)`) so the single route doesn't carry two copies of the `onPipelineStage` callback. ### Nav - [ ] `components/app-shell.tsx` drops the `Grid` entry from both top-nav and bottom-tab arrays. Nav-label tests updated. ### Tests - [ ] Vitest: `/monitor?view=grid` renders `PipelineGrid`; `/monitor` (no `view`) renders `PipelineList`; malformed `view=xyz` falls back to list. - [ ] Vitest: toggling the control updates the URL search param and swaps the rendered component. - [ ] Playwright: existing `/app/monitor/grid?repo=…&risk=1` URL lands on `/app/monitor?view=grid&repo=…&risk=1` with filters preserved and the grid rendered. - [ ] Existing `pipeline-list.test.tsx` + `pipeline-grid.test.tsx` stay green — component APIs are unchanged. ## Out of scope - Any change to `PipelineGrid` or `PipelineList` rendering — the merge is routing-only. - Changes to `/issues/pipeline` server contract or SSE envelopes. - Per-view filter state (e.g. grid-only sort) — `sort` stays local component state. ## References - Spec: `specs/ui-consolidation.md` § UC-2 (Merge Grid into Pipeline as a view toggle) - Tracking: #259 - `apps/web/src/routes/monitor.grid.tsx` (deleted) - `apps/web/src/routes/monitor.index.tsx` (extended) - `apps/web/src/components/pipeline-list.tsx` (FilterBar extended) - `apps/web/src/components/app-shell.tsx` (nav prune) ## Dependencies - **Blocked by:** #262 (UC-1) — reuses the `/app/specs` → `/app/planner?spec=…` redirect helper pattern. Otherwise independent.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#263
No description provided.