From 8d9b741158d6a944d08cbf9b5db5d3dfe0e10ecf Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 14 Sep 2026 10:56:47 +0200 Subject: [PATCH] fix(vscode): report failed mention drops and GitHub comment batches insertDrop now returns false when execCommand silently no-ops without changing the editor, so drag callers do not treat a failed insert as a consumed drag. postAllGithub now converts a rejected or synchronously throwing send into the batch failure result, so it stops at the first failure and the review UI clears its pending state instead of hanging. --- .../tests/unit/comments-github.test.ts | 15 +++++++++++++++ .../tests/unit/use-file-mention.test.ts | 9 +++++++++ .../webview-ui/diff-viewer/comments-github.ts | 11 +++++++++-- .../webview-ui/src/hooks/useFileMention.ts | 5 ++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/comments-github.test.ts b/packages/kilo-vscode/tests/unit/comments-github.test.ts index 8395294bf612..7d9dac921232 100644 --- a/packages/kilo-vscode/tests/unit/comments-github.test.ts +++ b/packages/kilo-vscode/tests/unit/comments-github.test.ts @@ -112,4 +112,19 @@ describe("postAllGithub", () => { expect(result.posted.map((item) => item.id)).toEqual(["first"]) expect(result.failure).toBe("boom") }) + + it("treats a rejected send as the first failure and keeps the unposted comments", async () => { + const sent: string[] = [] + const result = await postAllGithub( + [comment("first", 2), comment("second", 1), comment("third", 1)], + fake((item) => { + sent.push(item.id) + if (item.id === "second") throw new Error("network down") + return { success: true } + }), + ) + expect(sent).toEqual(["first", "second"]) + expect(result.posted.map((item) => item.id)).toEqual(["first"]) + expect(result.failure).toBe("network down") + }) }) diff --git a/packages/kilo-vscode/tests/unit/use-file-mention.test.ts b/packages/kilo-vscode/tests/unit/use-file-mention.test.ts index 1993c102eb89..c54b015f70ee 100644 --- a/packages/kilo-vscode/tests/unit/use-file-mention.test.ts +++ b/packages/kilo-vscode/tests/unit/use-file-mention.test.ts @@ -2022,4 +2022,13 @@ describe("useFileMention reference drops", () => { expect(area.value).toBe("") }) }) + + it("reports a drop as unhandled when the editor does not apply the insert", () => { + withMention("", undefined, (mention, area) => { + // execCommand can silently no-op; the textarea stays unchanged. + globalThis.document.execCommand = () => false + expect(mention.insertDrop({ kind: "terminal" }, area, () => {}, "")).toBe(false) + expect(area.value).toBe("") + }) + }) }) diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/comments-github.ts b/packages/kilo-vscode/webview-ui/diff-viewer/comments-github.ts index 4408ec5f1ef8..067844077d98 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/comments-github.ts +++ b/packages/kilo-vscode/webview-ui/diff-viewer/comments-github.ts @@ -84,7 +84,9 @@ export function createCommentsGithub(opts: Options): CommentsGithub { const available = () => opts.canPublish?.() !== false && !!opts.target() && !!opts.snapshot() - const send = (comment: ReviewComment) => { + // async so a synchronous throw while building the request becomes a rejection + // that postAllGithub can report instead of escaping the per-comment loop. + const send = async (comment: ReviewComment) => { const { promise, resolve: settle } = Promise.withResolvers<{ success: boolean; error?: string }>() const target = opts.target() const context = resolve(comment) @@ -128,7 +130,12 @@ export async function postAllGithub( ): Promise<{ posted: ReviewComment[]; failure?: string }> { const posted: ReviewComment[] = [] for (const comment of comments) { - const result = await github.send(comment) + // A rejected send is a failed request too. Convert it into a result so the + // batch stops at the first failure and the caller can clear its pending state. + const result = await github.send(comment).then( + (value) => value, + (error: unknown) => ({ success: false, error: error instanceof Error ? error.message : String(error) }), + ) if (!result.success) return { posted, failure: result.error } posted.push(comment) } diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts b/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts index 4b938070518d..6bf3b0178706 100644 --- a/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts +++ b/packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts @@ -737,7 +737,10 @@ export function useFileMention( setText(textarea.value) closeMention() onSelect?.() - return true + // execCommand can silently no-op when the editor is not editable. Report the + // drop as unhandled when the text did not change so callers do not treat a + // failed insert as a consumed drag. + return textarea.value !== val } const insertDrop = (