Skip to content

Commit 0143b5e

Browse files
committed
fix: add error boundary to PendingReviewsBanner for corrupted data
- Add BannerErrorBoundary class component to catch rendering errors - Show 'Reviews data corrupted' message with clear button on error - Add clearAll() method to usePendingReviews hook - Wire up onClearAll prop through AIView Handles old localStorage format gracefully by catching render errors rather than requiring migration logic.
1 parent 5d608da commit 0143b5e

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
lines changed

src/browser/components/AIView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
631631
onSendToChat={handleSendReviewToChat}
632632
onRemove={pendingReviews.removeReview}
633633
onClearChecked={pendingReviews.clearChecked}
634+
onClearAll={pendingReviews.clearAll}
634635
/>
635636
<ChatInput
636637
variant="workspace"

src/browser/components/PendingReviewsBanner.tsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* that map to CSS variables defined in globals.css.
77
*/
88

9-
import React, { useState, useCallback, useMemo } from "react";
9+
import React, { useState, useCallback, useMemo, Component, type ReactNode } from "react";
1010
import {
1111
ChevronDown,
1212
ChevronUp,
@@ -17,12 +17,55 @@ import {
1717
MessageSquare,
1818
Eye,
1919
EyeOff,
20+
AlertTriangle,
2021
} from "lucide-react";
2122
import { cn } from "@/common/lib/utils";
2223
import { Button } from "./ui/button";
2324
import { Tooltip, TooltipWrapper } from "./Tooltip";
2425
import type { PendingReview, ReviewNoteData } from "@/common/types/review";
2526

27+
/**
28+
* Error boundary for the banner - catches rendering errors from malformed data
29+
*/
30+
interface ErrorBoundaryState {
31+
hasError: boolean;
32+
}
33+
34+
class BannerErrorBoundary extends Component<
35+
{ children: ReactNode; onClear: () => void },
36+
ErrorBoundaryState
37+
> {
38+
state: ErrorBoundaryState = { hasError: false };
39+
40+
static getDerivedStateFromError(): ErrorBoundaryState {
41+
return { hasError: true };
42+
}
43+
44+
render() {
45+
if (this.state.hasError) {
46+
return (
47+
<div className="border-border bg-dark flex items-center gap-2 border-t px-3 py-1.5 text-xs">
48+
<AlertTriangle className="text-warning size-3.5" />
49+
<span className="text-muted">Reviews data corrupted</span>
50+
<Button
51+
variant="ghost"
52+
size="sm"
53+
className="text-error h-5 px-2 text-xs"
54+
onClick={() => {
55+
this.props.onClear();
56+
this.setState({ hasError: false });
57+
}}
58+
>
59+
<Trash2 className="mr-1 h-3 w-3" />
60+
Clear all
61+
</Button>
62+
</div>
63+
);
64+
}
65+
return this.props.children;
66+
}
67+
}
68+
2669
interface PendingReviewsBannerProps {
2770
/** All reviews (pending and checked) */
2871
reviews: PendingReview[];
@@ -40,6 +83,8 @@ interface PendingReviewsBannerProps {
4083
onRemove: (reviewId: string) => void;
4184
/** Clear all checked reviews */
4285
onClearChecked: () => void;
86+
/** Clear all reviews (used for error recovery) */
87+
onClearAll: () => void;
4388
}
4489

4590
/**
@@ -122,7 +167,7 @@ const ReviewItem: React.FC<{
122167
);
123168
};
124169

125-
export const PendingReviewsBanner: React.FC<PendingReviewsBannerProps> = ({
170+
const PendingReviewsBannerInner: React.FC<PendingReviewsBannerProps> = ({
126171
reviews,
127172
pendingCount,
128173
checkedCount,
@@ -249,3 +294,14 @@ export const PendingReviewsBanner: React.FC<PendingReviewsBannerProps> = ({
249294
</div>
250295
);
251296
};
297+
298+
/**
299+
* Exported component wrapped in error boundary
300+
*/
301+
export const PendingReviewsBanner: React.FC<PendingReviewsBannerProps> = (props) => {
302+
return (
303+
<BannerErrorBoundary onClear={props.onClearAll}>
304+
<PendingReviewsBannerInner {...props} />
305+
</BannerErrorBoundary>
306+
);
307+
};

src/browser/components/RightSidebar/CodeReview/ReviewPanel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ import { parseDiff, extractAllHunks, buildGitDiffCommand } from "@/common/utils/
3232
import { getReviewSearchStateKey } from "@/common/constants/storage";
3333
import { Tooltip, TooltipWrapper } from "@/browser/components/Tooltip";
3434
import { parseNumstat, buildFileTree, extractNewPath } from "@/common/utils/git/numstatParser";
35-
import type { DiffHunk, ReviewFilters as ReviewFiltersType, ReviewNoteData } from "@/common/types/review";
35+
import type {
36+
DiffHunk,
37+
ReviewFilters as ReviewFiltersType,
38+
ReviewNoteData,
39+
} from "@/common/types/review";
3640
import type { FileTreeNode } from "@/common/utils/git/numstatParser";
3741
import { matchesKeybind, KEYBINDS, formatKeybind } from "@/browser/utils/ui/keybinds";
3842
import { applyFrontendFilters } from "@/browser/utils/review/filterHunks";

src/browser/hooks/usePendingReviews.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export interface UsePendingReviewsReturn {
3232
removeReview: (reviewId: string) => void;
3333
/** Clear all checked reviews */
3434
clearChecked: () => void;
35+
/** Clear all reviews (for error recovery) */
36+
clearAll: () => void;
3537
/** Get a review by ID */
3638
getReview: (reviewId: string) => PendingReview | undefined;
3739
}
@@ -160,6 +162,14 @@ export function usePendingReviews(workspaceId: string): UsePendingReviewsReturn
160162
});
161163
}, [setState]);
162164

165+
const clearAll = useCallback(() => {
166+
setState((prev) => ({
167+
...prev,
168+
reviews: {},
169+
lastUpdated: Date.now(),
170+
}));
171+
}, [setState]);
172+
163173
const getReview = useCallback(
164174
(reviewId: string): PendingReview | undefined => {
165175
return state.reviews[reviewId];
@@ -176,6 +186,7 @@ export function usePendingReviews(workspaceId: string): UsePendingReviewsReturn
176186
uncheckReview,
177187
removeReview,
178188
clearChecked,
189+
clearAll,
179190
getReview,
180191
};
181192
}

0 commit comments

Comments
 (0)