diff --git a/src/features/chat/chat-host.ts b/src/features/chat/chat-host.ts index 9d545c7a..74eac7bf 100644 --- a/src/features/chat/chat-host.ts +++ b/src/features/chat/chat-host.ts @@ -7,13 +7,11 @@ export interface CodexChatHost { readonly settings: CodexPanelSettings; 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 | null): void; refreshThreadsViewLiveState(): void; - refreshOpenViews(): void; refreshSharedThreadListFromOpenSurface(): void; applyThreadListSnapshot(threads: readonly Thread[]): void; refreshThreadList(fetchThreads: () => Promise): Promise; diff --git a/src/main.ts b/src/main.ts index 88b87b0e..fdd335bb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,16 +3,13 @@ import { Plugin } from "obsidian"; import { VIEW_TYPE_CODEX_PANEL, VIEW_TYPE_CODEX_THREADS, VIEW_TYPE_CODEX_TURN_DIFF } from "./constants"; import { registerSelectionRewriteCommand } from "./features/selection-rewrite/command"; import { CodexChatView } from "./features/chat/view"; +import type { CodexChatHost } from "./features/chat/chat-host"; import { CodexChatTurnDiffView } from "./features/chat/chat-turn-diff-view"; -import { openThreadPicker } from "./features/thread-picker/modal"; -import type { OpenCodexPanelSnapshot } from "./runtime/open-panel-snapshot"; -import { CodexThreadsView } from "./features/threads-view/view"; -import type { Thread } from "./generated/app-server/v2/Thread"; -import type { Model } from "./generated/app-server/v2/Model"; -import type { SharedAppServerMetadata } from "./runtime/shared-app-server-state"; +import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal"; +import { CodexThreadsView, type CodexThreadsHost } from "./features/threads-view/view"; import { SharedAppServerCache } from "./runtime/shared-app-server-cache"; import { DEFAULT_SETTINGS, getVaultPath, normalizeSettings, settingsMatchNormalizedData, type CodexPanelSettings } from "./settings/model"; -import { CodexPanelSettingTab } from "./settings/tab"; +import { CodexPanelSettingTab, type CodexPanelSettingTabHost } from "./settings/tab"; import { persistedChatTurnDiffViewState, type ChatTurnDiffViewState } from "./features/chat/ui/turn-diff"; import { WorkspacePanelCoordinator } from "./workspace/panel-coordinator"; import { ThreadSurfaceCoordinator } from "./workspace/thread-surface-coordinator"; @@ -24,14 +21,14 @@ export default class CodexPanelPlugin extends Plugin { private readonly panels = new WorkspacePanelCoordinator({ app: this.app, refreshThreadsViewLiveState: () => { - this.refreshThreadsViewLiveState(); + this.threadSurfaces.refreshThreadsViewLiveState(); }, }); private readonly threadSurfaces = new ThreadSurfaceCoordinator({ app: this.app, panels: this.panels, refreshThreadSurfaces: () => { - this.refreshSharedThreadListFromOpenSurface(); + this.threadSurfaces.refreshSharedThreadListFromOpenSurface(); }, }); @@ -40,9 +37,9 @@ export default class CodexPanelPlugin extends Plugin { this.vaultPath = getVaultPath(this.app); await this.loadSettings(); - this.registerView(VIEW_TYPE_CODEX_PANEL, (leaf) => new CodexChatView(leaf, this)); + this.registerView(VIEW_TYPE_CODEX_PANEL, (leaf) => new CodexChatView(leaf, this.chatHost())); this.registerView(VIEW_TYPE_CODEX_TURN_DIFF, (leaf) => new CodexChatTurnDiffView(leaf)); - this.registerView(VIEW_TYPE_CODEX_THREADS, (leaf) => new CodexThreadsView(leaf, this)); + this.registerView(VIEW_TYPE_CODEX_THREADS, (leaf) => new CodexThreadsView(leaf, this.threadsHost())); this.registerEvent( this.app.workspace.on("active-leaf-change", (leaf) => { this.panels.recordLastFocusedPanel(leaf); @@ -50,19 +47,19 @@ export default class CodexPanelPlugin extends Plugin { ); this.addRibbonIcon("bot-message-square", "Open panel", () => { - void this.activateView(); + void this.panels.activateView(); }); this.addCommand({ id: "open-panel", name: "Open panel", - callback: () => void this.activateView(), + callback: () => void this.panels.activateView(), }); this.addCommand({ id: "open-new-panel", name: "Open new panel", - callback: () => void this.activateNewView(), + callback: () => void this.panels.activateNewView(), }); this.addCommand({ @@ -74,21 +71,21 @@ export default class CodexPanelPlugin extends Plugin { this.addCommand({ id: "open-thread", name: "Open thread...", - callback: () => void openThreadPicker(this), + callback: () => void openThreadPicker(this.threadPickerHost()), }); this.addCommand({ id: "new-chat", name: "Start new chat", callback: async () => { - const view = await this.activateView(); + const view = await this.panels.activateView(); await view.startNewThread(); }, }); registerSelectionRewriteCommand(this); - this.addSettingTab(new CodexPanelSettingTab(this.app, this)); + this.addSettingTab(new CodexPanelSettingTab(this.app, this, this.settingTabHost())); this.panels.scheduleBootRestoredPanelLoads(); } @@ -97,39 +94,7 @@ export default class CodexPanelPlugin extends Plugin { this.panels.cancelBootRestoredPanelLoads(); } - async activateView(): Promise { - return this.panels.activateView(); - } - - async activateNewView(options: { connect?: boolean } = {}): Promise { - return this.panels.activateNewView(options); - } - - async openThreadInNewView(threadId: string): Promise { - return this.panels.openThreadInNewView(threadId); - } - - async openNewPanel(): Promise { - await this.panels.openNewPanel(); - } - - async openThreadInAvailableView(threadId: string): Promise { - await this.panels.openThreadInAvailableView(threadId); - } - - async openThreadInCurrentView(threadId: string): Promise { - await this.panels.openThreadInCurrentView(threadId); - } - - async focusThreadInOpenView(threadId: string): Promise { - return this.panels.focusThreadInOpenView(threadId); - } - - async openThreadInIdleEmptyView(threadId: string): Promise { - return this.panels.openThreadInIdleEmptyView(threadId); - } - - async activateThreadsView(): Promise { + private async activateThreadsView(): Promise { const leaf = await this.app.workspace.ensureSideLeaf(VIEW_TYPE_CODEX_THREADS, "left", { active: true, reveal: true, @@ -139,7 +104,7 @@ export default class CodexPanelPlugin extends Plugin { return view; } - async openTurnDiff(state: ChatTurnDiffViewState): Promise { + private async openTurnDiff(state: ChatTurnDiffViewState): Promise { const existing = this.app.workspace.getLeavesOfType(VIEW_TYPE_CODEX_TURN_DIFF).at(0); const leaf = existing ?? this.app.workspace.getLeaf("tab"); await leaf.setViewState({ type: VIEW_TYPE_CODEX_TURN_DIFF, active: true, state: { ...persistedChatTurnDiffViewState(state) } }); @@ -150,67 +115,6 @@ export default class CodexPanelPlugin extends Plugin { await this.app.workspace.revealLeaf(leaf); } - refreshOpenViews(): void { - this.threadSurfaces.refreshOpenViews(); - } - - refreshSharedThreadListFromOpenSurface(): void { - this.threadSurfaces.refreshSharedThreadListFromOpenSurface(); - } - - refreshThreadList(fetchThreads: () => Promise): Promise { - return this.sharedAppServerCache.refreshThreadList(fetchThreads, (threads) => { - this.threadSurfaces.applyThreadListSnapshot(threads); - }); - } - - applyThreadListSnapshot(threads: readonly Thread[]): void { - this.sharedAppServerCache.applyThreadListSnapshot(threads); - this.threadSurfaces.applyThreadListSnapshot(threads); - } - - cachedThreadList(): readonly Thread[] | null { - return this.sharedAppServerCache.cachedThreadList(); - } - - publishAppServerMetadata(metadata: SharedAppServerMetadata): void { - this.sharedAppServerCache.applyAppServerMetadataSnapshot(metadata); - this.threadSurfaces.publishAppServerMetadata(metadata); - } - - publishModels(models: readonly Model[]): void { - this.sharedAppServerCache.applyModelsSnapshot(models); - this.threadSurfaces.publishModels(models); - } - - cachedAppServerMetadata(): SharedAppServerMetadata | null { - return this.sharedAppServerCache.cachedAppServerMetadata(); - } - - cachedModels(): Model[] { - return this.sharedAppServerCache.cachedModels(); - } - - refreshThreadsViewLiveState(): void { - this.threadSurfaces.refreshThreadsViewLiveState(); - } - - notifyThreadArchived(threadId: string, options: { closeOpenPanels?: boolean } = {}): void { - this.threadSurfaces.notifyThreadArchived(threadId, options); - } - - notifyThreadRenamed(threadId: string, name: string | null): void { - this.threadSurfaces.notifyThreadRenamed(threadId, name); - } - - getOpenPanelSnapshots(): OpenCodexPanelSnapshot[] { - return this.panels.getOpenPanelSnapshots(); - } - - async focusOpenPanel(viewId: string, threadId: string | null = null): Promise { - return this.panels.focusOpenPanel(viewId, threadId); - } - async loadSettings(): Promise { const data: unknown = await this.loadData(); this.settings = normalizeSettings(data); @@ -222,4 +126,95 @@ export default class CodexPanelPlugin extends Plugin { async saveSettings(): Promise { await this.saveData(this.settings); } + + private chatHost(): CodexChatHost { + return { + settings: this.settings, + vaultPath: this.vaultPath, + openThreadInNewView: (threadId) => this.panels.openThreadInNewView(threadId), + focusThreadInOpenView: (threadId) => this.panels.focusThreadInOpenView(threadId), + openTurnDiff: (state) => this.openTurnDiff(state), + notifyThreadArchived: (threadId) => { + this.threadSurfaces.notifyThreadArchived(threadId); + }, + notifyThreadRenamed: (threadId, name) => { + this.threadSurfaces.notifyThreadRenamed(threadId, name); + }, + refreshThreadsViewLiveState: () => { + this.threadSurfaces.refreshThreadsViewLiveState(); + }, + refreshSharedThreadListFromOpenSurface: () => { + this.threadSurfaces.refreshSharedThreadListFromOpenSurface(); + }, + applyThreadListSnapshot: (threads) => { + this.sharedAppServerCache.applyThreadListSnapshot(threads); + this.threadSurfaces.applyThreadListSnapshot(threads); + }, + refreshThreadList: (fetchThreads) => + this.sharedAppServerCache.refreshThreadList(fetchThreads, (threads) => { + this.threadSurfaces.applyThreadListSnapshot(threads); + }), + cachedThreadList: () => this.sharedAppServerCache.cachedThreadList(), + publishAppServerMetadata: (metadata) => { + this.sharedAppServerCache.applyAppServerMetadataSnapshot(metadata); + this.threadSurfaces.publishAppServerMetadata(metadata); + }, + cachedAppServerMetadata: () => this.sharedAppServerCache.cachedAppServerMetadata(), + }; + } + + private threadsHost(): CodexThreadsHost { + return { + settings: this.settings, + vaultPath: this.vaultPath, + openNewPanel: () => this.panels.openNewPanel(), + openThreadInAvailableView: (threadId) => this.panels.openThreadInAvailableView(threadId), + getOpenPanelSnapshots: () => this.panels.getOpenPanelSnapshots(), + notifyThreadArchived: (threadId, options) => { + this.threadSurfaces.notifyThreadArchived(threadId, options); + }, + notifyThreadRenamed: (threadId, name) => { + this.threadSurfaces.notifyThreadRenamed(threadId, name); + }, + refreshThreadList: (fetchThreads) => + this.sharedAppServerCache.refreshThreadList(fetchThreads, (threads) => { + this.threadSurfaces.applyThreadListSnapshot(threads); + }), + cachedThreadList: () => this.sharedAppServerCache.cachedThreadList(), + }; + } + + private threadPickerHost(): ThreadPickerHost { + return { + app: this.app, + settings: this.settings, + vaultPath: this.vaultPath, + cachedThreadList: () => this.sharedAppServerCache.cachedThreadList(), + refreshThreadList: (fetchThreads) => + this.sharedAppServerCache.refreshThreadList(fetchThreads, (threads) => { + this.threadSurfaces.applyThreadListSnapshot(threads); + }), + openThreadInCurrentView: (threadId) => this.panels.openThreadInCurrentView(threadId), + openThreadInAvailableView: (threadId) => this.panels.openThreadInAvailableView(threadId), + }; + } + + private settingTabHost(): CodexPanelSettingTabHost { + return { + settings: this.settings, + vaultPath: this.vaultPath, + saveSettings: () => this.saveSettings(), + refreshOpenViews: () => { + this.threadSurfaces.refreshOpenViews(); + }, + refreshSharedThreadListFromOpenSurface: () => { + this.threadSurfaces.refreshSharedThreadListFromOpenSurface(); + }, + cachedModels: () => this.sharedAppServerCache.cachedModels(), + publishModels: (models) => { + this.sharedAppServerCache.applyModelsSnapshot(models); + this.threadSurfaces.publishModels(models); + }, + }; + } } diff --git a/src/settings/tab.ts b/src/settings/tab.ts index bceac461..54e9dab2 100644 --- a/src/settings/tab.ts +++ b/src/settings/tab.ts @@ -48,9 +48,10 @@ export class CodexPanelSettingTab extends PluginSettingTab { constructor( app: App, + owner: Plugin, private readonly plugin: CodexPanelSettingTabHost, ) { - super(app, plugin); + super(app, owner); this.models = plugin.cachedModels(); } @@ -528,7 +529,7 @@ export class CodexPanelSettingTab extends PluginSettingTab { } } -export interface CodexPanelSettingTabHost extends Plugin { +export interface CodexPanelSettingTabHost { settings: CodexPanelSettings; vaultPath: string; saveSettings(): Promise; diff --git a/tests/features/chat/view-connection.test.ts b/tests/features/chat/view-connection.test.ts index f17dbc67..d77bbb1d 100644 --- a/tests/features/chat/view-connection.test.ts +++ b/tests/features/chat/view-connection.test.ts @@ -1191,12 +1191,10 @@ 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(), - refreshOpenViews: vi.fn(), refreshSharedThreadListFromOpenSurface: vi.fn(), refreshThreadsViewLiveState: vi.fn(), applyThreadListSnapshot: vi.fn(), diff --git a/tests/main.test.ts b/tests/main.test.ts index 82e4441a..e30b14f7 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -5,12 +5,28 @@ import { FileSystemAdapter } from "obsidian"; import { VIEW_TYPE_CODEX_PANEL } from "../src/constants"; import { DEFAULT_SETTINGS } from "../src/settings/model"; +import type CodexPanelPlugin from "../src/main"; import type { CodexChatView } from "../src/features/chat/view"; import type { Thread } from "../src/generated/app-server/v2/Thread"; +import type { SharedAppServerCache } from "../src/runtime/shared-app-server-cache"; +import type { WorkspacePanelCoordinator } from "../src/workspace/panel-coordinator"; +import type { ThreadSurfaceCoordinator } from "../src/workspace/thread-surface-coordinator"; import { installObsidianDomShims } from "./support/dom"; installObsidianDomShims(); +function panels(plugin: CodexPanelPlugin): WorkspacePanelCoordinator { + return (plugin as unknown as { panels: WorkspacePanelCoordinator }).panels; +} + +function threadSurfaces(plugin: CodexPanelPlugin): ThreadSurfaceCoordinator { + return (plugin as unknown as { threadSurfaces: ThreadSurfaceCoordinator }).threadSurfaces; +} + +function sharedAppServerCache(plugin: CodexPanelPlugin): SharedAppServerCache { + return (plugin as unknown as { sharedAppServerCache: SharedAppServerCache }).sharedAppServerCache; +} + describe("CodexPanelPlugin boot restored panel loading", () => { beforeEach(() => { vi.useRealTimers(); @@ -56,7 +72,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { restoredLeaf.view = chatView(CodexChatView, restoredLeaf); }); - await plugin.openThreadInAvailableView("thread-1"); + await panels(plugin).openThreadInAvailableView("thread-1"); expect(restoredLeaf.loadIfDeferred).toHaveBeenCalledTimes(1); expect(restoredLeaf.view).toBeInstanceOf(CodexChatView); @@ -84,7 +100,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const openEmptyThread = vi.spyOn(emptyView, "openThread").mockResolvedValue(undefined); const plugin = await pluginWithLeaves([openLeaf, emptyLeaf]); - await plugin.openThreadInAvailableView("thread-1"); + await panels(plugin).openThreadInAvailableView("thread-1"); expect((plugin.app.workspace.revealLeaf as ReturnType).mock.calls).toContainEqual([openLeaf]); expect(openEmptyThread).not.toHaveBeenCalled(); @@ -121,7 +137,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const openEmptyThread = vi.spyOn(emptyView, "openThread").mockResolvedValue(undefined); const plugin = await pluginWithLeaves([busyLeaf, emptyLeaf]); - await plugin.openThreadInAvailableView("thread-1"); + await panels(plugin).openThreadInAvailableView("thread-1"); expect(openEmptyThread).toHaveBeenCalledWith("thread-1"); }); @@ -139,7 +155,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([olderLeaf, currentLeaf]); (plugin.app.workspace.getMostRecentLeaf as ReturnType).mockReturnValue(currentLeaf); - await plugin.openThreadInCurrentView("thread-1"); + await panels(plugin).openThreadInCurrentView("thread-1"); expect(openCurrentThread).toHaveBeenCalledWith("thread-1"); expect(openOlderThread).not.toHaveBeenCalled(); @@ -159,7 +175,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { (plugin.app.workspace.getActiveViewOfType as ReturnType).mockReturnValue(activeView); (plugin.app.workspace.getMostRecentLeaf as ReturnType).mockReturnValue(fallbackLeaf); - await plugin.openThreadInCurrentView("thread-1"); + await panels(plugin).openThreadInCurrentView("thread-1"); expect(openActiveThread).toHaveBeenCalledWith("thread-1"); expect(openFallbackThread).not.toHaveBeenCalled(); @@ -179,7 +195,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([openLeaf, currentLeaf]); (plugin.app.workspace.getMostRecentLeaf as ReturnType).mockReturnValue(currentLeaf); - await plugin.openThreadInCurrentView("thread-1"); + await panels(plugin).openThreadInCurrentView("thread-1"); expect(focusOpenThread).toHaveBeenCalledWith("thread-1"); expect(openCurrentThread).not.toHaveBeenCalled(); @@ -197,7 +213,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { restoredLeaf.view = view; }); - await plugin.openThreadInCurrentView("selected-thread"); + await panels(plugin).openThreadInCurrentView("selected-thread"); expect(restoredLeaf.loadIfDeferred).toHaveBeenCalledOnce(); expect(openThread).toHaveBeenCalledWith("selected-thread"); @@ -217,7 +233,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const focusComposer = vi.spyOn(view, "focusComposer").mockImplementation(() => undefined); const openThread = vi.spyOn(view, "openThread").mockResolvedValue(undefined); - await plugin.openThreadInNewView("thread-1"); + await panels(plugin).openThreadInNewView("thread-1"); expect(connect).not.toHaveBeenCalled(); expect(focusComposer).toHaveBeenCalledOnce(); @@ -239,7 +255,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([firstLeaf, activeLeaf]); (plugin.app.workspace.getActiveViewOfType as ReturnType).mockReturnValue(activeView); - await expect(plugin.activateView()).resolves.toBe(activeView); + await expect(panels(plugin).activateView()).resolves.toBe(activeView); expect((plugin.app.workspace.revealLeaf as ReturnType).mock.calls).toContainEqual([activeLeaf]); expect(connectActive).toHaveBeenCalledOnce(); @@ -267,7 +283,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { if (name === "active-leaf-change") activeLeafHandlers.push(handler); return {}; }); - const refreshLiveState = vi.spyOn(plugin, "refreshThreadsViewLiveState").mockImplementation(() => undefined); + const refreshLiveState = vi.spyOn(threadSurfaces(plugin), "refreshThreadsViewLiveState").mockImplementation(() => undefined); await plugin.onload(); const activeLeafHandler = activeLeafHandlers.at(0); @@ -275,7 +291,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { activeLeafHandler(secondLeaf); expect(refreshLiveState).toHaveBeenCalledOnce(); - expect(plugin.getOpenPanelSnapshots()).toMatchObject([ + expect(panels(plugin).getOpenPanelSnapshots()).toMatchObject([ { viewId: "first", lastFocused: false }, { viewId: "second", lastFocused: true }, ]); @@ -295,7 +311,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([firstLeaf, activeLeaf]); (plugin.app.workspace.getActiveViewOfType as ReturnType).mockReturnValue(activeView); - expect(plugin.getOpenPanelSnapshots()).toMatchObject([ + expect(panels(plugin).getOpenPanelSnapshots()).toMatchObject([ { viewId: "first", lastFocused: false }, { viewId: "active", lastFocused: true }, ]); @@ -316,7 +332,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([recentLeaf, otherLeaf]); (plugin.app.workspace.getMostRecentLeaf as ReturnType).mockReturnValue(recentLeaf); - expect(plugin.getOpenPanelSnapshots()).toMatchObject([ + expect(panels(plugin).getOpenPanelSnapshots()).toMatchObject([ { viewId: "recent", lastFocused: true }, { viewId: "other", lastFocused: false }, ]); @@ -335,7 +351,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const focusComposer = vi.spyOn(view, "focusComposer").mockImplementation(() => undefined); const openThread = vi.spyOn(view, "openThread").mockResolvedValue(undefined); - await plugin.openNewPanel(); + await panels(plugin).openNewPanel(); expect(connect).toHaveBeenCalledOnce(); expect(focusComposer).toHaveBeenCalledOnce(); @@ -344,9 +360,11 @@ describe("CodexPanelPlugin boot restored panel loading", () => { it("refreshes shared thread lists after archive lifecycle notifications", async () => { const plugin = await pluginWithLeaves([]); - const refreshSharedThreadList = vi.spyOn(plugin, "refreshSharedThreadListFromOpenSurface").mockImplementation(() => undefined); + const refreshSharedThreadList = vi + .spyOn(threadSurfaces(plugin), "refreshSharedThreadListFromOpenSurface") + .mockImplementation(() => undefined); - plugin.notifyThreadArchived("thread-1"); + threadSurfaces(plugin).notifyThreadArchived("thread-1"); expect(refreshSharedThreadList).toHaveBeenCalledOnce(); }); @@ -361,15 +379,15 @@ describe("CodexPanelPlugin boot restored panel loading", () => { otherLeaf.view = chatView(CodexChatView, otherLeaf); vi.spyOn(otherLeaf.view as CodexChatView, "openPanelSnapshot").mockReturnValue(panelSnapshot({ threadId: "thread-2" })); const plugin = await pluginWithLeaves([restoredMatchingLeaf, matchingLeaf, otherLeaf]); - vi.spyOn(plugin, "refreshSharedThreadListFromOpenSurface").mockImplementation(() => undefined); + vi.spyOn(threadSurfaces(plugin), "refreshSharedThreadListFromOpenSurface").mockImplementation(() => undefined); - plugin.notifyThreadArchived("thread-1"); + threadSurfaces(plugin).notifyThreadArchived("thread-1"); expect(restoredMatchingLeaf.detach).not.toHaveBeenCalled(); expect(matchingLeaf.detach).not.toHaveBeenCalled(); expect(otherLeaf.detach).not.toHaveBeenCalled(); - plugin.notifyThreadArchived("thread-1", { closeOpenPanels: true }); + threadSurfaces(plugin).notifyThreadArchived("thread-1", { closeOpenPanels: true }); expect(restoredMatchingLeaf.detach).toHaveBeenCalledOnce(); expect(matchingLeaf.detach).toHaveBeenCalledOnce(); @@ -378,9 +396,11 @@ describe("CodexPanelPlugin boot restored panel loading", () => { it("refreshes shared thread lists after rename lifecycle notifications", async () => { const plugin = await pluginWithLeaves([]); - const refreshSharedThreadList = vi.spyOn(plugin, "refreshSharedThreadListFromOpenSurface").mockImplementation(() => undefined); + const refreshSharedThreadList = vi + .spyOn(threadSurfaces(plugin), "refreshSharedThreadListFromOpenSurface") + .mockImplementation(() => undefined); - plugin.notifyThreadRenamed("thread-1", "Renamed thread"); + threadSurfaces(plugin).notifyThreadRenamed("thread-1", "Renamed thread"); expect(refreshSharedThreadList).toHaveBeenCalledOnce(); }); @@ -396,8 +416,8 @@ describe("CodexPanelPlugin boot restored panel loading", () => { ); const secondFetch = vi.fn().mockResolvedValue([thread("second")]); - const first = plugin.refreshThreadList(fetchThreads); - const second = plugin.refreshThreadList(secondFetch); + const first = sharedAppServerCache(plugin).refreshThreadList(fetchThreads); + const second = sharedAppServerCache(plugin).refreshThreadList(secondFetch); expect(fetchThreads).toHaveBeenCalledOnce(); expect(secondFetch).not.toHaveBeenCalled(); @@ -405,16 +425,16 @@ describe("CodexPanelPlugin boot restored panel loading", () => { await expect(first).resolves.toEqual([thread("first")]); await expect(second).resolves.toEqual([thread("first")]); - expect(plugin.cachedThreadList()).toEqual([thread("first")]); + expect(sharedAppServerCache(plugin).cachedThreadList()).toEqual([thread("first")]); }); it("keeps the previous shared thread list when refresh fails", async () => { const plugin = await pluginWithLeaves([]); - await plugin.refreshThreadList(() => Promise.resolve([thread("cached")])); + await sharedAppServerCache(plugin).refreshThreadList(() => Promise.resolve([thread("cached")])); - await expect(plugin.refreshThreadList(() => Promise.reject(new Error("boom")))).rejects.toThrow("boom"); + await expect(sharedAppServerCache(plugin).refreshThreadList(() => Promise.reject(new Error("boom")))).rejects.toThrow("boom"); - expect(plugin.cachedThreadList()).toEqual([thread("cached")]); + expect(sharedAppServerCache(plugin).cachedThreadList()).toEqual([thread("cached")]); }); it("refreshes shared thread lists from a connected chat panel", async () => { @@ -433,7 +453,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves([disconnectedLeaf, connectedLeaf]); - plugin.refreshSharedThreadListFromOpenSurface(); + threadSurfaces(plugin).refreshSharedThreadListFromOpenSurface(); expect(disconnectedRefresh).not.toHaveBeenCalled(); expect(connectedRefresh).toHaveBeenCalledOnce(); @@ -462,7 +482,7 @@ describe("CodexPanelPlugin boot restored panel loading", () => { const plugin = await pluginWithLeaves(leaves); sourceLeaf.detach(); - plugin.notifyThreadArchived("thread-1"); + threadSurfaces(plugin).notifyThreadArchived("thread-1"); expect(sourceRefresh).not.toHaveBeenCalled(); expect(remainingRefresh).toHaveBeenCalledOnce(); @@ -535,12 +555,10 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) { settings: { ...DEFAULT_SETTINGS, codexPath: "codex", sendShortcut: "enter" }, vaultPath: "/vault", openThreadInNewView: vi.fn(), - openThreadInAvailableView: vi.fn(), focusThreadInOpenView: vi.fn(), openTurnDiff: vi.fn(), notifyThreadArchived: vi.fn(), notifyThreadRenamed: vi.fn(), - refreshOpenViews: vi.fn(), refreshSharedThreadListFromOpenSurface: vi.fn(), refreshThreadsViewLiveState: vi.fn(), applyThreadListSnapshot: vi.fn(), diff --git a/tests/settings/settings-tab.test.ts b/tests/settings/settings-tab.test.ts index 2e615e7c..a5b63a1c 100644 --- a/tests/settings/settings-tab.test.ts +++ b/tests/settings/settings-tab.test.ts @@ -368,6 +368,7 @@ function newSettingsTab( } = {}, ): CodexPanelSettingTab { return new CodexPanelSettingTab( + {} as never, {} as never, { settings: {