From 0de3db212e151e0c24ac2392cb963c77cf2a6550 Mon Sep 17 00:00:00 2001 From: murashit Date: Tue, 16 Jun 2026 11:26:24 +0900 Subject: [PATCH] Organize ChatPanelSession method order --- src/features/chat/host/session.ts | 638 +++++++++++++++--------------- 1 file changed, 319 insertions(+), 319 deletions(-) diff --git a/src/features/chat/host/session.ts b/src/features/chat/host/session.ts index a9457cba..8c66c2f9 100644 --- a/src/features/chat/host/session.ts +++ b/src/features/chat/host/session.ts @@ -185,6 +185,325 @@ export class ChatPanelSession implements ChatSurfaceHandle { this.parts = this.createSessionParts(); } + displayTitle(): string { + return codexPanelDisplayTitle(this.state.activeThread.id, this.state.threadList.listedThreads, this.restoredThreadTitle()); + } + + persistedState(): Record { + const threadId = this.state.activeThread.id; + if (!threadId) return { version: 1 }; + + const threadTitle = this.restoredThreadTitle() ?? this.activeThreadTitle(); + return { + version: 1, + threadId, + ...(threadTitle ? { threadTitle } : {}), + }; + } + + applyViewState(state: unknown): void { + const restoredThread = parseRestoredThreadState(state); + if (restoredThread) { + this.parts.thread.restoration.restore(restoredThread); + this.scheduleRestoredThreadHydration(); + return; + } + + this.invalidateResumeWork(); + this.parts.thread.restoration.clear(); + this.parts.thread.restoration.clearHydration(); + this.scheduleWarmup(); + } + + refreshSettings(): void { + const nextContext = this.currentAppServerContext(); + if (!appServerQueryContextRawEquals(this.observedAppServerContext, nextContext)) { + this.observedAppServerContext = nextContext; + this.connectionWork.invalidate(); + this.invalidateResumeWork(); + this.parts.connection.manager.resetConnection(); + this.applyCachedAppServerState(); + } + this.mountOrRepairShell(); + } + + refreshSharedThreadList(): Promise { + return this.loadSharedThreadList(); + } + + async runWithAppServerClient(operation: (client: AppServerClient) => Promise): Promise { + const client = this.parts.connection.manager.currentClient(); + if (!client) throw new Error("Codex app-server is not connected."); + const result = await operation(client); + if (this.parts.connection.manager.currentClient() !== client) { + throw new Error("Codex app-server connection changed while loading shared data."); + } + return result; + } + + openPanelSnapshot(): ChatPanelSnapshot { + return { + viewId: this.environment.obsidian.viewId, + threadId: this.closing ? null : this.state.activeThread.id, + turnLifecycle: openPanelTurnLifecycle(this.state.turn.lifecycle), + pendingApprovals: this.state.requests.approvals.length, + pendingUserInputs: this.state.requests.pendingUserInputs.length, + hasComposerDraft: this.state.composer.draft.trim().length > 0, + connected: this.parts.connection.manager.isConnected(), + }; + } + + async openThread(threadId: string): Promise { + await this.parts.thread.resume.resumeThread(threadId); + this.focusComposer(); + } + + async focusThread(threadId: string | null = null): Promise { + if (threadId && this.parts.thread.restoration.isPending(threadId)) { + await this.ensureRestoredThreadLoaded(); + } + this.focusComposer(); + } + + focusComposer(): void { + this.parts.composer.controller.focus(); + } + + applyThreadArchived(threadId: string): void { + this.parts.thread.identity.applyThreadArchived(threadId); + } + + applyThreadRenamed(threadId: string, name: string | null): void { + this.parts.thread.identity.applyThreadRenamed(threadId, name); + } + + open(): void { + this.opened = true; + this.closing = false; + this.parts.composer.controller.registerNoteIndexInvalidation((eventRef) => { + this.environment.obsidian.registerEvent(eventRef); + }); + this.environment.obsidian.registerPointerDown((event) => { + this.closeToolbarPanelOnOutsidePointer(event); + }); + this.subscribeAppServerState(); + this.mountOrRepairShell(); + this.scheduleWarmup(); + this.scheduleRestoredThreadHydration(); + } + + close(): void { + this.opened = false; + this.closing = true; + this.connectionWork.invalidate(); + this.invalidateResumeWork(); + this.deferredTasks.clearAll(); + this.unsubscribeAppServerState(); + const panelRoot = this.environment.view.panelRoot(); + this.parts.render.messageStreamPresenter.dispose(); + this.parts.composer.controller.dispose(); + unmountChatPanelShell(panelRoot); + this.parts.connection.manager.disconnect(); + this.refreshLiveState(); + this.deferLiveStateRefresh(); + } + + setComposerText(text: string): void { + this.parts.composer.controller.setDraft(text, { focus: true }); + } + + async connect(): Promise { + await this.parts.connection.controller.ensureConnected(); + } + + async startNewThread(): Promise { + if (chatTurnBusy(this.state)) return; + + this.parts.thread.identity.clearActiveThreadContext(); + this.dispatch({ type: "ui/panel-set", panel: null }); + this.dispatch({ type: "connection/status-set", statusText: "New chat." }); + this.focusComposer(); + } + + private get state(): ChatState { + return this.stateStore.getState(); + } + + private dispatch(action: ChatAction): void { + this.stateStore.dispatch(action); + } + + private receiveObservedThreads(threads: readonly Thread[]): void { + this.parts.serverActions.threads.applyThreadList(threads); + this.refreshTabHeader(); + } + + private receiveObservedThreadResult(result: AppServerObservedQueryResult): void { + if (result.data) this.receiveObservedThreads(result.data); + } + + private receiveObservedAppServerMetadata(metadata: SharedServerMetadata): void { + this.parts.serverActions.metadata.applyAppServerMetadata(metadata); + } + + private receiveObservedAppServerMetadataResult(result: AppServerObservedQueryResult): void { + if (result.data) this.receiveObservedAppServerMetadata(result.data); + } + + private receiveObservedModels(models: readonly ModelMetadata[]): void { + this.dispatch({ type: "connection/metadata-applied", availableModels: models }); + } + + private receiveObservedModelsResult(result: AppServerObservedQueryResult): void { + if (result.data) this.receiveObservedModels(result.data); + } + + private applyCachedAppServerState(): void { + const threads = this.environment.plugin.threadCatalog.activeThreadsSnapshot(); + if (threads) this.parts.serverActions.threads.applyThreadList(threads); + const metadata = this.environment.plugin.appServerData.appServerMetadataSnapshot(); + if (metadata) this.parts.serverActions.metadata.applyAppServerMetadata(metadata); + const models = this.environment.plugin.appServerData.modelsSnapshot(); + if (models) this.receiveObservedModels(models); + } + + private subscribeAppServerState(): void { + this.unsubscribeAppServerState(); + this.applyCachedAppServerState(); + this.appServerStateUnsubscribers.push( + this.environment.plugin.threadCatalog.observeActiveThreadsResult( + (result) => { + this.receiveObservedThreadResult(result); + }, + { emitCurrent: false }, + ), + this.environment.plugin.appServerData.observeAppServerMetadataResult( + (result) => { + this.receiveObservedAppServerMetadataResult(result); + }, + { emitCurrent: false }, + ), + this.environment.plugin.appServerData.observeModelsResult( + (result) => { + this.receiveObservedModelsResult(result); + }, + { emitCurrent: false }, + ), + ); + } + + private unsubscribeAppServerState(): void { + while (this.appServerStateUnsubscribers.length > 0) { + this.appServerStateUnsubscribers.pop()?.(); + } + } + + private mountOrRepairShell(): void { + const root = this.environment.view.panelRoot(); + if (!root) return; + renderChatPanelShell(root, { + stateStore: this.stateStore, + showToolbar: this.environment.plugin.settingsRef.settings.showToolbar, + parts: { + toolbar: { + surface: this.parts.surface.toolbar, + actions: this.parts.toolbar.actions, + }, + goal: this.parts.surface.goal, + messageStream: this.parts.render.messageStreamPresenter, + composer: { + controller: this.parts.composer.controller, + actions: { + submit: () => void this.parts.composer.submission.submit(), + }, + }, + }, + }); + } + + private scheduleWarmup(): void { + const shouldWarmup = (): boolean => this.opened && !this.parts.connection.manager.isConnected(); + if (!shouldWarmup()) return; + + this.deferredTasks.scheduleAppServerWarmup(() => { + if (!shouldWarmup() || this.closing) return; + void this.parts.connection.controller.ensureConnected(); + }); + } + + private invalidateResumeWork(): void { + this.resumeWork.invalidate(); + this.parts.thread.history.invalidate(); + } + + private async loadSharedThreadList(): Promise { + try { + const threads = await this.environment.plugin.threadCatalog.refreshActiveThreads(); + this.parts.serverActions.threads.applyThreadList(threads); + } catch (error) { + if (isStaleAppServerSharedQueryContextError(error)) return; + throw error; + } + } + + private notifyActiveThreadIdentityChanged(): void { + this.refreshTabHeader(); + this.environment.obsidian.requestWorkspaceLayoutSave(); + } + + private refreshTabHeader(): void { + this.environment.view.refreshTabHeader(); + } + + private refreshLiveState(): void { + this.environment.plugin.threadCatalog.refreshThreadsViewLiveState(); + } + + private deferLiveStateRefresh(): void { + this.viewWindow().setTimeout(() => { + this.refreshLiveState(); + }, 0); + } + + private viewWindow(): Window { + return this.environment.view.viewWindow() ?? window; + } + + private currentAppServerContext(): AppServerQueryContext { + return { + codexPath: this.environment.plugin.settingsRef.settings.codexPath, + vaultPath: this.environment.plugin.settingsRef.vaultPath, + }; + } + + private closeToolbarPanelOnOutsidePointer(event: PointerEvent): void { + this.parts.toolbar.panels.closeOnOutsidePointer({ + target: event.target, + viewWindow: this.environment.view.viewWindow() as (Window & { Element: typeof Element }) | null, + contains: (element) => this.environment.view.containsElement(element), + renameEditing: this.parts.thread.rename.isEditing(), + }); + } + + private activeThreadTitle(): string | null { + const threadId = this.state.activeThread.id; + if (!threadId) return null; + const thread = this.state.threadList.listedThreads.find((item) => item.id === threadId); + return thread ? getThreadTitle(thread) : null; + } + + private restoredThreadTitle(): string | null { + return this.parts.thread.restoration.title(); + } + + private ensureRestoredThreadLoaded(): Promise { + return this.parts.thread.restoration.ensureLoaded((threadId) => this.parts.thread.resume.resumeThread(threadId)); + } + + private scheduleRestoredThreadHydration(): void { + this.parts.thread.restoration.scheduleHydration((threadId) => this.parts.thread.resume.resumeThread(threadId)); + } + private createSessionParts(): ChatPanelSessionParts { const status = this.createSessionStatus(); const connection = this.createConnectionManager(); @@ -963,325 +1282,6 @@ export class ChatPanelSession implements ChatSurfaceHandle { }; } - private get state(): ChatState { - return this.stateStore.getState(); - } - - private dispatch(action: ChatAction): void { - this.stateStore.dispatch(action); - } - - displayTitle(): string { - return codexPanelDisplayTitle(this.state.activeThread.id, this.state.threadList.listedThreads, this.restoredThreadTitle()); - } - - persistedState(): Record { - const threadId = this.state.activeThread.id; - if (!threadId) return { version: 1 }; - - const threadTitle = this.restoredThreadTitle() ?? this.activeThreadTitle(); - return { - version: 1, - threadId, - ...(threadTitle ? { threadTitle } : {}), - }; - } - - applyViewState(state: unknown): void { - const restoredThread = parseRestoredThreadState(state); - if (restoredThread) { - this.parts.thread.restoration.restore(restoredThread); - this.scheduleRestoredThreadHydration(); - return; - } - - this.invalidateResumeWork(); - this.parts.thread.restoration.clear(); - this.parts.thread.restoration.clearHydration(); - this.scheduleWarmup(); - } - - refreshSettings(): void { - const nextContext = this.currentAppServerContext(); - if (!appServerQueryContextRawEquals(this.observedAppServerContext, nextContext)) { - this.observedAppServerContext = nextContext; - this.connectionWork.invalidate(); - this.invalidateResumeWork(); - this.parts.connection.manager.resetConnection(); - this.applyCachedAppServerState(); - } - this.mountOrRepairShell(); - } - - refreshSharedThreadList(): Promise { - return this.loadSharedThreadList(); - } - - async runWithAppServerClient(operation: (client: AppServerClient) => Promise): Promise { - const client = this.parts.connection.manager.currentClient(); - if (!client) throw new Error("Codex app-server is not connected."); - const result = await operation(client); - if (this.parts.connection.manager.currentClient() !== client) { - throw new Error("Codex app-server connection changed while loading shared data."); - } - return result; - } - - private receiveObservedThreads(threads: readonly Thread[]): void { - this.parts.serverActions.threads.applyThreadList(threads); - this.refreshTabHeader(); - } - - private receiveObservedThreadResult(result: AppServerObservedQueryResult): void { - if (result.data) this.receiveObservedThreads(result.data); - } - - private receiveObservedAppServerMetadata(metadata: SharedServerMetadata): void { - this.parts.serverActions.metadata.applyAppServerMetadata(metadata); - } - - private receiveObservedAppServerMetadataResult(result: AppServerObservedQueryResult): void { - if (result.data) this.receiveObservedAppServerMetadata(result.data); - } - - private receiveObservedModels(models: readonly ModelMetadata[]): void { - this.dispatch({ type: "connection/metadata-applied", availableModels: models }); - } - - private receiveObservedModelsResult(result: AppServerObservedQueryResult): void { - if (result.data) this.receiveObservedModels(result.data); - } - - openPanelSnapshot(): ChatPanelSnapshot { - return { - viewId: this.environment.obsidian.viewId, - threadId: this.closing ? null : this.state.activeThread.id, - turnLifecycle: openPanelTurnLifecycle(this.state.turn.lifecycle), - pendingApprovals: this.state.requests.approvals.length, - pendingUserInputs: this.state.requests.pendingUserInputs.length, - hasComposerDraft: this.state.composer.draft.trim().length > 0, - connected: this.parts.connection.manager.isConnected(), - }; - } - - async openThread(threadId: string): Promise { - await this.parts.thread.resume.resumeThread(threadId); - this.focusComposer(); - } - - async focusThread(threadId: string | null = null): Promise { - if (threadId && this.parts.thread.restoration.isPending(threadId)) { - await this.ensureRestoredThreadLoaded(); - } - this.focusComposer(); - } - - focusComposer(): void { - this.parts.composer.controller.focus(); - } - - applyThreadArchived(threadId: string): void { - this.parts.thread.identity.applyThreadArchived(threadId); - } - - applyThreadRenamed(threadId: string, name: string | null): void { - this.parts.thread.identity.applyThreadRenamed(threadId, name); - } - - open(): void { - this.opened = true; - this.closing = false; - this.parts.composer.controller.registerNoteIndexInvalidation((eventRef) => { - this.environment.obsidian.registerEvent(eventRef); - }); - this.environment.obsidian.registerPointerDown((event) => { - this.closeToolbarPanelOnOutsidePointer(event); - }); - this.subscribeAppServerState(); - this.mountOrRepairShell(); - this.scheduleWarmup(); - this.scheduleRestoredThreadHydration(); - } - - close(): void { - this.opened = false; - this.closing = true; - this.connectionWork.invalidate(); - this.invalidateResumeWork(); - this.deferredTasks.clearAll(); - this.unsubscribeAppServerState(); - const panelRoot = this.environment.view.panelRoot(); - this.parts.render.messageStreamPresenter.dispose(); - this.parts.composer.controller.dispose(); - unmountChatPanelShell(panelRoot); - this.parts.connection.manager.disconnect(); - this.refreshLiveState(); - this.deferLiveStateRefresh(); - } - - setComposerText(text: string): void { - this.parts.composer.controller.setDraft(text, { focus: true }); - } - - async connect(): Promise { - await this.parts.connection.controller.ensureConnected(); - } - - async startNewThread(): Promise { - if (chatTurnBusy(this.state)) return; - - this.parts.thread.identity.clearActiveThreadContext(); - this.dispatch({ type: "ui/panel-set", panel: null }); - this.dispatch({ type: "connection/status-set", statusText: "New chat." }); - this.focusComposer(); - } - - private applyCachedAppServerState(): void { - const threads = this.environment.plugin.threadCatalog.activeThreadsSnapshot(); - if (threads) this.parts.serverActions.threads.applyThreadList(threads); - const metadata = this.environment.plugin.appServerData.appServerMetadataSnapshot(); - if (metadata) this.parts.serverActions.metadata.applyAppServerMetadata(metadata); - const models = this.environment.plugin.appServerData.modelsSnapshot(); - if (models) this.receiveObservedModels(models); - } - - private subscribeAppServerState(): void { - this.unsubscribeAppServerState(); - this.applyCachedAppServerState(); - this.appServerStateUnsubscribers.push( - this.environment.plugin.threadCatalog.observeActiveThreadsResult( - (result) => { - this.receiveObservedThreadResult(result); - }, - { emitCurrent: false }, - ), - this.environment.plugin.appServerData.observeAppServerMetadataResult( - (result) => { - this.receiveObservedAppServerMetadataResult(result); - }, - { emitCurrent: false }, - ), - this.environment.plugin.appServerData.observeModelsResult( - (result) => { - this.receiveObservedModelsResult(result); - }, - { emitCurrent: false }, - ), - ); - } - - private unsubscribeAppServerState(): void { - while (this.appServerStateUnsubscribers.length > 0) { - this.appServerStateUnsubscribers.pop()?.(); - } - } - - private mountOrRepairShell(): void { - const root = this.environment.view.panelRoot(); - if (!root) return; - renderChatPanelShell(root, { - stateStore: this.stateStore, - showToolbar: this.environment.plugin.settingsRef.settings.showToolbar, - parts: { - toolbar: { - surface: this.parts.surface.toolbar, - actions: this.parts.toolbar.actions, - }, - goal: this.parts.surface.goal, - messageStream: this.parts.render.messageStreamPresenter, - composer: { - controller: this.parts.composer.controller, - actions: { - submit: () => void this.parts.composer.submission.submit(), - }, - }, - }, - }); - } - - private scheduleWarmup(): void { - const shouldWarmup = (): boolean => this.opened && !this.parts.connection.manager.isConnected(); - if (!shouldWarmup()) return; - - this.deferredTasks.scheduleAppServerWarmup(() => { - if (!shouldWarmup() || this.closing) return; - void this.parts.connection.controller.ensureConnected(); - }); - } - - private invalidateResumeWork(): void { - this.resumeWork.invalidate(); - this.parts.thread.history.invalidate(); - } - - private async loadSharedThreadList(): Promise { - try { - const threads = await this.environment.plugin.threadCatalog.refreshActiveThreads(); - this.parts.serverActions.threads.applyThreadList(threads); - } catch (error) { - if (isStaleAppServerSharedQueryContextError(error)) return; - throw error; - } - } - - private notifyActiveThreadIdentityChanged(): void { - this.refreshTabHeader(); - this.environment.obsidian.requestWorkspaceLayoutSave(); - } - - private refreshTabHeader(): void { - this.environment.view.refreshTabHeader(); - } - - private refreshLiveState(): void { - this.environment.plugin.threadCatalog.refreshThreadsViewLiveState(); - } - - private deferLiveStateRefresh(): void { - this.viewWindow().setTimeout(() => { - this.refreshLiveState(); - }, 0); - } - - private viewWindow(): Window { - return this.environment.view.viewWindow() ?? window; - } - - private currentAppServerContext(): AppServerQueryContext { - return { - codexPath: this.environment.plugin.settingsRef.settings.codexPath, - vaultPath: this.environment.plugin.settingsRef.vaultPath, - }; - } - - private closeToolbarPanelOnOutsidePointer(event: PointerEvent): void { - this.parts.toolbar.panels.closeOnOutsidePointer({ - target: event.target, - viewWindow: this.environment.view.viewWindow() as (Window & { Element: typeof Element }) | null, - contains: (element) => this.environment.view.containsElement(element), - renameEditing: this.parts.thread.rename.isEditing(), - }); - } - - private activeThreadTitle(): string | null { - const threadId = this.state.activeThread.id; - if (!threadId) return null; - const thread = this.state.threadList.listedThreads.find((item) => item.id === threadId); - return thread ? getThreadTitle(thread) : null; - } - - private restoredThreadTitle(): string | null { - return this.parts.thread.restoration.title(); - } - - private ensureRestoredThreadLoaded(): Promise { - return this.parts.thread.restoration.ensureLoaded((threadId) => this.parts.thread.resume.resumeThread(threadId)); - } - - private scheduleRestoredThreadHydration(): void { - this.parts.thread.restoration.scheduleHydration((threadId) => this.parts.thread.resume.resumeThread(threadId)); - } - private statusSummaryLines(): string[] { return buildStatusSummaryLines({ activeThreadId: this.state.activeThread.id,