From f146f40b00ff5ca6b68b6418bc2c0d85bbd13bbf Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 10 May 2026 15:59:35 -0700 Subject: [PATCH] fix(session): preserve live comments across daemon-driven reloads --- src/hunk-session/bridge.test.ts | 10 ++- src/hunk-session/bridge.ts | 3 +- src/ui/AppHost.interactions.test.tsx | 91 ++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/hunk-session/bridge.test.ts b/src/hunk-session/bridge.test.ts index 87b6a954..386c62d7 100644 --- a/src/hunk-session/bridge.test.ts +++ b/src/hunk-session/bridge.test.ts @@ -116,7 +116,11 @@ describe("createHunkSessionBridge", () => { type: "command", requestId: "reload-1", command: "reload_session", - input: { sessionId: "session-1", nextInput: { kind: "vcs", staged: false, options: {} } }, + input: { + sessionId: "session-1", + nextInput: { kind: "vcs", staged: false, options: {} }, + sourcePath: "/repo", + }, }); await bridge.dispatchCommand({ type: "command", @@ -133,6 +137,10 @@ describe("createHunkSessionBridge", () => { expect(handlers.navigateToLocation).toHaveBeenCalledTimes(1); expect(handlers.reloadSession).toHaveBeenCalledTimes(1); + expect(handlers.reloadSession).toHaveBeenCalledWith( + { kind: "vcs", staged: false, options: {} }, + { resetApp: false, sourcePath: "/repo" }, + ); expect(handlers.removeLiveComment).toHaveBeenCalledTimes(1); expect(handlers.clearLiveComments).toHaveBeenCalledTimes(1); }); diff --git a/src/hunk-session/bridge.ts b/src/hunk-session/bridge.ts index 17ff9984..1987dabc 100644 --- a/src/hunk-session/bridge.ts +++ b/src/hunk-session/bridge.ts @@ -30,7 +30,7 @@ export interface HunkSessionBridgeHandlers { HunkSessionServerMessage, { command: "reload_session" } >["input"]["nextInput"], - options?: { sourcePath?: string }, + options?: { resetApp?: boolean; sourcePath?: string }, ) => Promise; removeLiveComment: (commentId: string) => RemovedCommentResult; } @@ -68,6 +68,7 @@ export function createHunkSessionBridge(handlers: HunkSessionBridgeHandlers) { return handlers.navigateToLocation(message.input); case "reload_session": return handlers.reloadSession(message.input.nextInput, { + resetApp: false, sourcePath: message.input.sourcePath, }); case "remove_comment": diff --git a/src/ui/AppHost.interactions.test.tsx b/src/ui/AppHost.interactions.test.tsx index 82dc9e45..c0fdd87e 100644 --- a/src/ui/AppHost.interactions.test.tsx +++ b/src/ui/AppHost.interactions.test.tsx @@ -72,6 +72,13 @@ function createMockHostClient() { latestSnapshot = snapshot.state; }, } as unknown as HunkSessionBrokerClient, + dispatchCommand: async (message: HunkSessionServerMessage) => { + if (!bridge) { + throw new Error("Expected App to register a bridge before running the test command."); + } + + return bridge.dispatchCommand(message); + }, getBridge: () => bridge, getLatestSnapshot: () => latestSnapshot, navigateToHunk: async ( @@ -1110,6 +1117,90 @@ describe("App interactions", () => { } }); + test("session reload preserves live comments while refreshing the file diff", async () => { + const dir = mkdtempSync(join(tmpdir(), "hunk-session-reload-")); + const left = join(dir, "before.ts"); + const right = join(dir, "after.ts"); + const reviewNote = "Keep this daemon review note"; + + writeFileSync(left, "export const answer = 41;\n"); + writeFileSync(right, "export const answer = 42;\n"); + + const bootstrap = await loadAppBootstrap({ + kind: "diff", + left, + right, + options: { + mode: "split", + }, + }); + const { dispatchCommand, hostClient } = createMockHostClient(); + + const setup = await testRender(, { + width: 220, + height: 20, + }); + + try { + await flush(setup); + + await act(async () => { + await dispatchCommand({ + type: "command", + requestId: "comment-1", + command: "comment", + input: { + sessionId: "session-1", + filePath: "after.ts", + side: "new", + line: 1, + summary: reviewNote, + reveal: true, + }, + }); + }); + + let frame = await waitForFrame(setup, (currentFrame) => currentFrame.includes(reviewNote)); + expect(frame).toContain(reviewNote); + + writeFileSync(right, "export const answer = 42;\nexport const added = true;\n"); + + await act(async () => { + await dispatchCommand({ + type: "command", + requestId: "reload-1", + command: "reload_session", + input: { + sessionId: "session-1", + nextInput: { + kind: "diff", + left, + right, + options: { + mode: "split", + }, + }, + sourcePath: dir, + }, + }); + }); + + frame = await waitForFrame( + setup, + (currentFrame) => currentFrame.includes("export const added = true;"), + 20, + ); + + expect(frame).toContain("export const added = true;"); + expect(frame).toContain(reviewNote); + } finally { + await act(async () => { + setup.renderer.destroy(); + }); + rmSync(dir, { force: true, recursive: true }); + } + }); + test("watch mode reloads the current file diff from disk", async () => { const dir = mkdtempSync(join(tmpdir(), "hunk-watch-")); const left = join(dir, "before.ts");