feat(web-ui): pipeline progress indicator (Think→Build→Prove→Ship)#488
feat(web-ui): pipeline progress indicator (Think→Build→Prove→Ship)#488
Conversation
Adds a persistent PipelineProgressBar to AppLayout showing the four CodeFRAME phases with live completion status from existing API endpoints. - usePipelineStatus hook: aggregates prd/tasks/proof/review APIs via SWR, derives completion per phase (think=PRD exists, build=DONE+MERGED>0, prove=no open requirements, ship=no uncommitted diff) - PipelineProgressBar: horizontal stepper with checkmark (Tick01Icon) for complete phases, numbered circle for incomplete, current phase highlighted with bg-accent, responsive (abbreviated labels on mobile, full on lg:) - AppLayout: injects PipelineProgressBar above children in flex-col column - Bar hidden on root path (/), workspace-aware via localStorage events Tests: 24 new unit tests (hook + component), 371 total passing Closes #466
WalkthroughAdds a pipeline progress feature: a new client hook to compute per-phase status, a PipelineProgressBar UI component and tests, an icon mock for Tick01Icon, and integrates the bar into AppLayout. Changes
Sequence Diagram(s)sequenceDiagram
participant Page as Page (Next.js)
participant AppLayout as AppLayout
participant ProgressBar as PipelineProgressBar
participant Hook as usePipelineStatus
participant SWR as SWR/API
Page->>AppLayout: render
AppLayout->>ProgressBar: include component
ProgressBar->>Hook: call usePipelineStatus()
Hook->>Hook: read workspacePath (storage/event)
Hook->>SWR: conditionally fetch PRD, tasks, proof, review
SWR-->>Hook: return data / loading / error
Hook-->>ProgressBar: computed PipelineStatus
ProgressBar->>ProgressBar: determine active phase from pathname
ProgressBar->>Page: render step links, ticks, aria-current
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
web-ui/src/__tests__/hooks/usePipelineStatus.test.ts (2)
1-5: Remove unused import.
waitForis imported but never used in any test case.♻️ Suggested fix
-import { renderHook, waitFor } from '@testing-library/react'; +import { renderHook } from '@testing-library/react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/__tests__/hooks/usePipelineStatus.test.ts` around lines 1 - 5, Remove the unused imported symbol waitFor from the test file import list: locate the import statement at the top of web-ui/src/__tests__/hooks/usePipelineStatus.test.ts (currently importing renderHook, waitFor from '@testing-library/react') and delete only the waitFor token so the import reads import { renderHook } from '@testing-library/react'; leaving other imports (renderHook, useSWR, usePipelineStatus, getSelectedWorkspacePath) unchanged.
203-214: Test only verifies two phases' loading states.The test "returns isLoading true while any phase is loading" only asserts
think.isLoadingandbuild.isLoading. Consider verifying all four phases for completeness, or add separate tests for each phase's loading state.♻️ Suggested enhancement
it('returns isLoading true while any phase is loading', () => { mockSWRCalls({ prd: { isLoading: true }, tasks: { isLoading: false }, - proof: { isLoading: false }, - review: { isLoading: false }, + proof: { isLoading: true }, + review: { isLoading: false }, }); const { result } = renderHook(() => usePipelineStatus()); expect(result.current.think.isLoading).toBe(true); expect(result.current.build.isLoading).toBe(false); + expect(result.current.prove.isLoading).toBe(true); + expect(result.current.ship.isLoading).toBe(false); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/__tests__/hooks/usePipelineStatus.test.ts` around lines 203 - 214, The test only asserts two phases; update the "returns isLoading true while any phase is loading" spec so it verifies all four phase loading flags returned by usePipelineStatus: after calling mockSWRCalls({ prd: { isLoading: true }, tasks: { isLoading: false }, proof: { isLoading: false }, review: { isLoading: false }}) assert result.current.think.isLoading, result.current.build.isLoading, result.current.proof.isLoading, and result.current.review.isLoading match the mocked values (true for think/prd, false for the others) so the test fully covers all phase mappings in usePipelineStatus.web-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsx (2)
1-6: React import may be unnecessary.With the modern JSX transform (React 17+), explicit
import React from 'react'is no longer required in JSX files.♻️ Suggested fix
-import React from 'react'; import { render, screen } from '@testing-library/react';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsx` around lines 1 - 6, Remove the unnecessary top-level React import in the test file: delete the line "import React from 'react';" from PipelineProgressBar.test.tsx since the modern JSX transform makes it redundant; keep the other imports (render, screen, usePathname, PipelineProgressBar, usePipelineStatus) intact and run the test suite to verify nothing else relies on the explicit React symbol.
55-64: Consider adding aria-current assertion for active phase.The test "highlights the Think phase when on /prd" verifies the href but doesn't assert that
aria-current="step"is set on the active link. This would strengthen accessibility testing.♻️ Suggested enhancement
it('highlights the Think phase when on /prd', () => { mockUsePathname.mockReturnValue('/prd'); mockUsePipelineStatus.mockReturnValue(allIncomplete); render(<PipelineProgressBar />); // Think link should have active styling indicator const thinkLink = screen.getByRole('link', { name: /think/i }); expect(thinkLink).toHaveAttribute('href', '/prd'); + expect(thinkLink).toHaveAttribute('aria-current', 'step'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsx` around lines 55 - 64, Update the test "highlights the Think phase when on /prd" in PipelineProgressBar.test.tsx to also assert accessibility state by checking that the active link (thinkLink retrieved via screen.getByRole('link', { name: /think/i })) has aria-current="step"; keep the existing href assertion but add an expectation like expect(thinkLink).toHaveAttribute('aria-current', 'step') to validate the active phase is exposed to assistive tech.web-ui/src/hooks/usePipelineStatus.ts (2)
8-18: Consider moving exported interfaces to the shared types file.Per coding guidelines, TypeScript types should be defined in
web-ui/src/types/index.ts. ThePhaseStatusandPipelineStatusinterfaces are exported and could be consumed elsewhere (e.g., in components or tests).♻️ Suggested refactor
Move to
web-ui/src/types/index.ts:export interface PhaseStatus { isComplete: boolean; isLoading: boolean; } export interface PipelineStatus { think: PhaseStatus; build: PhaseStatus; prove: PhaseStatus; ship: PhaseStatus; }Then import in the hook:
-export interface PhaseStatus { - isComplete: boolean; - isLoading: boolean; -} - -export interface PipelineStatus { - think: PhaseStatus; - build: PhaseStatus; - prove: PhaseStatus; - ship: PhaseStatus; -} +import type { PipelineStatus } from '@/types';As per coding guidelines: "TypeScript types must be defined in
web-ui/src/types/index.ts".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/hooks/usePipelineStatus.ts` around lines 8 - 18, Move the exported interfaces PhaseStatus and PipelineStatus out of the hook and into the central shared types module (the project's types index), then update the hook to import these types instead of declaring them inline; specifically remove the local declarations of PhaseStatus and PipelineStatus from usePipelineStatus, add equivalent exports to the shared types index, and replace local references in functions/types inside usePipelineStatus with imported PhaseStatus and PipelineStatus to keep types centralized and consumable by other modules.
39-61: Consider surfacing error states to the consumer.The hook currently only exposes
isCompleteandisLoadingper phase. If any API call fails, the phase will appear incomplete without indicating an error occurred. This could confuse users when the pipeline status appears stuck.♻️ Suggested enhancement
export interface PhaseStatus { isComplete: boolean; isLoading: boolean; + isError: boolean; } // In each SWR call, destructure error: -const { data: prdData, isLoading: prdLoading } = useSWR(...) +const { data: prdData, isLoading: prdLoading, error: prdError } = useSWR(...) // Then in return: think: { isComplete: !!prdData, isLoading: prdLoading, + isError: !!prdError, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/hooks/usePipelineStatus.ts` around lines 39 - 61, The hook hides API errors by only exposing isComplete/isLoading for each phase (prd, tasks, proof, review); update each useSWR invocation (the ones returning prdData/prdLoading, tasksData/tasksLoading, proofData/proofLoading, reviewData/reviewLoading) to also capture the error (e.g., prdError, tasksError, proofError, reviewError) and expose per-phase error flags/objects (e.g., isPrdError, prdError) in the hook's return shape so consumers can detect and render error states instead of treating failures as simply incomplete.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web-ui/src/components/layout/PipelineProgressBar.tsx`:
- Around line 36-39: The comment claims the bar should be hidden for root and
any path with no matching phase, but the code only checks pathname === '/' and
then computes activePhase via getActivePhase(pathname); update the
implementation to match the comment by adding a check after computing
activePhase (e.g., if getActivePhase(pathname) returns falsy) and return null
when there is no active phase, referencing getActivePhase and pathname in the
change; alternatively, if the current behavior is intended, update the comment
above the pathname check to remove the "no matching phase" clause to reflect
that non-pipeline pages still render the bar.
---
Nitpick comments:
In `@web-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsx`:
- Around line 1-6: Remove the unnecessary top-level React import in the test
file: delete the line "import React from 'react';" from
PipelineProgressBar.test.tsx since the modern JSX transform makes it redundant;
keep the other imports (render, screen, usePathname, PipelineProgressBar,
usePipelineStatus) intact and run the test suite to verify nothing else relies
on the explicit React symbol.
- Around line 55-64: Update the test "highlights the Think phase when on /prd"
in PipelineProgressBar.test.tsx to also assert accessibility state by checking
that the active link (thinkLink retrieved via screen.getByRole('link', { name:
/think/i })) has aria-current="step"; keep the existing href assertion but add
an expectation like expect(thinkLink).toHaveAttribute('aria-current', 'step') to
validate the active phase is exposed to assistive tech.
In `@web-ui/src/__tests__/hooks/usePipelineStatus.test.ts`:
- Around line 1-5: Remove the unused imported symbol waitFor from the test file
import list: locate the import statement at the top of
web-ui/src/__tests__/hooks/usePipelineStatus.test.ts (currently importing
renderHook, waitFor from '@testing-library/react') and delete only the waitFor
token so the import reads import { renderHook } from '@testing-library/react';
leaving other imports (renderHook, useSWR, usePipelineStatus,
getSelectedWorkspacePath) unchanged.
- Around line 203-214: The test only asserts two phases; update the "returns
isLoading true while any phase is loading" spec so it verifies all four phase
loading flags returned by usePipelineStatus: after calling mockSWRCalls({ prd: {
isLoading: true }, tasks: { isLoading: false }, proof: { isLoading: false },
review: { isLoading: false }}) assert result.current.think.isLoading,
result.current.build.isLoading, result.current.proof.isLoading, and
result.current.review.isLoading match the mocked values (true for think/prd,
false for the others) so the test fully covers all phase mappings in
usePipelineStatus.
In `@web-ui/src/hooks/usePipelineStatus.ts`:
- Around line 8-18: Move the exported interfaces PhaseStatus and PipelineStatus
out of the hook and into the central shared types module (the project's types
index), then update the hook to import these types instead of declaring them
inline; specifically remove the local declarations of PhaseStatus and
PipelineStatus from usePipelineStatus, add equivalent exports to the shared
types index, and replace local references in functions/types inside
usePipelineStatus with imported PhaseStatus and PipelineStatus to keep types
centralized and consumable by other modules.
- Around line 39-61: The hook hides API errors by only exposing
isComplete/isLoading for each phase (prd, tasks, proof, review); update each
useSWR invocation (the ones returning prdData/prdLoading,
tasksData/tasksLoading, proofData/proofLoading, reviewData/reviewLoading) to
also capture the error (e.g., prdError, tasksError, proofError, reviewError) and
expose per-phase error flags/objects (e.g., isPrdError, prdError) in the hook's
return shape so consumers can detect and render error states instead of treating
failures as simply incomplete.
🪄 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
Run ID: 6f815411-1c02-4941-86ef-cf4855faa9ab
📒 Files selected for processing (6)
web-ui/__mocks__/@hugeicons/react.jsweb-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsxweb-ui/src/__tests__/hooks/usePipelineStatus.test.tsweb-ui/src/components/layout/AppLayout.tsxweb-ui/src/components/layout/PipelineProgressBar.tsxweb-ui/src/hooks/usePipelineStatus.ts
Review: feat(web-ui): pipeline progress indicatorClean implementation that reinforces the Think→Build→Prove→Ship mental model without requiring any new backend work. Good accessibility attributes, responsive layout, and solid test coverage. A few items worth addressing: Issues1. // Current — always null on first render, delaying all SWR fetches
const [workspacePath, setWorkspacePath] = useState<string | null>(null);
// Fix — lazy initializer avoids the null-on-first-render flash
const [workspacePath, setWorkspacePath] = useState<string | null>(() => getSelectedWorkspacePath());Without this, the four SWR calls start with 2. Ship phase false-positive on a fresh project isComplete: !!reviewData && reviewData.files_changed === 0,A brand-new workspace with zero committed changes will mark Ship as complete before the user has done anything. Consider requiring at least one DONE/MERGED task as a prerequisite guard, or document this as intentional "clean state" behavior. NitsUnused imports in tests
import { renderHook, waitFor } from '@testing-library/react';
// ^^^^^^^^ unused
Neither affects correctness, but they are lint-bait. Comment vs. code mismatch The inline comment says Looks good
The lazy initializer fix (issue 1) is the most impactful change — everything else is polish. |
- Fix comment/behavior mismatch: bar now returns null for non-pipeline paths (e.g. /settings) matching the stated intent (Option B) - Move PhaseStatus/PipelineStatus types to src/types/index.ts per guidelines - Add isError field to PhaseStatus to surface API call failures - Remove unused waitFor import from hook test - Expand isLoading test to assert all 4 phases independently - Remove unnecessary React import from component test (modern JSX transform) - Add aria-current="step" assertion to all active-phase tests - Add test for null return on non-pipeline paths
|
Review: Pipeline Progress Indicator Good implementation overall — the hook design is clean, test coverage is solid, and the component correctly handles the active/complete/upcoming visual states. The latest revision already addresses CodeRabbit's earlier suggestions (types in Ship completion semantics The most significant issue: Consider either:
No auto-refresh All four SWR calls use { revalidateOnFocus: false, refreshInterval: 30_000 }Hydration flash (minor, known tradeoff) The Nitpick:
The CodeRabbit items still worth actioning: removing the unused |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web-ui/src/hooks/usePipelineStatus.ts (1)
28-49: Refactor SWR calls to use tuple keys with destructuring for type safety and idiomatic v2 pattern.SWR v2 recommends tuple/array keys for dynamic parameters instead of string concatenation with closed-over variables. This improves type safety by eliminating non-null assertions and ensures the cache key fully represents request inputs.
Refactor pattern (apply to all four SWR calls)
- const { data: prdData, isLoading: prdLoading, error: prdError } = useSWR( - workspacePath ? `/pipeline/prd?path=${workspacePath}` : null, - () => prdApi.getLatest(workspacePath!), - { revalidateOnFocus: false } - ); + const { data: prdData, isLoading: prdLoading, error: prdError } = useSWR( + workspacePath ? ['pipeline/prd', workspacePath] as const : null, + ([, path]) => prdApi.getLatest(path), + { revalidateOnFocus: false } + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/hooks/usePipelineStatus.ts` around lines 28 - 49, Replace the string-based SWR keys with tuple/array keys and remove the non-null assertions by passing workspacePath as a tuple element so the key fully represents inputs; update all four useSWR calls (the ones producing prdData/prdLoading/prdError, tasksData/tasksLoading/tasksError, proofData/proofLoading/proofError, reviewData/reviewLoading/reviewError) to use keys like ['pipeline/prd', workspacePath] and change the fetcher to accept the key tuple and call the corresponding API functions (prdApi.getLatest, tasksApi.getAll, proofApi.getStatus, reviewApi.getDiff) using the workspacePath parameter instead of closing over it. Ensure revalidateOnFocus remains set and that the fetcher handles a possibly undefined workspacePath by returning null or not making the request when workspacePath is falsy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@web-ui/src/hooks/usePipelineStatus.ts`:
- Around line 28-49: Replace the string-based SWR keys with tuple/array keys and
remove the non-null assertions by passing workspacePath as a tuple element so
the key fully represents inputs; update all four useSWR calls (the ones
producing prdData/prdLoading/prdError, tasksData/tasksLoading/tasksError,
proofData/proofLoading/proofError, reviewData/reviewLoading/reviewError) to use
keys like ['pipeline/prd', workspacePath] and change the fetcher to accept the
key tuple and call the corresponding API functions (prdApi.getLatest,
tasksApi.getAll, proofApi.getStatus, reviewApi.getDiff) using the workspacePath
parameter instead of closing over it. Ensure revalidateOnFocus remains set and
that the fetcher handles a possibly undefined workspacePath by returning null or
not making the request when workspacePath is falsy.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cfe07f70-1748-49eb-9dc7-2345bf75bbd8
📒 Files selected for processing (5)
web-ui/src/__tests__/components/layout/PipelineProgressBar.test.tsxweb-ui/src/__tests__/hooks/usePipelineStatus.test.tsweb-ui/src/components/layout/PipelineProgressBar.tsxweb-ui/src/hooks/usePipelineStatus.tsweb-ui/src/types/index.ts
✅ Files skipped from review due to trivial changes (2)
- web-ui/src/types/index.ts
- web-ui/src/tests/components/layout/PipelineProgressBar.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- web-ui/src/tests/hooks/usePipelineStatus.test.ts
- web-ui/src/components/layout/PipelineProgressBar.tsx
Replace string-based SWR cache keys with tuple/array keys per SWR v2 pattern. Each key tuple ['pipeline/prd', workspacePath] fully encodes the request inputs without closing over workspacePath with ! assertions.
Follow-up ReviewGood iteration — the latest push addresses nearly everything from both my previous reviews and CodeRabbit: Addressed ✅
Still open Ship phase false-positive (raised in both previous reviews — still not addressed) isComplete: !!reviewData && reviewData.files_changed === 0,A freshly cloned repo with a clean working tree shows Ship as complete before anything has been built or proved. The isComplete: !!reviewData &&
reviewData.files_changed === 0 &&
(tasksData?.by_status?.MERGED ?? 0) > 0,No auto-refresh (raised in previous review — still not addressed) All four SWR calls use { revalidateOnFocus: false, refreshInterval: 30_000 }Nit: The nav still has The Ship semantics fix is the only blocker I'd hold for before merge. Auto-refresh is a meaningful UX improvement worth addressing in this PR since the bar is meant to show live status. The |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web-ui/src/hooks/usePipelineStatus.ts (1)
29-51: Optional: Extract shared SWR options to reduce repetition.The same options object is repeated four times; centralizing it lowers drift risk if behavior changes later.
♻️ Small refactor
+ const swrOptions = { revalidateOnFocus: false } as const; + const { data: prdData, isLoading: prdLoading, error: prdError } = useSWR( workspacePath ? (['pipeline/prd', workspacePath] as const) : null, ([, path]) => prdApi.getLatest(path), - { revalidateOnFocus: false } + swrOptions ); @@ const { data: tasksData, isLoading: tasksLoading, error: tasksError } = useSWR( workspacePath ? (['pipeline/tasks', workspacePath] as const) : null, ([, path]) => tasksApi.getAll(path), - { revalidateOnFocus: false } + swrOptions ); @@ const { data: proofData, isLoading: proofLoading, error: proofError } = useSWR( workspacePath ? (['pipeline/proof', workspacePath] as const) : null, ([, path]) => proofApi.getStatus(path), - { revalidateOnFocus: false } + swrOptions ); @@ const { data: reviewData, isLoading: reviewLoading, error: reviewError } = useSWR( workspacePath ? (['pipeline/review', workspacePath] as const) : null, ([, path]) => reviewApi.getDiff(path), - { revalidateOnFocus: false } + swrOptions );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web-ui/src/hooks/usePipelineStatus.ts` around lines 29 - 51, Extract the repeated SWR options into a single constant and reuse it in each useSWR call: create a const (e.g., swrOpts or commonSwrOptions) with { revalidateOnFocus: false } and replace the inline options object in the useSWR calls for prdData/prdLoading/prdError, tasksData/tasksLoading/tasksError, proofData/proofLoading/proofError, and reviewData/reviewLoading/reviewError so all four useSWR invocations pass the shared swr options instead of duplicating the object.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@web-ui/src/hooks/usePipelineStatus.ts`:
- Around line 29-51: Extract the repeated SWR options into a single constant and
reuse it in each useSWR call: create a const (e.g., swrOpts or commonSwrOptions)
with { revalidateOnFocus: false } and replace the inline options object in the
useSWR calls for prdData/prdLoading/prdError, tasksData/tasksLoading/tasksError,
proofData/proofLoading/proofError, and reviewData/reviewLoading/reviewError so
all four useSWR invocations pass the shared swr options instead of duplicating
the object.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b123c6f5-8982-47bf-bf0d-27ffc0c21fde
📒 Files selected for processing (2)
web-ui/src/__tests__/hooks/usePipelineStatus.test.tsweb-ui/src/hooks/usePipelineStatus.ts
✅ Files skipped from review due to trivial changes (1)
- web-ui/src/tests/hooks/usePipelineStatus.test.ts
Summary
PipelineProgressBartoAppLayoutshowing the four CodeFRAME phases with live completion status derived from existing API endpoints — no new backend work neededusePipelineStatushook (SWR + workspace-detection pattern) to aggregate completion from prd/tasks/proof/review APIs/(workspace selector), shown on all other pages with the current phase highlightedWhat was built
web-ui/src/hooks/usePipelineStatus.tsweb-ui/src/components/layout/PipelineProgressBar.tsxweb-ui/src/components/layout/AppLayout.tsx{children}web-ui/__mocks__/@hugeicons/react.jsTick01IconmockPhase completion logic
prdApi.getLatest()returns databy_status.DONE + MERGED > 0proof.total > 0 && proof.open === 0reviewApi.getDiff().files_changed === 0Test plan
Acceptance criteria
bg-accent)Link)lg:Closes #466
Summary by CodeRabbit
New Features
Tests