Skip to content

Commit 6009525

Browse files
committed
refactor: store ReviewNoteData instead of parsing formatted strings
- Add ReviewNoteData interface for structured review data - Add formatReviewNoteForChat() to format data when sending to chat - Update PendingReview.data to hold ReviewNoteData instead of content string - Update all onReviewNote callbacks throughout the callback chain - Simplify getReviewSummary() to use review.data.userNote directly - Remove string parsing regex from banner component
1 parent dc6ca67 commit 6009525

File tree

12 files changed

+79
-64
lines changed

12 files changed

+79
-64
lines changed

src/browser/components/AIView.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import { useForceCompaction } from "@/browser/hooks/useForceCompaction";
5252
import { useAPI } from "@/browser/contexts/API";
5353
import { usePendingReviews } from "@/browser/hooks/usePendingReviews";
5454
import { PendingReviewsBanner } from "./PendingReviewsBanner";
55+
import type { ReviewNoteData } from "@/common/types/review";
56+
import { formatReviewNoteForChat } from "@/common/types/review";
5557

5658
interface AIViewProps {
5759
workspaceId: string;
@@ -220,15 +222,15 @@ const AIViewInner: React.FC<AIViewProps> = ({
220222

221223
// Handler for review notes from Code Review tab - adds to pending reviews
222224
const handleReviewNote = useCallback(
223-
(note: string) => {
224-
pendingReviews.addReview(note);
225+
(data: ReviewNoteData) => {
226+
pendingReviews.addReview(data);
225227
},
226228
[pendingReviews]
227229
);
228230

229-
// Handler to send a review to chat input
230-
const handleSendReviewToChat = useCallback((content: string) => {
231-
chatInputAPI.current?.appendText(content);
231+
// Handler to send a review to chat input (formats to message string)
232+
const handleSendReviewToChat = useCallback((data: ReviewNoteData) => {
233+
chatInputAPI.current?.appendText(formatReviewNoteForChat(data));
232234
}, []);
233235

234236
// Handler for manual compaction from CompactionWarning click

src/browser/components/Messages/MessageRenderer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import type { DisplayedMessage } from "@/common/types/message";
3+
import type { ReviewNoteData } from "@/common/types/review";
34
import { UserMessage } from "./UserMessage";
45
import { AssistantMessage } from "./AssistantMessage";
56
import { ToolMessage } from "./ToolMessage";
@@ -16,7 +17,7 @@ interface MessageRendererProps {
1617
workspaceId?: string;
1718
isCompacting?: boolean;
1819
/** Handler for adding review notes from inline diffs */
19-
onReviewNote?: (note: string) => void;
20+
onReviewNote?: (data: ReviewNoteData) => void;
2021
}
2122

2223
// Memoized to prevent unnecessary re-renders when parent (AIView) updates

src/browser/components/Messages/ToolMessage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ import type {
3535
WebFetchToolArgs,
3636
WebFetchToolResult,
3737
} from "@/common/types/tools";
38+
import type { ReviewNoteData } from "@/common/types/review";
3839

3940
interface ToolMessageProps {
4041
message: DisplayedMessage & { type: "tool" };
4142
className?: string;
4243
workspaceId?: string;
4344
/** Handler for adding review notes from inline diffs */
44-
onReviewNote?: (note: string) => void;
45+
onReviewNote?: (data: ReviewNoteData) => void;
4546
}
4647

4748
// Type guards using Zod schemas for single source of truth

src/browser/components/PendingReviewsBanner.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import { cn } from "@/common/lib/utils";
2222
import { Button } from "./ui/button";
2323
import { Tooltip, TooltipWrapper } from "./Tooltip";
24-
import type { PendingReview } from "@/common/types/review";
24+
import type { PendingReview, ReviewNoteData } from "@/common/types/review";
2525

2626
interface PendingReviewsBannerProps {
2727
/** All reviews (pending and checked) */
@@ -35,7 +35,7 @@ interface PendingReviewsBannerProps {
3535
/** Uncheck a review */
3636
onUncheck: (reviewId: string) => void;
3737
/** Send review content to chat input */
38-
onSendToChat: (content: string) => void;
38+
onSendToChat: (data: ReviewNoteData) => void;
3939
/** Remove a review */
4040
onRemove: (reviewId: string) => void;
4141
/** Clear all checked reviews */
@@ -46,13 +46,8 @@ interface PendingReviewsBannerProps {
4646
* Extract a short summary from review content for display
4747
*/
4848
function getReviewSummary(review: PendingReview): string {
49-
// Extract the user's note from the review content (after the code block)
50-
const noteMatch = /```\n> (.+?)\n<\/review>/s.exec(review.content);
51-
if (noteMatch) {
52-
const note = noteMatch[1].trim();
53-
return note.length > 50 ? note.slice(0, 50) + "…" : note;
54-
}
55-
return `${review.filePath}:${review.lineRange}`;
49+
const note = review.data.userNote.trim();
50+
return note.length > 50 ? note.slice(0, 50) + "…" : note;
5651
}
5752

5853
/**
@@ -91,7 +86,7 @@ const ReviewItem: React.FC<{
9186
<div className="min-w-0 flex-1">
9287
<div className="flex items-baseline gap-1.5">
9388
<span className="font-mono text-[var(--color-review-accent)]">
94-
{review.filePath}:{review.lineRange}
89+
{review.data.filePath}:{review.data.lineRange}
9590
</span>
9691
</div>
9792
<div className="text-muted truncate">{getReviewSummary(review)}</div>
@@ -243,7 +238,7 @@ export const PendingReviewsBanner: React.FC<PendingReviewsBannerProps> = ({
243238
review={review}
244239
onCheck={() => onCheck(review.id)}
245240
onUncheck={() => onUncheck(review.id)}
246-
onSendToChat={() => onSendToChat(review.content)}
241+
onSendToChat={() => onSendToChat(review.data)}
247242
onRemove={() => onRemove(review.id)}
248243
/>
249244
))

src/browser/components/RightSidebar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { calculateTokenMeterData } from "@/common/utils/tokens/tokenMeterUtils";
1212
import { matchesKeybind, KEYBINDS, formatKeybind } from "@/browser/utils/ui/keybinds";
1313
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
1414
import { cn } from "@/common/lib/utils";
15+
import type { ReviewNoteData } from "@/common/types/review";
1516

1617
interface SidebarContainerProps {
1718
collapsed: boolean;
@@ -84,7 +85,7 @@ interface RightSidebarProps {
8485
/** Whether currently resizing */
8586
isResizing?: boolean;
8687
/** Callback when user adds a review note from Code Review tab */
87-
onReviewNote?: (note: string) => void;
88+
onReviewNote?: (data: ReviewNoteData) => void;
8889
/** Workspace is still being created (git operations in progress) */
8990
isCreating?: boolean;
9091
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import React, { useState, useMemo } from "react";
6-
import type { DiffHunk } from "@/common/types/review";
6+
import type { DiffHunk, ReviewNoteData } from "@/common/types/review";
77
import { SelectableDiffRenderer } from "../../shared/DiffRenderer";
88
import {
99
type SearchHighlightConfig,
@@ -24,7 +24,7 @@ interface HunkViewerProps {
2424
onClick?: (e: React.MouseEvent<HTMLElement>) => void;
2525
onToggleRead?: (e: React.MouseEvent<HTMLElement>) => void;
2626
onRegisterToggleExpand?: (hunkId: string, toggleFn: () => void) => void;
27-
onReviewNote?: (note: string) => void;
27+
onReviewNote?: (data: ReviewNoteData) => void;
2828
searchConfig?: SearchHighlightConfig;
2929
}
3030

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { parseDiff, extractAllHunks, buildGitDiffCommand } from "@/common/utils/
3232
import { getReviewSearchStateKey } from "@/common/constants/storage";
3333
import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/ui/tooltip";
3434
import { parseNumstat, buildFileTree, extractNewPath } from "@/common/utils/git/numstatParser";
35-
import type { DiffHunk, ReviewFilters as ReviewFiltersType } from "@/common/types/review";
35+
import type { DiffHunk, ReviewFilters as ReviewFiltersType, ReviewNoteData } from "@/common/types/review";
3636
import type { FileTreeNode } from "@/common/utils/git/numstatParser";
3737
import { matchesKeybind, KEYBINDS, formatKeybind } from "@/browser/utils/ui/keybinds";
3838
import { applyFrontendFilters } from "@/browser/utils/review/filterHunks";
@@ -42,7 +42,7 @@ import { useAPI } from "@/browser/contexts/API";
4242
interface ReviewPanelProps {
4343
workspaceId: string;
4444
workspacePath: string;
45-
onReviewNote?: (note: string) => void;
45+
onReviewNote?: (data: ReviewNoteData) => void;
4646
/** Trigger to focus panel (increment to trigger) */
4747
focusTrigger?: number;
4848
/** Workspace is still being created (git operations in progress) */

src/browser/components/shared/DiffRenderer.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
highlightSearchMatches,
1919
type SearchHighlightConfig,
2020
} from "@/browser/utils/highlighting/highlightSearchTerms";
21+
import type { ReviewNoteData } from "@/common/types/review";
2122

2223
// Shared type for diff line types
2324
export type DiffLineType = "add" | "remove" | "context" | "header";
@@ -313,8 +314,8 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
313314
interface SelectableDiffRendererProps extends Omit<DiffRendererProps, "filePath"> {
314315
/** File path for generating review notes */
315316
filePath: string;
316-
/** Callback when user submits a review note */
317-
onReviewNote?: (note: string) => void;
317+
/** Callback when user submits a review note with structured data */
318+
onReviewNote?: (data: ReviewNoteData) => void;
318319
/** Callback when user clicks on a line (to activate parent hunk) */
319320
onLineClick?: () => void;
320321
/** Search highlight configuration (optional) */
@@ -339,7 +340,7 @@ interface ReviewNoteInputProps {
339340
lineData: Array<{ index: number; type: DiffLineType; lineNum: number }>;
340341
lines: string[]; // Original diff lines with +/- prefix
341342
filePath: string;
342-
onSubmit: (note: string) => void;
343+
onSubmit: (data: ReviewNoteData) => void;
343344
onCancel: () => void;
344345
}
345346

@@ -379,20 +380,25 @@ const ReviewNoteInput: React.FC<ReviewNoteInputProps> = React.memo(
379380
});
380381

381382
// Elide middle lines if more than 3 lines selected
382-
let selectedLines: string;
383+
let selectedCode: string;
383384
if (allLines.length <= 3) {
384-
selectedLines = allLines.join("\n");
385+
selectedCode = allLines.join("\n");
385386
} else {
386387
const omittedCount = allLines.length - 2;
387-
selectedLines = [
388+
selectedCode = [
388389
allLines[0],
389390
` (${omittedCount} lines omitted)`,
390391
allLines[allLines.length - 1],
391392
].join("\n");
392393
}
393394

394-
const reviewNote = `<review>\nRe ${filePath}:${lineRange}\n\`\`\`\n${selectedLines}\n\`\`\`\n> ${noteText.trim()}\n</review>`;
395-
onSubmit(reviewNote);
395+
// Pass structured data instead of formatted message
396+
onSubmit({
397+
filePath,
398+
lineRange,
399+
selectedCode,
400+
userNote: noteText.trim(),
401+
});
396402
};
397403

398404
return (
@@ -530,9 +536,9 @@ export const SelectableDiffRenderer = React.memo<SelectableDiffRendererProps>(
530536
});
531537
};
532538

533-
const handleSubmitNote = (reviewNote: string) => {
539+
const handleSubmitNote = (data: ReviewNoteData) => {
534540
if (!onReviewNote) return;
535-
onReviewNote(reviewNote);
541+
onReviewNote(data);
536542
setSelection(null);
537543
};
538544

src/browser/components/tools/FileEditToolCall.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useToolExpansion, getStatusDisplay, type ToolStatus } from "./shared/to
2525
import { useCopyToClipboard } from "@/browser/hooks/useCopyToClipboard";
2626
import { DiffContainer, DiffRenderer, SelectableDiffRenderer } from "../shared/DiffRenderer";
2727
import { KebabMenu, type KebabMenuItem } from "../KebabMenu";
28+
import type { ReviewNoteData } from "@/common/types/review";
2829

2930
type FileEditOperationArgs =
3031
| FileEditReplaceStringToolArgs
@@ -41,13 +42,13 @@ interface FileEditToolCallProps {
4142
args: FileEditOperationArgs;
4243
result?: FileEditToolResult;
4344
status?: ToolStatus;
44-
onReviewNote?: (note: string) => void;
45+
onReviewNote?: (data: ReviewNoteData) => void;
4546
}
4647

4748
function renderDiff(
4849
diff: string,
4950
filePath?: string,
50-
onReviewNote?: (note: string) => void
51+
onReviewNote?: (data: ReviewNoteData) => void
5152
): React.ReactNode {
5253
try {
5354
const patches = parsePatch(diff);

src/browser/hooks/usePendingReviews.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,7 @@
66
import { useCallback, useMemo } from "react";
77
import { usePersistedState } from "./usePersistedState";
88
import { getPendingReviewsKey } from "@/common/constants/storage";
9-
import type { PendingReview, PendingReviewsState } from "@/common/types/review";
10-
11-
/**
12-
* Parse a review note to extract file path and line range
13-
* Expected format: <review>\nRe filePath:lineRange\n...
14-
*/
15-
function parseReviewNote(content: string): { filePath: string; lineRange: string } {
16-
const match = /Re ([^:]+):(\d+(?:-\d+)?)/.exec(content);
17-
if (match) {
18-
return { filePath: match[1], lineRange: match[2] };
19-
}
20-
return { filePath: "unknown", lineRange: "?" };
21-
}
9+
import type { PendingReview, PendingReviewsState, ReviewNoteData } from "@/common/types/review";
2210

2311
/**
2412
* Generate a unique ID for a review
@@ -34,8 +22,8 @@ export interface UsePendingReviewsReturn {
3422
pendingCount: number;
3523
/** Count of checked reviews */
3624
checkedCount: number;
37-
/** Add a new review from a review note */
38-
addReview: (content: string) => PendingReview;
25+
/** Add a new review from structured data */
26+
addReview: (data: ReviewNoteData) => PendingReview;
3927
/** Mark a review as checked */
4028
checkReview: (reviewId: string) => void;
4129
/** Uncheck a review (mark as pending again) */
@@ -77,13 +65,10 @@ export function usePendingReviews(workspaceId: string): UsePendingReviewsReturn
7765
}, [reviews]);
7866

7967
const addReview = useCallback(
80-
(content: string): PendingReview => {
81-
const { filePath, lineRange } = parseReviewNote(content);
68+
(data: ReviewNoteData): PendingReview => {
8269
const review: PendingReview = {
8370
id: generateReviewId(),
84-
content,
85-
filePath,
86-
lineRange,
71+
data,
8772
status: "pending",
8873
createdAt: Date.now(),
8974
};

0 commit comments

Comments
 (0)