From fa54d33559d09bd3b499938bcaa23aafa5ad59fe Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 30 Apr 2026 12:23:01 -0500 Subject: [PATCH] Bound jump-to-latest comment loading Co-Authored-By: Paperclip --- ui/src/components/IssueChatThread.tsx | 10 ++++++++-- ui/src/lib/optimistic-issue-comments.ts | 4 +++- ui/src/pages/IssueDetail.tsx | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index 1ce4193e6d1..511e9766bea 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -124,6 +124,7 @@ interface IssueChatMessageContext { onStopRun?: (runId: string) => Promise; stopRunLabel?: string; stoppingRunLabel?: string; + stopRunVariant?: "stop" | "pause"; onInterruptQueued?: (runId: string) => Promise; onCancelQueued?: (commentId: string) => void; onImageClick?: (src: string) => void; @@ -280,6 +281,7 @@ interface IssueChatThreadProps { onStopRun?: (runId: string) => Promise; stopRunLabel?: string; stoppingRunLabel?: string; + stopRunVariant?: "stop" | "pause"; imageUploadHandler?: (file: File) => Promise; onAttachImage?: (file: File) => Promise; draftKey?: string; @@ -1347,6 +1349,7 @@ function IssueChatAssistantMessage({ onStopRun, stopRunLabel = "Stop run", stoppingRunLabel = "Stopping...", + stopRunVariant = "stop", } = useContext(IssueChatCtx); const custom = message.metadata.custom as Record; const anchorId = typeof custom.anchorId === "string" ? custom.anchorId : undefined; @@ -1541,7 +1544,7 @@ function IssueChatAssistantMessage({ - {stopRunLabel.toLowerCase().includes("pause") ? ( + {stopRunVariant === "pause" ? ( ) : ( @@ -3087,6 +3090,7 @@ export function IssueChatThread({ onStopRun, stopRunLabel, stoppingRunLabel, + stopRunVariant, imageUploadHandler, onAttachImage, draftKey, @@ -3528,6 +3532,7 @@ export function IssueChatThread({ onStopRun: stableOnStopRun, stopRunLabel, stoppingRunLabel, + stopRunVariant, onInterruptQueued: stableOnInterruptQueued, onCancelQueued: stableOnCancelQueued, onImageClick: stableOnImageClick, @@ -3547,6 +3552,7 @@ export function IssueChatThread({ stableOnStopRun, stopRunLabel, stoppingRunLabel, + stopRunVariant, stableOnInterruptQueued, stableOnCancelQueued, stableOnImageClick, diff --git a/ui/src/lib/optimistic-issue-comments.ts b/ui/src/lib/optimistic-issue-comments.ts index f1b16005150..c1ec5549af6 100644 --- a/ui/src/lib/optimistic-issue-comments.ts +++ b/ui/src/lib/optimistic-issue-comments.ts @@ -162,6 +162,7 @@ export async function loadRemainingIssueCommentPages(p pages: ReadonlyArray> | undefined; pageParams: ReadonlyArray | undefined; pageSize: number; + maxPages?: number; fetchPage: (afterCommentId: string) => Promise>; }): Promise<{ pages: T[][]; pageParams: Array }> { const pages = (params.pages ?? []).map((page) => [...page]); @@ -176,8 +177,9 @@ export async function loadRemainingIssueCommentPages(p if (params.pageSize <= 0) return { pages, pageParams }; let cursor = getNextPageCursor(pages[pages.length - 1], params.pageSize); + const maxPages = Math.max(0, params.maxPages ?? Number.POSITIVE_INFINITY); const seenCursors = new Set(); - while (cursor && !seenCursors.has(cursor)) { + while (cursor && !seenCursors.has(cursor) && seenCursors.size < maxPages) { seenCursors.add(cursor); const nextPage = [...await params.fetchPage(cursor)]; pages.push(nextPage); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index f66ad83f683..78a2055f4df 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -158,6 +158,7 @@ type IssueDetailComment = (IssueComment | OptimisticIssueComment) & { const FEEDBACK_TERMS_URL = import.meta.env.VITE_FEEDBACK_TERMS_URL?.trim() || "https://paperclip.ing/tos"; const ISSUE_COMMENT_PAGE_SIZE = 50; const ISSUE_COMMENT_AUTOLOAD_LIMIT = ISSUE_COMMENT_PAGE_SIZE * 3; +const JUMP_TO_LATEST_MAX_COMMENT_PAGES = 10; const TREE_CONTROL_MODE_LABEL: Record = { pause: "Pause subtree", resume: "Resume subtree", @@ -858,6 +859,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({ onStopRun={onPauseWorkRun} stopRunLabel="Pause work" stoppingRunLabel="Pausing..." + stopRunVariant="pause" onAcceptInteraction={onAcceptInteraction} onRejectInteraction={onRejectInteraction} onSubmitInteractionAnswers={(interaction, answers) => @@ -2702,6 +2704,7 @@ export function IssueDetail() { pages: refreshed.data?.pages, pageParams: refreshed.data?.pageParams as Array | undefined, pageSize: ISSUE_COMMENT_PAGE_SIZE, + maxPages: JUMP_TO_LATEST_MAX_COMMENT_PAGES, fetchPage: (afterCommentId) => issuesApi.listComments(issueId!, { order: "desc",