diff --git a/src/features/chat/thread-selection-controller.ts b/src/features/chat/thread-selection-controller.ts index b5ee9744..677de63a 100644 --- a/src/features/chat/thread-selection-controller.ts +++ b/src/features/chat/thread-selection-controller.ts @@ -1,4 +1,4 @@ -import { chatTurnBusy, type ChatState, type ChatStateStore } from "./chat-state"; +import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state"; export interface ThreadSelectionControllerHost { stateStore: ChatStateStore; @@ -22,7 +22,18 @@ export class ThreadSelectionController { await this.host.resumeThread(threadId); } + async selectThreadFromToolbar(threadId: string): Promise { + if (chatTurnBusy(this.state) && threadId !== this.state.activeThreadId) return; + + this.dispatch({ type: "ui/panel-set", panel: null }); + await this.selectThread(threadId); + } + private get state(): ChatState { return this.host.stateStore.getState(); } + + private dispatch(action: ChatAction): void { + this.host.stateStore.dispatch(action); + } } diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index e73b70f0..87107c4c 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -989,11 +989,7 @@ export class CodexChatView extends ItemView { }, connect: () => void this.reconnectActions.reconnectFromToolbar(), refreshStatus: () => void this.refreshStatusPanel(), - resumeThread: (threadId) => { - if (this.turnBusy && threadId !== this.state.activeThreadId) return; - this.dispatch({ type: "ui/panel-set", panel: null }); - void this.selectThread(threadId); - }, + resumeThread: (threadId) => void this.threadSelection.selectThreadFromToolbar(threadId), startArchiveThread: (threadId) => { this.toolbarPanels.startArchive(threadId); }, diff --git a/tests/features/chat/thread-selection-controller.test.ts b/tests/features/chat/thread-selection-controller.test.ts index 24c8218a..f02ce4c2 100644 --- a/tests/features/chat/thread-selection-controller.test.ts +++ b/tests/features/chat/thread-selection-controller.test.ts @@ -64,4 +64,29 @@ describe("ThreadSelectionController", () => { expect(host.focusThreadInOpenView).not.toHaveBeenCalled(); expect(host.resumeThread).not.toHaveBeenCalled(); }); + + it("closes the toolbar panel before selecting from the toolbar", async () => { + const { controller, host, stateStore } = createController(); + stateStore.dispatch({ type: "ui/panel-set", panel: "history" }); + + await controller.selectThreadFromToolbar("thread"); + + expect(stateStore.getState().openDetails.size).toBe(0); + expect(host.closeForThreadSelection).toHaveBeenCalledOnce(); + expect(host.resumeThread).toHaveBeenCalledWith("thread"); + }); + + it("ignores toolbar selection while another thread is busy", async () => { + const { controller, host, stateStore } = createController(); + resumeThreadState(stateStore, "active"); + stateStore.dispatch({ type: "ui/panel-set", panel: "history" }); + stateStore.dispatch({ type: "turn/started", threadId: "active", turnId: "turn" }); + + await controller.selectThreadFromToolbar("other"); + + expect(stateStore.getState().openDetails.has("history")).toBe(true); + expect(host.addSystemMessage).not.toHaveBeenCalled(); + expect(host.closeForThreadSelection).not.toHaveBeenCalled(); + expect(host.resumeThread).not.toHaveBeenCalled(); + }); });