From 28f7ea17c83374520629342099767a0ddc6ec3ae Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Thu, 30 Apr 2026 22:07:18 +0000 Subject: [PATCH 1/2] feat(code-review): batch review comments into a single agent prompt Closes #1827. Inline review comments now have an "Add to review" checkbox: when checked, submitting the form queues the comment into a per-task draft instead of dispatching to the agent. A pinned PendingReviewBar at the bottom of the review panel lists every queued draft (file, line range, snippet) with edit / delete affordances and a single "Send to agent" action that combines all drafts into one prompt via buildBatchedInlineCommentsPrompt. Drafts also render inline on the diff at their line range as DraftCommentAnnotation, sharing the same edit flow as the comment textarea. Drafts are in-memory per task and clear on successful send, manual discard, or task unmount. Submitting a one-off comment (checkbox off) leaves any queued drafts intact. PR comment "Fix with agent" / "Ask agent" actions are unchanged. Generated-By: PostHog Code Task-Id: 53f42d57-7308-48a1-996f-836f1c7536eb --- .../components/CommentAnnotation.tsx | 132 +++++++++++---- .../components/DraftCommentAnnotation.tsx | 64 ++++++++ .../components/InteractiveFileDiff.tsx | 152 ++++++++++++++---- .../components/PendingReviewBar.tsx | 142 ++++++++++++++++ .../code-review/components/ReviewShell.tsx | 48 +++--- .../code-review/hooks/useCommentState.ts | 39 ++++- .../stores/reviewDraftsStore.test.ts | 111 +++++++++++++ .../code-review/stores/reviewDraftsStore.ts | 110 +++++++++++++ .../renderer/features/code-review/types.ts | 9 ++ .../code-review/utils/diffAnnotations.ts | 17 ++ .../code-review/utils/reviewPrompts.ts | 47 +++++- 11 files changed, 790 insertions(+), 81 deletions(-) create mode 100644 apps/code/src/renderer/features/code-review/components/DraftCommentAnnotation.tsx create mode 100644 apps/code/src/renderer/features/code-review/components/PendingReviewBar.tsx create mode 100644 apps/code/src/renderer/features/code-review/stores/reviewDraftsStore.test.ts create mode 100644 apps/code/src/renderer/features/code-review/stores/reviewDraftsStore.ts diff --git a/apps/code/src/renderer/features/code-review/components/CommentAnnotation.tsx b/apps/code/src/renderer/features/code-review/components/CommentAnnotation.tsx index e8ad71f9a..244531125 100644 --- a/apps/code/src/renderer/features/code-review/components/CommentAnnotation.tsx +++ b/apps/code/src/renderer/features/code-review/components/CommentAnnotation.tsx @@ -1,9 +1,10 @@ import { sendPromptToAgent } from "@features/sessions/utils/sendPromptToAgent"; import { PaperPlaneTilt, X } from "@phosphor-icons/react"; import type { AnnotationSide } from "@pierre/diffs"; -import { Button, IconButton } from "@radix-ui/themes"; +import { Button, Checkbox, Flex, IconButton, Text } from "@radix-ui/themes"; import { isSendMessageSubmitKey } from "@utils/sendMessageKey"; -import { useCallback, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useReviewDraftsStore } from "../stores/reviewDraftsStore"; import { buildInlineCommentPrompt } from "../utils/reviewPrompts"; interface CommentAnnotationProps { @@ -13,6 +14,8 @@ interface CommentAnnotationProps { endLine: number; side: AnnotationSide; onDismiss: () => void; + initialText?: string; + editingDraftId?: string; } export function CommentAnnotation({ @@ -22,28 +25,78 @@ export function CommentAnnotation({ endLine, side, onDismiss, + initialText, + editingDraftId, }: CommentAnnotationProps) { const textareaRef = useRef(null); + const addDraft = useReviewDraftsStore((s) => s.addDraft); + const updateDraft = useReviewDraftsStore((s) => s.updateDraft); + const setBatchEnabled = useReviewDraftsStore((s) => s.setBatchEnabled); + const initialBatchEnabled = useReviewDraftsStore((s) => + s.isBatchEnabled(taskId), + ); - const setTextareaRef = useCallback((el: HTMLTextAreaElement | null) => { - ( - textareaRef as React.MutableRefObject - ).current = el; - if (el) { - requestAnimationFrame(() => el.focus()); - } - }, []); + const [batch, setBatch] = useState( + editingDraftId ? true : initialBatchEnabled, + ); + + const setTextareaRef = useCallback( + (el: HTMLTextAreaElement | null) => { + ( + textareaRef as React.MutableRefObject + ).current = el; + if (el) { + if (initialText !== undefined) { + el.value = initialText; + } + requestAnimationFrame(() => el.focus()); + } + }, + [initialText], + ); + + // Keep the checkbox in sync if another action toggles batchEnabled while + // this textarea is open (e.g. user adds a draft elsewhere). + useEffect(() => { + if (editingDraftId) return; + setBatch(initialBatchEnabled); + }, [initialBatchEnabled, editingDraftId]); const handleSubmit = useCallback(() => { const text = textareaRef.current?.value?.trim(); - if (text) { + if (!text) return; + + if (editingDraftId) { + updateDraft(taskId, editingDraftId, text); onDismiss(); - sendPromptToAgent( - taskId, - buildInlineCommentPrompt(filePath, startLine, endLine, side, text), - ); + return; } - }, [taskId, filePath, startLine, endLine, side, onDismiss]); + + if (batch) { + addDraft(taskId, { filePath, startLine, endLine, side, text }); + setBatchEnabled(taskId, true); + onDismiss(); + return; + } + + onDismiss(); + sendPromptToAgent( + taskId, + buildInlineCommentPrompt(filePath, startLine, endLine, side, text), + ); + }, [ + taskId, + filePath, + startLine, + endLine, + side, + onDismiss, + batch, + editingDraftId, + addDraft, + updateDraft, + setBatchEnabled, + ]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -59,27 +112,52 @@ export function CommentAnnotation({ [handleSubmit, onDismiss], ); + const submitLabel = editingDraftId + ? "Update comment" + : batch + ? "Add to review" + : "Send to agent"; + return (