diff --git a/src/features/chat/panel/cached-app-server-state.ts b/src/features/chat/panel/cached-app-server-state.ts index e00188f3..76429ceb 100644 --- a/src/features/chat/panel/cached-app-server-state.ts +++ b/src/features/chat/panel/cached-app-server-state.ts @@ -1,14 +1,20 @@ +import type { SharedAppServerMetadata } from "../../../app-server/shared-cache-state"; +import type { Thread } from "../../../domain/threads/model"; import type { ChatServerMetadataActions } from "../server-actions/metadata-actions"; import type { ChatServerThreadActions } from "../server-actions/thread-actions"; -import type { ChatPanelContext } from "./context"; + +export interface CachedSharedAppServerStateSource { + cachedThreadList: () => readonly Thread[] | null; + cachedAppServerMetadata: () => SharedAppServerMetadata | null; +} export function applyCachedSharedAppServerState( - context: ChatPanelContext, + source: CachedSharedAppServerStateSource, serverThreads: ChatServerThreadActions, serverMetadata: ChatServerMetadataActions, ): void { - const threads = context.plugin.cachedThreadList(); + const threads = source.cachedThreadList(); if (threads) serverThreads.applyThreadList(threads); - const metadata = context.plugin.cachedAppServerMetadata(); + const metadata = source.cachedAppServerMetadata(); if (metadata) serverMetadata.applyAppServerMetadata(metadata); } diff --git a/src/features/chat/panel/composition.ts b/src/features/chat/panel/composition.ts index 7bb1520d..bc40ecf8 100644 --- a/src/features/chat/panel/composition.ts +++ b/src/features/chat/panel/composition.ts @@ -10,7 +10,6 @@ import type { ChatThreadActions } from "../threads/thread-actions"; import type { ThreadHistoryController } from "../threads/thread-history-controller"; import type { ThreadRenameController } from "../threads/thread-rename-controller"; import type { ToolbarPanelController } from "./toolbar-controller"; -import type { AppServerWarmupActions } from "../session/app-server-warmup-controller"; import type { ChatConnectionController } from "../session/connection-controller"; import type { ChatReconnectActions } from "../session/reconnect-actions"; import type { PendingRequestController } from "../requests/pending-request-controller"; @@ -20,11 +19,9 @@ import type { RestoredThreadController } from "../threads/restored-thread-contro import type { ThreadIdentityActions } from "../threads/thread-identity-actions"; import type { ThreadResumeController } from "../threads/thread-resume-controller"; import type { ThreadSelectionActions } from "../threads/thread-selection-controller"; -import type { ChatViewOpenCloseActions } from "./open-close-actions"; import type { ChatViewRenderController } from "./view-render-controller"; -import type { ChatViewStateActions } from "./view-state-controller"; import type { ChatMessageRenderer } from "../ui/message-stream"; -import type { ChatPanelContext } from "./context"; +import type { ChatControllerCompositionPorts } from "./controller-ports"; import { createChatServerActionControllers, createChatConnectionControllers, @@ -40,7 +37,7 @@ export interface ChatViewControllers { manager: ConnectionManager; controller: ChatConnectionController; reconnect: ChatReconnectActions; - warmup: AppServerWarmupActions; + scheduleWarmup: () => void; }; inbound: { controller: ChatInboundController; @@ -76,12 +73,13 @@ export interface ChatViewControllers { render: { controller: ChatViewRenderController; messages: ChatMessageRenderer; - openClose: ChatViewOpenCloseActions; - viewState: ChatViewStateActions; + openView: () => void; + closeView: () => void; + applyViewState: (state: unknown) => void; }; } -export function createChatViewControllers(ports: ChatPanelContext): ChatViewControllers { +export function createChatViewControllers(ports: ChatControllerCompositionPorts): ChatViewControllers { const connection = new ConnectionManager(() => ports.plugin.settings.codexPath, ports.plugin.vaultPath); const { renderController } = createViewRenderControllerGroup(ports, { connection }); const runtimeSettings = createChatRuntimeSettingsActions({ @@ -94,7 +92,7 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont const { history, threadActions, goals, restoredThread, threadResume, threadIdentity, threadRename } = createThreadControllerGroup(ports, { connection, }); - const { toolbarPanels, viewStateController } = createPanelUiControllerGroup(ports, { + const { toolbarPanels, applyViewState } = createPanelUiControllerGroup(ports, { threadActions, }); const { threadSelection } = createThreadSelectionControllerGroup(ports, { @@ -152,7 +150,7 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont goals, history, }); - const { appServerWarmup, openCloseController } = createConnectionLifecycleControllerGroup(ports, { + const { scheduleAppServerWarmup, openView, closeView } = createConnectionLifecycleControllerGroup(ports, { connection, composerController, messageRenderer, @@ -165,7 +163,7 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont manager: connection, controller: connectionController, reconnect: reconnectActions, - warmup: appServerWarmup, + scheduleWarmup: scheduleAppServerWarmup, }, inbound: { controller, @@ -201,8 +199,9 @@ export function createChatViewControllers(ports: ChatPanelContext): ChatViewCont render: { controller: renderController, messages: messageRenderer, - openClose: openCloseController, - viewState: viewStateController, + openView, + closeView, + applyViewState, }, }; } diff --git a/src/features/chat/panel/context.ts b/src/features/chat/panel/controller-ports.ts similarity index 98% rename from src/features/chat/panel/context.ts rename to src/features/chat/panel/controller-ports.ts index 8653e68d..52d1ac6a 100644 --- a/src/features/chat/panel/context.ts +++ b/src/features/chat/panel/controller-ports.ts @@ -16,7 +16,7 @@ import type { } from "./lifecycle"; import type { ComposerMetaViewModel } from "./model"; -export interface ChatPanelContext { +export interface ChatControllerCompositionPorts { obsidian: ChatPanelObsidianContext; plugin: CodexChatHost; state: ChatPanelStateContext; diff --git a/src/features/chat/panel/ui-composition.ts b/src/features/chat/panel/ui-composition.ts index 0f53bf30..7ea8ed0c 100644 --- a/src/features/chat/panel/ui-composition.ts +++ b/src/features/chat/panel/ui-composition.ts @@ -3,18 +3,20 @@ import type { ChatServerMetadataActions } from "../server-actions/metadata-actio import type { ChatServerThreadActions } from "../server-actions/thread-actions"; import type { ChatComposerController } from "../composer/controller"; import type { ChatThreadActions } from "../threads/thread-actions"; -import { createAppServerWarmupActions } from "../session/app-server-warmup-controller"; -import { createChatViewOpenCloseActions } from "./open-close-actions"; +import { scheduleAppServerWarmup } from "../session/app-server-warmup-controller"; +import { closeChatView, openChatView, type ChatViewLifecycleHost } from "./view-lifecycle"; import { ToolbarPanelController } from "./toolbar-controller"; import { ChatViewRenderController } from "./view-render-controller"; -import { createChatViewStateActions } from "./view-state-controller"; +import { applyChatViewState } from "./view-state-controller"; import type { ChatMessageRenderer } from "../ui/message-stream"; import { applyCachedSharedAppServerState } from "./cached-app-server-state"; -import type { ChatPanelContext } from "./context"; +import type { ChatControllerCompositionPorts } from "./controller-ports"; import { createChatShellRenderPort } from "./shell-render"; +type ViewRenderControllerGroupPorts = Pick; + export function createViewRenderControllerGroup( - context: ChatPanelContext, + context: ViewRenderControllerGroupPorts, refs: { connection: ConnectionManager; }, @@ -42,8 +44,13 @@ export function createViewRenderControllerGroup( }; } +type ConnectionLifecycleControllerGroupPorts = Pick< + ChatControllerCompositionPorts, + "client" | "lifecycle" | "liveState" | "obsidian" | "plugin" | "render" +>; + export function createConnectionLifecycleControllerGroup( - context: ChatPanelContext, + context: ConnectionLifecycleControllerGroupPorts, refs: { connection: ConnectionManager; composerController: ChatComposerController; @@ -52,76 +59,93 @@ export function createConnectionLifecycleControllerGroup( serverMetadata: ChatServerMetadataActions; }, ) { - const { obsidian, lifecycle, render, liveState, client } = context; + const { obsidian, plugin, lifecycle, render, liveState, client } = context; const { deferredTasks } = lifecycle; + const warmupHost = { + deferredTasks, + opened: lifecycle.getOpened, + closing: lifecycle.getClosing, + connected: () => refs.connection.isConnected(), + ensureConnected: client.ensureConnected, + }; + + const viewLifecycleHost: ChatViewLifecycleHost = { + setOpened: lifecycle.setOpened, + setClosing: lifecycle.setClosing, + registerEvent: obsidian.registerEvent, + registerComposerNoteIndexInvalidation: (register) => { + refs.composerController.registerNoteIndexInvalidation(register); + }, + registerPointerDown: obsidian.registerPointerDown, + registerActiveLeafChange: obsidian.registerActiveLeafChange, + handleActiveLeafChange: obsidian.handleActiveLeafChange, + applyCachedSharedAppServerState: () => { + applyCachedSharedAppServerState(plugin, refs.serverThreads, refs.serverMetadata); + }, + render: render.now, + scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup, + scheduleDeferredRestoredThreadHydration: lifecycle.scheduleDeferredRestoredThreadHydration, + closeToolbarPanelOnOutsidePointer: render.closeToolbarPanelOnOutsidePointer, + invalidateConnectionWork: lifecycle.invalidateConnectionWork, + invalidateResumeWork: lifecycle.invalidateResumeWork, + clearDeferredTasks: () => { + deferredTasks.clearAll(); + }, + panelRoot: render.panelRoot, + disposeMessages: () => { + refs.messageRenderer.dispose(); + }, + disposeComposer: () => { + refs.composerController.dispose(); + }, + disconnect: () => { + refs.connection.disconnect(); + }, + clearClient: client.clear, + refreshLiveState: liveState.refresh, + deferRefreshLiveState: liveState.deferRefresh, + }; + return { - appServerWarmup: createAppServerWarmupActions({ - deferredTasks, - opened: lifecycle.getOpened, - closing: lifecycle.getClosing, - connected: () => refs.connection.isConnected(), - ensureConnected: client.ensureConnected, - }), - openCloseController: createChatViewOpenCloseActions({ - setOpened: lifecycle.setOpened, - setClosing: lifecycle.setClosing, - registerEvent: obsidian.registerEvent, - registerComposerNoteIndexInvalidation: (register) => { - refs.composerController.registerNoteIndexInvalidation(register); - }, - registerPointerDown: obsidian.registerPointerDown, - registerActiveLeafChange: obsidian.registerActiveLeafChange, - handleActiveLeafChange: obsidian.handleActiveLeafChange, - applyCachedSharedAppServerState: () => { - applyCachedSharedAppServerState(context, refs.serverThreads, refs.serverMetadata); - }, - render: render.now, - scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup, - scheduleDeferredRestoredThreadHydration: lifecycle.scheduleDeferredRestoredThreadHydration, - closeToolbarPanelOnOutsidePointer: render.closeToolbarPanelOnOutsidePointer, - invalidateConnectionWork: lifecycle.invalidateConnectionWork, - invalidateResumeWork: lifecycle.invalidateResumeWork, - clearDeferredTasks: () => { - deferredTasks.clearAll(); - }, - panelRoot: render.panelRoot, - disposeMessages: () => { - refs.messageRenderer.dispose(); - }, - disposeComposer: () => { - refs.composerController.dispose(); - }, - disconnect: () => { - refs.connection.disconnect(); - }, - clearClient: client.clear, - refreshLiveState: liveState.refresh, - deferRefreshLiveState: liveState.deferRefresh, - }), + scheduleAppServerWarmup: () => { + scheduleAppServerWarmup(warmupHost); + }, + openView: () => { + openChatView(viewLifecycleHost); + }, + closeView: () => { + closeChatView(viewLifecycleHost); + }, }; } +type PanelUiControllerGroupPorts = Pick; + export function createPanelUiControllerGroup( - context: ChatPanelContext, + context: PanelUiControllerGroupPorts, refs: { threadActions: ChatThreadActions; }, ) { const { lifecycle, render, thread } = context; - const toolbarPanels = new ToolbarPanelController({ - stateStore: context.state.stateStore, - threadActions: refs.threadActions, - scheduleRender: render.schedule, - }); - const viewStateController = createChatViewStateActions({ + const viewStateHost = { invalidateResumeWork: lifecycle.invalidateResumeWork, clearRestoredThreadLifecycle: thread.clearRestoredLifecycle, clearDeferredRestoredThreadHydration: lifecycle.clearDeferredRestoredThreadHydration, scheduleDeferredAppServerWarmup: lifecycle.scheduleDeferredAppServerWarmup, restoreThreadPlaceholder: thread.restorePlaceholder, - }); + }; - return { toolbarPanels, viewStateController }; + const toolbarPanels = new ToolbarPanelController({ + stateStore: context.state.stateStore, + threadActions: refs.threadActions, + scheduleRender: render.schedule, + }); + const applyViewState = (state: unknown) => { + applyChatViewState(viewStateHost, state); + }; + + return { toolbarPanels, applyViewState }; } diff --git a/src/features/chat/panel/open-close-actions.ts b/src/features/chat/panel/view-lifecycle.ts similarity index 79% rename from src/features/chat/panel/open-close-actions.ts rename to src/features/chat/panel/view-lifecycle.ts index aa54ce8a..e4188a2b 100644 --- a/src/features/chat/panel/open-close-actions.ts +++ b/src/features/chat/panel/view-lifecycle.ts @@ -2,7 +2,7 @@ import type { EventRef, WorkspaceLeaf } from "obsidian"; import { unmountChatPanelShell } from "../ui/shell"; -export interface ChatViewOpenCloseActionsHost { +export interface ChatViewLifecycleHost { setOpened: (opened: boolean) => void; setClosing: (closing: boolean) => void; registerEvent: (eventRef: EventRef) => void; @@ -27,23 +27,7 @@ export interface ChatViewOpenCloseActionsHost { deferRefreshLiveState: () => void; } -export interface ChatViewOpenCloseActions { - open: () => void; - close: () => void; -} - -export function createChatViewOpenCloseActions(host: ChatViewOpenCloseActionsHost): ChatViewOpenCloseActions { - return { - open: () => { - openChatView(host); - }, - close: () => { - closeChatView(host); - }, - }; -} - -function openChatView(host: ChatViewOpenCloseActionsHost): void { +export function openChatView(host: ChatViewLifecycleHost): void { host.setOpened(true); host.setClosing(false); host.registerComposerNoteIndexInvalidation((eventRef) => { @@ -59,7 +43,7 @@ function openChatView(host: ChatViewOpenCloseActionsHost): void { host.scheduleDeferredRestoredThreadHydration(); } -function closeChatView(host: ChatViewOpenCloseActionsHost): void { +export function closeChatView(host: ChatViewLifecycleHost): void { host.setOpened(false); host.setClosing(true); host.invalidateConnectionWork(); diff --git a/src/features/chat/panel/view-state-controller.ts b/src/features/chat/panel/view-state-controller.ts index 4f60b33b..a374d328 100644 --- a/src/features/chat/panel/view-state-controller.ts +++ b/src/features/chat/panel/view-state-controller.ts @@ -9,23 +9,15 @@ export interface ChatViewStateControllerHost { restoreThreadPlaceholder: (restoredThread: RestoredThreadState) => void; } -export interface ChatViewStateActions { - applyState(state: unknown): void; -} +export function applyChatViewState(host: ChatViewStateControllerHost, state: unknown): void { + const restoredThread = parseRestoredThreadState(state); + if (!restoredThread) { + host.invalidateResumeWork(); + host.clearRestoredThreadLifecycle(); + host.clearDeferredRestoredThreadHydration(); + host.scheduleDeferredAppServerWarmup(); + return; + } -export function createChatViewStateActions(host: ChatViewStateControllerHost): ChatViewStateActions { - return { - applyState(state) { - const restoredThread = parseRestoredThreadState(state); - if (!restoredThread) { - host.invalidateResumeWork(); - host.clearRestoredThreadLifecycle(); - host.clearDeferredRestoredThreadHydration(); - host.scheduleDeferredAppServerWarmup(); - return; - } - - host.restoreThreadPlaceholder(restoredThread); - }, - }; + host.restoreThreadPlaceholder(restoredThread); } diff --git a/src/features/chat/session/app-server-warmup-controller.ts b/src/features/chat/session/app-server-warmup-controller.ts index 44e1cf28..b9151a26 100644 --- a/src/features/chat/session/app-server-warmup-controller.ts +++ b/src/features/chat/session/app-server-warmup-controller.ts @@ -8,20 +8,13 @@ export interface AppServerWarmupControllerHost { ensureConnected: () => Promise; } -export interface AppServerWarmupActions { - schedule(): void; -} - -export function createAppServerWarmupActions(host: AppServerWarmupControllerHost): AppServerWarmupActions { +export function scheduleAppServerWarmup(host: AppServerWarmupControllerHost): void { const shouldWarmup = (): boolean => host.opened() && !host.connected(); - return { - schedule() { - if (!shouldWarmup()) return; - host.deferredTasks.scheduleAppServerWarmup(() => { - if (!shouldWarmup() || host.closing()) return; - void host.ensureConnected(); - }); - }, - }; + if (!shouldWarmup()) return; + + host.deferredTasks.scheduleAppServerWarmup(() => { + if (!shouldWarmup() || host.closing()) return; + void host.ensureConnected(); + }); } diff --git a/src/features/chat/session/composition.ts b/src/features/chat/session/composition.ts index a5141f62..98615c24 100644 --- a/src/features/chat/session/composition.ts +++ b/src/features/chat/session/composition.ts @@ -10,10 +10,12 @@ import type { ServerRequestActions } from "../requests/server-request-actions"; import type { ChatThreadGoalActions } from "../threads/thread-goal-actions"; import type { ThreadRenameController } from "../threads/thread-rename-controller"; import { ChatInboundController } from "../inbound/controller"; -import type { ChatPanelContext } from "../panel/context"; +import type { ChatControllerCompositionPorts } from "../panel/controller-ports"; + +type ChatServerActionControllerPorts = Pick; export function createChatServerActionControllers( - context: ChatPanelContext, + context: ChatServerActionControllerPorts, refs: { connection: ConnectionManager; goals: ChatThreadGoalActions; @@ -53,8 +55,10 @@ export function createChatServerActionControllers( return { serverThreads, serverMetadata, serverDiagnostics }; } +type ChatInboundControllerPorts = Pick; + export function createChatInboundController( - context: ChatPanelContext, + context: ChatInboundControllerPorts, refs: { serverMetadata: ChatServerMetadataActions; serverDiagnostics: ChatServerDiagnosticsActions; @@ -87,8 +91,13 @@ export function createChatInboundController( }); } +type ChatConnectionControllerPorts = Pick< + ChatControllerCompositionPorts, + "client" | "lifecycle" | "liveState" | "plugin" | "render" | "state" | "status" | "thread" +>; + export function createChatConnectionControllers( - context: ChatPanelContext, + context: ChatConnectionControllerPorts, refs: { connection: ConnectionManager; serverMetadata: ChatServerMetadataActions; @@ -130,8 +139,13 @@ export function createChatConnectionControllers( }; } +type ChatReconnectControllerGroupPorts = Pick< + ChatControllerCompositionPorts, + "client" | "lifecycle" | "render" | "state" | "status" | "thread" +>; + export function createChatReconnectControllerGroup( - context: ChatPanelContext, + context: ChatReconnectControllerGroupPorts, refs: { connection: ConnectionManager; }, diff --git a/src/features/chat/threads/composition.ts b/src/features/chat/threads/composition.ts index 17f6647a..7d1ed93b 100644 --- a/src/features/chat/threads/composition.ts +++ b/src/features/chat/threads/composition.ts @@ -9,10 +9,15 @@ import { ThreadResumeController } from "./thread-resume-controller"; import { createThreadSelectionActions } from "./thread-selection-controller"; import { RestoredThreadController } from "./restored-thread-controller"; import type { ToolbarPanelController } from "../panel/toolbar-controller"; -import type { ChatPanelContext } from "../panel/context"; +import type { ChatControllerCompositionPorts } from "../panel/controller-ports"; + +type ThreadControllerGroupPorts = Pick< + ChatControllerCompositionPorts, + "client" | "composer" | "lifecycle" | "liveState" | "obsidian" | "plugin" | "render" | "scroll" | "state" | "status" | "thread" +>; export function createThreadControllerGroup( - context: ChatPanelContext, + context: ThreadControllerGroupPorts, refs: { connection: ConnectionManager; }, @@ -132,8 +137,10 @@ export function createThreadControllerGroup( }; } +type ThreadSelectionControllerGroupPorts = Pick; + export function createThreadSelectionControllerGroup( - context: ChatPanelContext, + context: ThreadSelectionControllerGroupPorts, refs: { toolbarPanels: ToolbarPanelController; }, diff --git a/src/features/chat/turns/composition.ts b/src/features/chat/turns/composition.ts index 142b88ec..bf9f9085 100644 --- a/src/features/chat/turns/composition.ts +++ b/src/features/chat/turns/composition.ts @@ -15,10 +15,15 @@ import type { ThreadRenameController } from "../threads/thread-rename-controller import type { ChatInboundController } from "../inbound/controller"; import { currentModel } from "../runtime/effective-settings"; import { ChatMessageRenderer } from "../ui/message-stream"; -import type { ChatPanelContext } from "../panel/context"; +import type { ChatControllerCompositionPorts } from "../panel/controller-ports"; + +type ConversationSurfaceControllerGroupPorts = Pick< + ChatControllerCompositionPorts, + "client" | "lifecycle" | "liveState" | "obsidian" | "plugin" | "render" | "runtime" | "scroll" | "state" | "status" | "thread" +>; export function createConversationSurfaceControllerGroup( - context: ChatPanelContext, + context: ConversationSurfaceControllerGroupPorts, refs: { controller: ChatInboundController; serverThreads: ChatServerThreadActions; diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index 071e7f45..dc536920 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -30,7 +30,7 @@ import { type ChatViewRenderScheduleOptions, } from "./panel/lifecycle"; import { ChatMessageScrollIntentController } from "./panel/message-scroll-intent-controller"; -import type { ChatPanelContext } from "./panel/context"; +import type { ChatControllerCompositionPorts } from "./panel/controller-ports"; import { createChatViewControllers, type ChatViewControllers } from "./panel/composition"; import { activeComposerThreadName, composerMetaViewModel, composerPlaceholder, renderComposerSlot } from "./panel/slots/composer"; import { renderGoalSlot } from "./panel/slots/goal"; @@ -65,7 +65,7 @@ export class CodexChatView extends ItemView { this.slotPorts = this.createSlotRendererPorts(); } - private createControllerPorts(): ChatPanelContext { + private createControllerPorts(): ChatControllerCompositionPorts { // Some callbacks are late-bound to controllers assigned immediately after this object is created. // Controller constructors must not invoke those callbacks synchronously during composition. return { @@ -398,7 +398,7 @@ export class CodexChatView extends ItemView { override async setState(state: unknown, result: ViewStateResult): Promise { await super.setState(state, result); - this.controllers.render.viewState.applyState(state); + this.controllers.render.applyViewState(state); } refreshSettings(): void { @@ -464,11 +464,11 @@ export class CodexChatView extends ItemView { } override async onOpen(): Promise { - this.controllers.render.openClose.open(); + this.controllers.render.openView(); } override async onClose(): Promise { - this.controllers.render.openClose.close(); + this.controllers.render.closeView(); } setComposerText(text: string): void { @@ -549,7 +549,7 @@ export class CodexChatView extends ItemView { } private scheduleDeferredAppServerWarmup(): void { - this.controllers.connection.warmup.schedule(); + this.controllers.connection.scheduleWarmup(); } private activeThreadTitle(): string | null { diff --git a/tests/features/chat/panel/open-close-actions.test.ts b/tests/features/chat/panel/view-lifecycle.test.ts similarity index 82% rename from tests/features/chat/panel/open-close-actions.test.ts rename to tests/features/chat/panel/view-lifecycle.test.ts index 30f4397f..1fff900b 100644 --- a/tests/features/chat/panel/open-close-actions.test.ts +++ b/tests/features/chat/panel/view-lifecycle.test.ts @@ -3,16 +3,16 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { EventRef } from "obsidian"; -import { createChatViewOpenCloseActions, type ChatViewOpenCloseActionsHost } from "../../../../src/features/chat/panel/open-close-actions"; +import { closeChatView, openChatView, type ChatViewLifecycleHost } from "../../../../src/features/chat/panel/view-lifecycle"; import { unmountChatPanelShell } from "../../../../src/features/chat/ui/shell"; vi.mock("../../../../src/features/chat/ui/shell", () => ({ unmountChatPanelShell: vi.fn(), })); -function createHost(overrides: Partial = {}) { +function createHost(overrides: Partial = {}) { const root = document.createElement("div"); - const host: ChatViewOpenCloseActionsHost = { + const host: ChatViewLifecycleHost = { setOpened: vi.fn(), setClosing: vi.fn(), registerEvent: vi.fn(), @@ -39,18 +39,18 @@ function createHost(overrides: Partial = {}) { deferRefreshLiveState: vi.fn(), ...overrides, }; - return { controller: createChatViewOpenCloseActions(host), host, root }; + return { host, root }; } -describe("createChatViewOpenCloseActions", () => { +describe("chat view lifecycle", () => { beforeEach(() => { vi.mocked(unmountChatPanelShell).mockClear(); }); it("registers open events and schedules startup work", () => { - const { controller, host } = createHost(); + const { host } = createHost(); - controller.open(); + openChatView(host); expect(host.setOpened).toHaveBeenCalledWith(true); expect(host.setClosing).toHaveBeenCalledWith(false); @@ -64,9 +64,9 @@ describe("createChatViewOpenCloseActions", () => { }); it("disposes mounted resources and refreshes live state on close", () => { - const { controller, host, root } = createHost(); + const { host, root } = createHost(); - controller.close(); + closeChatView(host); expect(host.setOpened).toHaveBeenCalledWith(false); expect(host.setClosing).toHaveBeenCalledWith(true); diff --git a/tests/features/chat/panel/view-state-controller.test.ts b/tests/features/chat/panel/view-state-controller.test.ts index 075b7249..98a1e3fc 100644 --- a/tests/features/chat/panel/view-state-controller.test.ts +++ b/tests/features/chat/panel/view-state-controller.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { createChatViewStateActions, type ChatViewStateControllerHost } from "../../../../src/features/chat/panel/view-state-controller"; +import { applyChatViewState, type ChatViewStateControllerHost } from "../../../../src/features/chat/panel/view-state-controller"; function createController(overrides: Partial = {}) { const host: ChatViewStateControllerHost = { @@ -11,14 +11,14 @@ function createController(overrides: Partial = {}) restoreThreadPlaceholder: vi.fn(), ...overrides, }; - return { controller: createChatViewStateActions(host), host }; + return { host }; } describe("ChatViewStateController", () => { it("restores a thread placeholder from persisted view state", () => { - const { controller, host } = createController(); + const { host } = createController(); - controller.applyState({ threadId: "thread", threadTitle: "Title" }); + applyChatViewState(host, { threadId: "thread", threadTitle: "Title" }); expect(host.restoreThreadPlaceholder).toHaveBeenCalledWith({ threadId: "thread", @@ -30,9 +30,9 @@ describe("ChatViewStateController", () => { }); it("clears restored lifecycle and schedules warmup when no thread is restored", () => { - const { controller, host } = createController(); + const { host } = createController(); - controller.applyState({ version: 1 }); + applyChatViewState(host, { version: 1 }); expect(host.invalidateResumeWork).toHaveBeenCalledOnce(); expect(host.clearRestoredThreadLifecycle).toHaveBeenCalledOnce(); diff --git a/tests/features/chat/session/app-server-warmup-controller.test.ts b/tests/features/chat/session/app-server-warmup-controller.test.ts index 9fd585a3..d9e2f794 100644 --- a/tests/features/chat/session/app-server-warmup-controller.test.ts +++ b/tests/features/chat/session/app-server-warmup-controller.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { createAppServerWarmupActions } from "../../../../src/features/chat/session/app-server-warmup-controller"; +import { scheduleAppServerWarmup } from "../../../../src/features/chat/session/app-server-warmup-controller"; import { ChatViewDeferredTasks } from "../../../../src/features/chat/panel/lifecycle"; function createController({ @@ -11,14 +11,14 @@ function createController({ connected = false, }: { opened?: boolean; closing?: boolean; connected?: boolean } = {}) { const ensureConnected = vi.fn().mockResolvedValue(undefined); - const controller = createAppServerWarmupActions({ + const host = { deferredTasks: new ChatViewDeferredTasks(() => window), opened: () => opened, closing: () => closing, connected: () => connected, ensureConnected, - }); - return { controller, ensureConnected }; + }; + return { host, ensureConnected }; } describe("AppServerWarmupController", () => { @@ -27,9 +27,9 @@ describe("AppServerWarmupController", () => { }); it("connects on the next tick when the opened view is disconnected", async () => { - const { controller, ensureConnected } = createController(); + const { host, ensureConnected } = createController(); - controller.schedule(); + scheduleAppServerWarmup(host); await vi.advanceTimersByTimeAsync(0); expect(ensureConnected).toHaveBeenCalledOnce(); @@ -39,8 +39,8 @@ describe("AppServerWarmupController", () => { const closed = createController({ opened: false }); const connected = createController({ connected: true }); - closed.controller.schedule(); - connected.controller.schedule(); + scheduleAppServerWarmup(closed.host); + scheduleAppServerWarmup(connected.host); await vi.advanceTimersByTimeAsync(0); @@ -49,9 +49,9 @@ describe("AppServerWarmupController", () => { }); it("skips a scheduled warmup if the view is closing", async () => { - const { controller, ensureConnected } = createController({ closing: true }); + const { host, ensureConnected } = createController({ closing: true }); - controller.schedule(); + scheduleAppServerWarmup(host); await vi.advanceTimersByTimeAsync(0); expect(ensureConnected).not.toHaveBeenCalled();