From d949518be2a9300513fd511b646d6a279a424145 Mon Sep 17 00:00:00 2001 From: murashit Date: Mon, 25 May 2026 18:10:40 +0900 Subject: [PATCH] Use current panel for chat resume selection --- src/features/chat/view.ts | 4 ++- tests/features/chat/view-connection.test.ts | 38 +++++++++++++++------ tests/main.test.ts | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index 709d3428..78f04887 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -78,6 +78,7 @@ export interface CodexChatHost { readonly vaultPath: string; openThreadInNewView(threadId: string): Promise; openThreadInAvailableView(threadId: string): Promise; + focusThreadInOpenView(threadId: string): Promise; openTurnDiff(state: ChatTurnDiffViewState): Promise; notifyThreadArchived(threadId: string): void; notifyThreadRenamed(threadId: string, name: string): void; @@ -1252,7 +1253,8 @@ export class CodexChatView extends ItemView { this.addSystemMessage("Finish or interrupt the current turn before switching threads."); return; } - await this.plugin.openThreadInAvailableView(threadId); + if (await this.plugin.focusThreadInOpenView(threadId)) return; + await this.resumeThread(threadId); } private closeToolbarPanelOnOutsidePointer(event: PointerEvent): void { diff --git a/tests/features/chat/view-connection.test.ts b/tests/features/chat/view-connection.test.ts index 6f9f0653..a7aeeaf0 100644 --- a/tests/features/chat/view-connection.test.ts +++ b/tests/features/chat/view-connection.test.ts @@ -220,10 +220,10 @@ describe("CodexChatView connection lifecycle", () => { expect(client.startTurn).toHaveBeenCalledWith("thread-new", "/vault", [{ type: "text", text: "hello", text_elements: [] }]); }); - it("routes slash resume through the shared panel selection path", async () => { - const openThreadInAvailableView = vi.fn().mockResolvedValue(undefined); + it("focuses an open panel instead of resuming a duplicate slash resume thread", async () => { + const focusThreadInOpenView = vi.fn().mockResolvedValue(true); const host = chatHost({ - openThreadInAvailableView, + focusThreadInOpenView, }); connectionMock.state.client = connectedClient({ listThreads: vi.fn().mockResolvedValue({ data: [threadFixture("thread-1")] }), @@ -234,23 +234,38 @@ describe("CodexChatView connection lifecycle", () => { view.setComposerText("/resume thread-1"); await (view as unknown as { submitComposerAction: () => Promise }).submitComposerAction(); - expect(openThreadInAvailableView).toHaveBeenCalledWith("thread-1"); + expect(focusThreadInOpenView).toHaveBeenCalledWith("thread-1"); expect(connectionMock.state.client["resumeThread"]).not.toHaveBeenCalled(); }); + it("resumes slash resume threads in the current panel when they are not already open", async () => { + const focusThreadInOpenView = vi.fn().mockResolvedValue(false); + const client = connectedClient({ + listThreads: vi.fn().mockResolvedValue({ data: [threadFixture("thread-1")] }), + threadTurnsList: vi.fn().mockResolvedValue({ data: [turnWithUserMessage("restored prompt")], nextCursor: null }), + }); + connectionMock.state.client = client; + const view = await chatView({ + host: chatHost({ + focusThreadInOpenView, + }), + }); + + await view.connect(); + view.setComposerText("/resume thread-1"); + await (view as unknown as { submitComposerAction: () => Promise }).submitComposerAction(); + + expect(focusThreadInOpenView).toHaveBeenCalledWith("thread-1"); + expect(client.resumeThread).toHaveBeenCalledWith("thread-1", "/vault"); + }); + it("keeps resumed messages pinned to bottom after slash resume in the same empty panel", async () => { const client = connectedClient({ listThreads: vi.fn().mockResolvedValue({ data: [threadFixture("thread-1")] }), threadTurnsList: vi.fn().mockResolvedValue({ data: [turnWithUserMessage("restored prompt")], nextCursor: null }), }); connectionMock.state.client = client; - const view = await chatView({ - host: chatHost({ - openThreadInAvailableView: async (threadId) => { - await view.openThread(threadId); - }, - }), - }); + const view = await chatView(); await view.onOpen(); await view.connect(); @@ -549,6 +564,7 @@ function chatHost(overrides: Partial = {}): CodexChatHost { vaultPath: "/vault", openThreadInNewView: vi.fn(), openThreadInAvailableView: vi.fn().mockResolvedValue(undefined), + focusThreadInOpenView: vi.fn().mockResolvedValue(false), openTurnDiff: vi.fn(), notifyThreadArchived: vi.fn(), notifyThreadRenamed: vi.fn(), diff --git a/tests/main.test.ts b/tests/main.test.ts index d00f1df9..6a188b72 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -234,9 +234,9 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) { { settings: { ...DEFAULT_SETTINGS, codexPath: "codex", sendShortcut: "enter" }, vaultPath: "/vault", - openNewPanel: vi.fn(), openThreadInNewView: vi.fn(), openThreadInAvailableView: vi.fn(), + focusThreadInOpenView: vi.fn(), openTurnDiff: vi.fn(), notifyThreadArchived: vi.fn(), notifyThreadRenamed: vi.fn(),