Move connection exit handling to controller

This commit is contained in:
murashit 2026-05-29 06:42:13 +09:00
parent 3fc2fc2e08
commit 92a4ed01bf
3 changed files with 54 additions and 8 deletions

View file

@ -17,12 +17,15 @@ export interface ChatConnectionControllerHost {
connectionWork: ChatConnectionWorkTracker;
appServer: ChatAppServerController;
setClient: (client: AppServerClient | null) => void;
invalidateResumeWork: () => void;
loadSharedThreadList: () => Promise<void>;
scheduleDeferredDiagnostics: () => void;
clearDeferredDiagnostics: () => void;
refreshTabHeader: () => void;
resetThreadTurnPresence: (hadTurns: boolean) => void;
setStatus: (status: string) => void;
addSystemMessage: (text: string) => void;
refreshLiveState: () => void;
render: () => void;
scheduleRender: () => void;
notifyConnectionFailed: () => void;
@ -54,6 +57,17 @@ export class ChatConnectionController {
this.host.connectionWork.invalidate();
}
handleExit(): void {
this.invalidate();
this.host.invalidateResumeWork();
this.host.setStatus("Codex app-server stopped.");
this.dispatch({ type: "connection/scoped-cleared" });
this.host.resetThreadTurnPresence(false);
this.host.setClient(null);
this.host.refreshLiveState();
this.host.render();
}
async refreshThreads(): Promise<void> {
this.host.setClient(this.host.connection.currentClient());
if (!this.host.connection.currentClient()) return;

View file

@ -267,14 +267,7 @@ export class CodexChatView extends ItemView {
this.render();
},
onExit: () => {
this.invalidateConnectionWork();
this.invalidateResumeWork();
this.setStatus("Codex app-server stopped.");
this.chatState.dispatch({ type: "connection/scoped-cleared" });
this.threadRename.resetThreadTurnPresence(false);
this.client = null;
this.plugin.refreshThreadsViewLiveState();
this.render();
this.connectionController.handleExit();
},
});
this.openCloseController = new ChatViewOpenCloseController({
@ -401,6 +394,9 @@ export class CodexChatView extends ItemView {
setClient: (client) => {
this.client = client;
},
invalidateResumeWork: () => {
this.invalidateResumeWork();
},
loadSharedThreadList: () => this.loadSharedThreadList(),
scheduleDeferredDiagnostics: () => {
this.scheduleDeferredDiagnostics();
@ -411,12 +407,18 @@ export class CodexChatView extends ItemView {
refreshTabHeader: () => {
this.refreshTabHeader();
},
resetThreadTurnPresence: (hadTurns) => {
this.threadRename.resetThreadTurnPresence(hadTurns);
},
setStatus: (status) => {
this.setStatus(status);
},
addSystemMessage: (text) => {
this.addSystemMessage(text);
},
refreshLiveState: () => {
this.plugin.refreshThreadsViewLiveState();
},
render: () => {
this.render();
},

View file

@ -35,12 +35,15 @@ function createController({ connected = false, client = {} as AppServerClient }
connectionWork: new ChatConnectionWorkTracker(),
appServer,
setClient,
invalidateResumeWork: vi.fn(),
loadSharedThreadList: vi.fn().mockResolvedValue(undefined),
scheduleDeferredDiagnostics: vi.fn(),
clearDeferredDiagnostics: vi.fn(),
refreshTabHeader: vi.fn(),
resetThreadTurnPresence: vi.fn(),
setStatus: vi.fn(),
addSystemMessage: vi.fn(),
refreshLiveState: vi.fn(),
render: vi.fn(),
scheduleRender: vi.fn(),
notifyConnectionFailed: vi.fn(),
@ -84,4 +87,31 @@ describe("ChatConnectionController", () => {
expect(refreshPublishedCapabilityDiagnostics).toHaveBeenCalledOnce();
expect(host.render).toHaveBeenCalledOnce();
});
it("clears connection-scoped state on server exit", () => {
const { controller, host, stateStore } = createController({ connected: true });
stateStore.dispatch({
type: "thread/list-applied",
threads: [{ id: "thread-1", title: "Thread 1" } as never],
threadsLoaded: true,
availableModels: [{ id: "model-1" } as never],
availableSkills: [{ name: "skill-1" } as never],
});
controller.handleExit();
expect(host.invalidateResumeWork).toHaveBeenCalledOnce();
expect(host.setStatus).toHaveBeenCalledWith("Codex app-server stopped.");
expect(host.resetThreadTurnPresence).toHaveBeenCalledWith(false);
expect(host.setClient).toHaveBeenCalledWith(null);
expect(host.refreshLiveState).toHaveBeenCalledOnce();
expect(host.render).toHaveBeenCalledOnce();
expect(stateStore.getState()).toMatchObject({
listedThreads: [],
threadsLoaded: false,
availableModels: [],
availableSkills: [],
runtimePicker: null,
});
});
});