Skip to content

feat(web-ui): PRD version history panel with diff and restore#525

Merged
frankbria merged 2 commits into
mainfrom
feature/issue-482-prd-version-history-panel
Apr 3, 2026
Merged

feat(web-ui): PRD version history panel with diff and restore#525
frankbria merged 2 commits into
mainfrom
feature/issue-482-prd-version-history-panel

Conversation

@frankbria

@frankbria frankbria commented Apr 3, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #482

  • Add PRDVersionHistoryModal — Dialog listing all saved versions with timestamps and change summaries. Supports read-only preview, unified diff against current, and non-destructive restore.
  • Add "History" button to PRDHeader (only shown when a PRD exists)
  • Wire state through PRDViewpage.tsx (versionHistoryOpen + onViewHistory callback)

How it works

The backend already stores every save as a new version via prdApi.createVersion(). This PR surfaces that data:

  1. Click History in the PRD header → opens the version history modal
  2. Each row shows: version number, timestamp, change summary (or "No summary"), "Current" badge on the latest
  3. Click View on any past version → read-only content preview
  4. Click Compare with current → calls GET /api/v2/prd/{id}/diff and shows unified diff
  5. Click Restore this version → confirmation prompt → creates new version with old content (non-destructive)

Acceptance criteria

  • Version list accessible from PRD page (drawer, panel, or tab)
  • Each version shows timestamp and change summary
  • Clicking a version opens read-only preview
  • Restore action creates new version (non-destructive)
  • Current version highlighted in list

Test plan

  • 15 new unit tests in PRDVersionHistoryModal.test.tsx
  • All existing PRD tests pass (62 total)
  • Zero lint errors in changed files

Summary by CodeRabbit

  • New Features

    • Version history modal to browse previous PRD versions, view previews, and mark current version.
    • Compare view to show diffs between a selected version and current.
    • Restore flow with confirmation to create a new PRD from a prior version.
    • History button added to PRD header to open the modal.
  • Tests

    • Comprehensive test suite covering loading/error states, preview, compare, and restore flows.

Add version history modal to PRD page so users can view, compare, and
restore previous PRD versions. All versions are already saved by the
backend; this surfaces them in the UI.

- PRDVersionHistoryModal: Dialog showing all saved versions (newest
  first) with timestamp and change_summary. Clicking "View" opens a
  read-only content preview. "Compare with current" calls prdApi.diff()
  and displays the unified diff. "Restore this version" creates a new
  version non-destructively via prdApi.createVersion(). Current version
  highlighted with a "Current" badge.
- PRDHeader: new "History" button (Time01Icon) visible when a PRD exists
- PRDView / page.tsx: versionHistoryOpen state + onViewHistory callback

Closes #482
@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 575f7b09-f257-4000-ae0c-7acb542a03f0

📥 Commits

Reviewing files that changed from the base of the PR and between 3296b1a and ff8f281.

📒 Files selected for processing (2)
  • web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx
  • web-ui/src/components/prd/PRDVersionHistoryModal.tsx

Walkthrough

Added a client-side PRD version history modal and integrated it into the PRD page. The modal lists versions (timestamp + summary), supports previewing a version, comparing it with the current version via diff, and restoring a version through a confirmation flow; includes tests covering loading, error, and interactive flows.

Changes

Cohort / File(s) Summary
Modal Component & Export
web-ui/src/components/prd/PRDVersionHistoryModal.tsx, web-ui/src/components/prd/index.ts
New exported PRDVersionHistoryModal component: fetches versions with SWR, shows list/preview/diff views, handles restore confirmation and createVersion call, resets state on close.
Tests
web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx
Comprehensive Jest/RTL tests for modal: jsdom ResizeObserver shim, mocked swr, mocked Radix ScrollArea, mocked prdApi (getVersions/diff/createVersion), assertions for list/preview/compare/restore, loading and error states.
PRD View & Header Props
web-ui/src/components/prd/PRDView.tsx, web-ui/src/components/prd/PRDHeader.tsx
Added optional onViewHistory?: () => void to PRDView/PRDHeader props and forwarded it; PRDHeader now conditionally renders a "History" button with Time01Icon when prd and callback exist.
Page Integration
web-ui/src/app/prd/page.tsx
Introduced versionHistoryOpen state, handleVersionRestored to mutate SWR cache and close modal, and wired onViewHistory to open PRDVersionHistoryModal when a PRD exists.

Sequence Diagram

sequenceDiagram
    actor User
    participant Header as PRDHeader
    participant Modal as PRDVersionHistoryModal
    participant API as prdApi
    participant SWR as SWR Cache

    User->>Header: Click "History"
    Header->>Modal: onViewHistory -> open modal

    Modal->>SWR: request versions (uses prdApi.getVersions)
    SWR-->>Modal: return version list
    Modal->>User: render version list

    User->>Modal: Click "View" on version
    Modal->>User: show preview of selected version

    alt Compare Flow
        User->>Modal: Click "Compare with current"
        Modal->>API: prdApi.diff(currentId, workspacePath, vA, vB)
        API-->>Modal: return diff text
        Modal->>User: display diff
    end

    alt Restore Flow
        User->>Modal: Click "Restore this version"
        Modal->>User: show confirm prompt
        User->>Modal: Confirm
        Modal->>API: prdApi.createVersion(restoredContent, summary)
        API-->>Modal: return new PrdResponse
        Modal->>SWR: mutate PRD cache with new PrdResponse
        Modal->>User: close modal, reset view
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through versioned trails so bright,

Viewed, compared, restored by soft moonlight,
A nibble here, a careful mend,
History kept from end to end,
Rewind, replay — a rabbit’s delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly and concisely summarizes the main addition: a PRD version history panel with diff and restore capabilities, directly matching the changeset's primary feature.
Linked Issues check ✅ Passed All coding requirements from #482 are met: version list with timestamps and summaries, read-only preview, compare with current via diff API, restore creates new version non-destructively, current version highlighted, and affected components updated.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #482 requirements: PRDVersionHistoryModal component, History button in PRDHeader, state wiring through PRDView and page.tsx, and corresponding tests. No unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-482-prd-version-history-panel

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

@claude

claude Bot commented Apr 3, 2026

Copy link
Copy Markdown

Review: PRDVersionHistoryModal

Overall this is clean, well-structured work. The component decomposition, non-destructive restore pattern, and test coverage are all solid. A few issues worth addressing:


Bug: unused prop in VersionPreviewProps

currentVersion is declared in the interface but never destructured or used inside VersionPreview:

// PRDVersionHistoryModal.tsx ~L205
interface VersionPreviewProps {
  currentVersion: number;   // declared...
  ...
}

function VersionPreview({
  preview,
  onBack,
  onCompare,
  onStartRestore,
  onCancelRestore,
  onConfirmRestore,
  // currentVersion never destructured
}: VersionPreviewProps) {

Either remove the prop from the interface and the call site, or destructure and use it (e.g. to disable the Restore button when previewing the current version — though that path is already blocked at the list level).


Silent error handling in handleCompare and handleConfirmRestore

Both async handlers swallow errors with empty catch blocks:

} catch {
  setPreview((p) => p && { ...p, diffLoading: false });
}

Users get no feedback when diff loading or restore fails. Consider adding a diffError / restoreError field to PreviewState and rendering an inline error message, or at minimum calling toast.error(...) (already used in page.tsx).


"Compare with current" can't be retried

Once a diff loads, disabled={diffLoading || !!diff} permanently disables the Compare button. If the diff request fails (empty catch), the button stays enabled — but if it succeeds there's no way to re-fetch. Removing || !!diff and allowing re-compare would be simpler.


Minor: test comment could be clearer

const viewButtons = screen.getAllByRole('button', { name: /^view$/i });
await user.click(viewButtons[0]); // click View on version 2

This is correct (version 3 is current so has no View button, making index 0 = version 2), but the comment could say so explicitly to avoid confusion when reading the test in isolation.


Looks good

  • Non-destructive restore creating a new version is the right pattern
  • SWR cache key correctly includes workspace path and is gated on open
  • handleClose properly resets view/preview state on any close mechanism
  • handleVersionRestored using mutatePrd(newPrd, false) avoids an unnecessary refetch
  • 15 tests with solid coverage of loading, error, and interaction flows

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
web-ui/src/components/prd/PRDVersionHistoryModal.tsx (1)

228-235: Unused currentVersion prop in VersionPreview.

The currentVersion prop is declared in VersionPreviewProps (line 220) and accepted in the component signature but is never used in the function body. This is likely dead code that can be removed.

♻️ Proposed fix
 interface VersionPreviewProps {
   preview: PreviewState;
-  currentVersion: number;
   onBack: () => void;
   onCompare: () => void;
   onStartRestore: () => void;
   onCancelRestore: () => void;
   onConfirmRestore: () => void;
 }

 function VersionPreview({
   preview,
-  currentVersion,
   onBack,
   onCompare,
   onStartRestore,
   onCancelRestore,
   onConfirmRestore,
 }: VersionPreviewProps) {

Also update the call site at line 131:

         <VersionPreview
           preview={preview}
-          currentVersion={prd.version}
           onBack={handleBackToList}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/prd/PRDVersionHistoryModal.tsx` around lines 228 - 235,
Remove the unused currentVersion prop: update the VersionPreviewProps type to
drop currentVersion, remove currentVersion from the VersionPreview component
parameter list (the VersionPreview function), and update any call sites that
pass currentVersion (e.g., where VersionPreview is instantiated) to stop passing
that prop; ensure PropTypes/TS types and any destructuring in VersionPreview no
longer reference currentVersion so there are no leftover unused variables.
web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx (1)

163-194: Consider adding a test for diff fetch failure.

The "Compare with current" tests verify the happy path, but there's no test for when prdApi.diff rejects. Given that the component silently swallows this error (as noted in the component review), adding a test would document the current behavior and serve as a reminder to improve error handling.

📝 Example test for diff error
it('handles diff fetch failure gracefully', async () => {
  mockDiff.mockRejectedValueOnce(new Error('Network error'));

  const user = userEvent.setup();
  render(<PRDVersionHistoryModal {...defaultProps} />);

  const viewButtons = screen.getAllByRole('button', { name: /^view$/i });
  await user.click(viewButtons[0]); // version 2

  const compareBtn = screen.getByRole('button', { name: /compare with current/i });
  await user.click(compareBtn);

  await waitFor(() => {
    expect(mockDiff).toHaveBeenCalled();
  });

  // Button should be re-enabled after failure (or show error state)
  // Update assertion based on desired behavior
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx` around
lines 163 - 194, Add a new test in PRDVersionHistoryModal.test.tsx that
simulates prdApi.diff failing by using mockDiff.mockRejectedValueOnce(new
Error('Network error')), then render <PRDVersionHistoryModal {...defaultProps}
/>, open a version via the same view button flow (using userEvent and
viewButtons[0]) and click the "Compare with current" button; assert that
mockDiff was called with currentPrd.id, WORKSPACE and the two versions and then
assert the component recovers from the failure (e.g., the compare button is
re-enabled or an error message is shown) to document current behavior and
prevent regressions.
🤖 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/prd/PRDVersionHistoryModal.tsx`:
- Around line 93-108: In handleConfirmRestore, the catch block for
prdApi.createVersion currently swallows errors and only resets state; import
toast from "sonner" and in the catch block call toast.error with a clear message
and the caught error (e.g., toast.error(`Failed to restore version:
${error?.message || error}`)) while still resetting preview (setPreview(...)).
Update the catch clause to capture the error parameter (catch (error) { ... })
and include the toast.error call so the user sees feedback when restore fails.
- Around line 77-91: The diff fetch currently swallows errors; add a diffError
field to PreviewState and track it via setPreview in handleCompare and
handleViewVersion: ensure handleViewVersion initializes preview.diffError =
false, set diffError = false before starting the API call, on success set
diffError = false (and set diff/result), and in the catch set diffError = true
and diffLoading = false (optionally store an error message). Update
VersionPreview to render an error message when preview?.diffError is true so
users see the failure. Use the existing functions/variables: PreviewState,
handleViewVersion, handleCompare, setPreview, VersionPreview, and prdApi.diff.

---

Nitpick comments:
In `@web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx`:
- Around line 163-194: Add a new test in PRDVersionHistoryModal.test.tsx that
simulates prdApi.diff failing by using mockDiff.mockRejectedValueOnce(new
Error('Network error')), then render <PRDVersionHistoryModal {...defaultProps}
/>, open a version via the same view button flow (using userEvent and
viewButtons[0]) and click the "Compare with current" button; assert that
mockDiff was called with currentPrd.id, WORKSPACE and the two versions and then
assert the component recovers from the failure (e.g., the compare button is
re-enabled or an error message is shown) to document current behavior and
prevent regressions.

In `@web-ui/src/components/prd/PRDVersionHistoryModal.tsx`:
- Around line 228-235: Remove the unused currentVersion prop: update the
VersionPreviewProps type to drop currentVersion, remove currentVersion from the
VersionPreview component parameter list (the VersionPreview function), and
update any call sites that pass currentVersion (e.g., where VersionPreview is
instantiated) to stop passing that prop; ensure PropTypes/TS types and any
destructuring in VersionPreview no longer reference currentVersion so there are
no leftover unused variables.
🪄 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: f13fbdc2-9989-4278-9061-8fa26a27c0dc

📥 Commits

Reviewing files that changed from the base of the PR and between 511f505 and 3296b1a.

📒 Files selected for processing (6)
  • web-ui/src/__tests__/components/prd/PRDVersionHistoryModal.test.tsx
  • web-ui/src/app/prd/page.tsx
  • web-ui/src/components/prd/PRDHeader.tsx
  • web-ui/src/components/prd/PRDVersionHistoryModal.tsx
  • web-ui/src/components/prd/PRDView.tsx
  • web-ui/src/components/prd/index.ts

Comment thread web-ui/src/components/prd/PRDVersionHistoryModal.tsx
Comment thread web-ui/src/components/prd/PRDVersionHistoryModal.tsx
- Remove unused currentVersion prop from VersionPreviewProps interface
  and call site
- Add diffError state to PreviewState; show inline error message when
  diff fetch fails so users can see the failure and retry
- Allow Compare button to be re-clicked after a failed or successful
  diff (remove || !!diff from disabled condition)
- Add toast.error in handleConfirmRestore so users get feedback when
  restore fails
- Add test: shows error message and re-enables Compare button on diff
  failure
- Clarify test comment for viewButtons index
@claude

claude Bot commented Apr 3, 2026

Copy link
Copy Markdown

Follow-up Review

All issues from my initial review have been addressed:

  • Unused prop in VersionPreviewProps removed from interface and call site
  • handleCompare now sets diffError state and renders an inline error message
  • Compare button stays enabled after diffError, allowing retry
  • handleConfirmRestore now calls toast.error in the catch block
  • Test added for diff fetch failure covering error state and button re-enable

The component is clean and the fixes are well-integrated. Ready to merge.

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.

UX: Add PRD version history panel for tracking requirement evolution

1 participant