From ac812568f5430fb2d00cb15e97d7b2dcc92d10e3 Mon Sep 17 00:00:00 2001 From: Dominic Roy Date: Wed, 12 Aug 2026 20:59:06 -0400 Subject: [PATCH 1/3] feat(web): quote reply to pull request comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each comment in the pull request Summary tab gets a reply button that prefills the composer with the comment quoted GitHub-style — every line behind "> ", cursor on the empty line below — instead of making the reader copy and reformat it by hand. Quoting appends under an existing draft rather than replacing it, and the button only appears when the viewer can comment and the comment has a body worth quoting. The composer's draft and posting state move up into the tab so the reply buttons can write into the draft and respect the posting lock; the draft stays keyed by pull request so switching tabs still starts clean. Co-Authored-By: Claude Fable 5 --- .../pullRequest/PullRequestSummaryTab.tsx | 131 ++++++++++++------ .../pullRequestDetail.logic.test.ts | 23 +++ .../pullRequest/pullRequestDetail.logic.ts | 15 ++ 3 files changed, 129 insertions(+), 40 deletions(-) diff --git a/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx b/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx index a57f2a4d160..b37594609ff 100644 --- a/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx +++ b/apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx @@ -12,11 +12,12 @@ import { HammerIcon, MessageSquareIcon, PencilIcon, + ReplyIcon, SendIcon, TagIcon, UsersIcon, } from "lucide-react"; -import { useRef, useState, type ReactNode } from "react"; +import { useRef, useState, type ReactNode, type Ref } from "react"; import { useAtomCommand } from "~/state/use-atom-command"; import { pullRequestEnvironment } from "~/state/pullRequests"; @@ -40,6 +41,7 @@ import { PullRequestActivityUnavailableState } from "./PullRequestActivityUnavai import { orderPullRequestComments, pullRequestFindingKey, + quoteReplyDraft, type PullRequestFinding, } from "./pullRequestDetail.logic"; import { @@ -280,43 +282,22 @@ function Section({ } function CommentComposer({ - environmentId, - detail, - onCommented, + body, + posting, + textareaRef, + onBodyChange, + onSubmit, }: { - environmentId: EnvironmentId; - detail: PullRequestDetailView; - onCommented: () => void; + body: string; + posting: boolean; + textareaRef: Ref; + onBodyChange: (body: string) => void; + onSubmit: () => void; }) { - const [body, setBody] = useState(""); - const [posting, setPosting] = useState(false); - const postComment = useAtomCommand(pullRequestEnvironment.comment, { reportFailure: false }); - - const submit = async () => { - const trimmed = body.trim(); - if (trimmed.length === 0 || posting) return; - setPosting(true); - const result = await postComment({ - environmentId, - input: { - projectId: detail.projectId, - repository: detail.repository, - number: detail.number, - body: trimmed, - }, - }); - setPosting(false); - if (result._tag === "Failure") { - toastManager.add({ type: "error", title: "Could not post the comment" }); - return; - } - setBody(""); - onCommented(); - }; - return (