diff --git a/apps/web/src/components/files/FilePreviewPanel.test.ts b/apps/web/src/components/files/FilePreviewPanel.test.ts index afee3d54e06b8..16572a85f416b 100644 --- a/apps/web/src/components/files/FilePreviewPanel.test.ts +++ b/apps/web/src/components/files/FilePreviewPanel.test.ts @@ -5,7 +5,7 @@ import { ProjectReadFileError } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; import { describe, expect, it, vi } from "vite-plus/test"; -import { remapFileCommentAnnotations } from "./fileCommentAnnotations"; +import { promptFileCommentEntries, remapFileCommentAnnotations } from "./fileCommentAnnotations"; import { filePreviewReadErrorMessage, isMarkdownPreviewFile, @@ -63,6 +63,22 @@ describe("file comment annotations", () => { }, ]); }); + + it("shows only drafts and comments still in the prompt", () => { + const entry = (id: string, kind: "draft" | "comment") => ({ + id, + kind, + startLine: 3, + endLine: 3, + text: "", + }); + expect( + promptFileCommentEntries( + [entry("sent", "comment"), entry("new", "draft"), entry("kept", "comment")], + new Set(["kept"]), + ), + ).toEqual([entry("new", "draft"), entry("kept", "comment")]); + }); }); describe("isMarkdownPreviewFile", () => { diff --git a/apps/web/src/components/files/FilePreviewPanel.tsx b/apps/web/src/components/files/FilePreviewPanel.tsx index cd498b88bd580..f21183011f99c 100644 --- a/apps/web/src/components/files/FilePreviewPanel.tsx +++ b/apps/web/src/components/files/FilePreviewPanel.tsx @@ -76,7 +76,7 @@ import { resolvePathLinkTarget } from "@t3tools/shared/fileLinks"; import { ScrollArea } from "~/components/ui/scroll-area"; import { stackedThreadToast, toastManager } from "~/components/ui/toast"; import { type DraftId, useComposerDraftStore } from "~/composerDraftStore"; -import { buildFileReviewComment } from "~/reviewCommentContext"; +import { buildFileReviewComment, type ReviewCommentContext } from "~/reviewCommentContext"; import { assetEnvironment } from "~/state/assets"; import { usePreviewAvailable } from "~/browser/previewRuntime"; import { useEnvironmentHttpBaseUrl, usePrimaryEnvironmentId } from "~/state/environments"; @@ -105,6 +105,7 @@ import { nextFileCommentId, normalizeFileCommentRange, remapFileCommentAnnotations, + promptFileCommentEntries, } from "./fileCommentAnnotations"; import { installFileEditorDismissal } from "./fileEditorDismissal"; import { @@ -667,6 +668,8 @@ function useFileLineReveal( }; } +const EMPTY_REVIEW_COMMENTS: ReadonlyArray = []; + const createFileEditor: EditorFactory = ( editorType, options, @@ -760,6 +763,12 @@ function EditableFileSurface({ }: EditableFileSurfaceProps) { const addReviewComment = useComposerDraftStore((store) => store.addReviewComment); const removeReviewComment = useComposerDraftStore((store) => store.removeReviewComment); + const promptCommentIds = new Set( + useComposerDraftStore( + (store) => + store.getComposerDraft(composerDraftTarget)?.reviewComments ?? EMPTY_REVIEW_COMMENTS, + ).map((comment) => comment.id), + ); const [lineAnnotations, setLineAnnotations] = useState([]); const [selectionOverride, setSelectionOverride] = useState(null); const selectedRange = @@ -808,7 +817,7 @@ function EditableFileSurface({ setLineAnnotations((current) => (current === nextLineAnnotations ? current : remapped)); for (const annotation of remapped) { for (const entry of annotation.metadata.entries) { - if (entry.kind !== "comment") continue; + if (entry.kind !== "comment" || !promptCommentIds.has(entry.id)) continue; addReviewComment( composerDraftTarget, buildFileReviewComment({ @@ -974,29 +983,37 @@ function EditableFileSurface({ }} selectedLines={selectedRange} lineAnnotations={lineAnnotations} - renderAnnotation={(annotation) => ( -
- {annotation.metadata.entries.map((entry) => ( - removeAnnotationEntry(entry.id)} - onComment={(text) => submitAnnotationEntry(entry.id, text)} - onDelete={() => removeAnnotationEntry(entry.id)} - /> - ))} -
- )} + renderAnnotation={(annotation) => { + // Sent comments stay annotated so a failed send can restore them at remapped lines. + const entries = promptFileCommentEntries( + annotation.metadata.entries, + promptCommentIds, + ); + if (entries.length === 0) return null; + return ( +
+ {entries.map((entry) => ( + removeAnnotationEntry(entry.id)} + onComment={(text) => submitAnnotationEntry(entry.id, text)} + onDelete={() => removeAnnotationEntry(entry.id)} + /> + ))} +
+ ); + }} className="min-h-full" /> diff --git a/apps/web/src/components/files/fileCommentAnnotations.ts b/apps/web/src/components/files/fileCommentAnnotations.ts index cbd4394cbf745..2ee2ccb0c0f8b 100644 --- a/apps/web/src/components/files/fileCommentAnnotations.ts +++ b/apps/web/src/components/files/fileCommentAnnotations.ts @@ -52,3 +52,11 @@ export function remapFileCommentAnnotations( }, })); } + +/** Drafts and the comments still in the prompt; sent or removed ones are not shown or re-added. */ +export function promptFileCommentEntries( + entries: ReadonlyArray, + promptCommentIds: ReadonlySet, +): FileCommentAnnotationEntry[] { + return entries.filter((entry) => entry.kind === "draft" || promptCommentIds.has(entry.id)); +}