fix: cancel chat scrolls on reconnect

This commit is contained in:
Shakker
2026-08-02 20:56:06 +01:00
parent 8f0337a730
commit 2a199fddde
2 changed files with 23 additions and 0 deletions
+2
View File
@@ -37,6 +37,7 @@ import {
reconcileStaleChatRunAfterSessionStatePublication,
replayPendingChatAbort,
} from "./run-lifecycle.ts";
import { cancelChatScroll } from "./scroll.ts";
import { clearChatMessagesFromCache } from "./session-message-cache.ts";
import { normalizeSidebarLayout } from "./sidebar-layout.ts";
import { reconcileWaitingApprovalsFromSnapshot } from "./tool-stream.ts";
@@ -184,6 +185,7 @@ export abstract class ChatPaneContext extends ChatPaneLifecycle {
}
if (sourceChanged) {
this.cancelHeaderRename();
cancelChatScroll(state);
releaseChatMediaResourceSubscriber(state.requestUpdate);
// A reconnect can retain the browser client. Keep async ownership tied
// to the logical connection, not only the transport object identity.
@@ -899,6 +899,27 @@ describe("chat pane connection lifecycle", () => {
expect(state.chatLoading).toBe(true);
});
it("cancels scroll work owned by the prior Gateway connection", () => {
const client = { request: vi.fn() } as unknown as GatewayBrowserClient;
const { pane, state } = createTestChatPane({ client, sessions: {} as SessionCapability });
const cancelCommit = vi.fn();
const initialScrollGeneration = state.chatScrollGeneration;
state.chatScrollCommitCleanup = cancelCommit;
state.chatIsProgrammaticScroll = true;
pane.applyGatewaySnapshot({
...pane.context.gateway.snapshot,
client,
phase: "reconnecting",
hello: null,
});
expect(cancelCommit).toHaveBeenCalledOnce();
expect(state.chatScrollCommitCleanup).toBeNull();
expect(state.chatScrollGeneration).toBe(initialScrollGeneration + 1);
expect(state.chatIsProgrammaticScroll).toBe(false);
});
it("refreshes the transcript before secondary hydration after a same-client reconnect", () => {
const request = vi.fn(() => new Promise<never>(() => {}));
const client = {