From 2b543f590415d071a9219e61deb2ac6a382b2642 Mon Sep 17 00:00:00 2001 From: murashit Date: Tue, 30 Jun 2026 09:14:34 +0900 Subject: [PATCH] Organize chat host wiring modules --- biome.jsonc | 4 -- .../no-app-server-root-module-imports.grit | 16 ----- .../host/{ => bundles}/composer-bundle.ts | 22 +++--- .../host/{ => bundles}/connection-bundle.ts | 34 +++++----- .../chat/host/bundles/reconnect-bundle.ts | 53 +++++++++++++++ .../chat/host/{ => bundles}/runtime-bundle.ts | 16 ++--- .../chat/host/{ => bundles}/shell-bundle.ts | 26 +++---- .../chat/host/{ => bundles}/thread-bundle.ts | 48 ++++++------- .../chat/host/{ => bundles}/turn-bundle.ts | 67 ++++--------------- .../composer-attachments.obsidian.ts | 4 +- ...ser-context-reference-provider.obsidian.ts | 2 +- .../vault-note-candidate-provider.obsidian.ts | 4 +- .../vault-note-links.obsidian.ts | 0 src/features/chat/host/session-graph.ts | 34 ++++++---- src/features/chat/host/session.ts | 2 +- .../chat/host/{ => session}/deferred-work.ts | 2 +- .../{ => session}/shared-state-binding.ts | 14 ++-- .../chat/host/composer-attachments.test.ts | 2 +- .../chat/host/connection-bundle.test.ts | 2 +- .../features/chat/host/deferred-work.test.ts | 2 +- .../chat/host/reconnect-action.test.ts | 57 ++++++++++++++++ .../features/chat/host/session-graph.test.ts | 2 +- tests/features/chat/host/turn-bundle.test.ts | 16 +---- .../vault-note-candidate-provider.test.ts | 4 +- tests/scripts/grit-policy.test.mjs | 67 ++++++++++--------- 25 files changed, 273 insertions(+), 227 deletions(-) delete mode 100644 scripts/grit/import-boundaries/no-app-server-root-module-imports.grit rename src/features/chat/host/{ => bundles}/composer-bundle.ts (78%) rename src/features/chat/host/{ => bundles}/connection-bundle.ts (88%) create mode 100644 src/features/chat/host/bundles/reconnect-bundle.ts rename src/features/chat/host/{ => bundles}/runtime-bundle.ts (79%) rename src/features/chat/host/{ => bundles}/shell-bundle.ts (80%) rename src/features/chat/host/{ => bundles}/thread-bundle.ts (88%) rename src/features/chat/host/{ => bundles}/turn-bundle.ts (66%) rename src/features/chat/host/{ => obsidian}/composer-attachments.obsidian.ts (98%) rename src/features/chat/host/{ => obsidian}/vault-composer-context-reference-provider.obsidian.ts (98%) rename src/features/chat/host/{ => obsidian}/vault-note-candidate-provider.obsidian.ts (96%) rename src/features/chat/host/{ => obsidian}/vault-note-links.obsidian.ts (100%) rename src/features/chat/host/{ => session}/deferred-work.ts (96%) rename src/features/chat/host/{ => session}/shared-state-binding.ts (87%) create mode 100644 tests/features/chat/host/reconnect-action.test.ts diff --git a/biome.jsonc b/biome.jsonc index 1bf3ff34..3728f79f 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -63,10 +63,6 @@ }, // App-server and generated protocol boundaries. - { - "path": "./scripts/grit/import-boundaries/no-app-server-root-module-imports.grit", - "includes": ["**/src/**/*.ts", "**/src/**/*.tsx"] - }, { "path": "./scripts/grit/import-boundaries/no-app-server-subfolder-root-imports.grit", "includes": ["**/src/app-server/**/*.ts"] diff --git a/scripts/grit/import-boundaries/no-app-server-root-module-imports.grit b/scripts/grit/import-boundaries/no-app-server-root-module-imports.grit deleted file mode 100644 index bfd708dc..00000000 --- a/scripts/grit/import-boundaries/no-app-server-root-module-imports.grit +++ /dev/null @@ -1,16 +0,0 @@ -language js - -private pattern js_module_reference() { - or { - JsImport(), - JsImportCallExpression() - } -} - -js_module_reference() as $stmt where { - $stmt <: contains `$source` where { - $source <: r"^[\"'](?:(?:\./)?app-server|(?:\.\./)+app-server|src/app-server)/[^/\"']+[\"']$", - not { $filename <: r".*/src/features/chat/[^/]+/.*", $source <: r"^[\"']\.\./app-server/[^/\"']+[\"']$" } - }, - register_diagnostic(span=$stmt, message="Import app-server boundary modules from responsibility subfolders, not src/app-server root modules.", severity="error") -} diff --git a/src/features/chat/host/composer-bundle.ts b/src/features/chat/host/bundles/composer-bundle.ts similarity index 78% rename from src/features/chat/host/composer-bundle.ts rename to src/features/chat/host/bundles/composer-bundle.ts index 24e01de7..fe12e1cb 100644 --- a/src/features/chat/host/composer-bundle.ts +++ b/src/features/chat/host/bundles/composer-bundle.ts @@ -1,17 +1,17 @@ import { Notice } from "obsidian"; -import { runtimeConfigOrDefault } from "../../../domain/runtime/config"; -import { runtimeSnapshotForChatState } from "../application/runtime/snapshot"; -import type { ChatStateStore } from "../application/state/store"; -import { resolveRuntimeControls } from "../domain/runtime/resolution"; -import { ChatComposerController } from "../panel/composer-controller"; -import { type ChatPanelComposerSurface, chatPanelComposerProjection } from "../panel/surface/composer-projection"; -import type { ChatMessageScrollController } from "../panel/surface/message-stream-scroll"; -import { createVaultComposerAttachmentHandler } from "./composer-attachments.obsidian"; -import type { ChatPanelEnvironment } from "./contracts"; +import { runtimeConfigOrDefault } from "../../../../domain/runtime/config"; +import { runtimeSnapshotForChatState } from "../../application/runtime/snapshot"; +import type { ChatStateStore } from "../../application/state/store"; +import { resolveRuntimeControls } from "../../domain/runtime/resolution"; +import { ChatComposerController } from "../../panel/composer-controller"; +import { type ChatPanelComposerSurface, chatPanelComposerProjection } from "../../panel/surface/composer-projection"; +import type { ChatMessageScrollController } from "../../panel/surface/message-stream-scroll"; +import type { ChatPanelEnvironment } from "../contracts"; +import { createVaultComposerAttachmentHandler } from "../obsidian/composer-attachments.obsidian"; +import { VaultComposerContextReferenceProvider } from "../obsidian/vault-composer-context-reference-provider.obsidian"; +import { VaultNoteCandidateProvider } from "../obsidian/vault-note-candidate-provider.obsidian"; import type { ChatPanelRuntimeSettingsActions } from "./runtime-bundle"; -import { VaultComposerContextReferenceProvider } from "./vault-composer-context-reference-provider.obsidian"; -import { VaultNoteCandidateProvider } from "./vault-note-candidate-provider.obsidian"; interface ChatPanelComposerHost { environment: ChatPanelEnvironment; diff --git a/src/features/chat/host/connection-bundle.ts b/src/features/chat/host/bundles/connection-bundle.ts similarity index 88% rename from src/features/chat/host/connection-bundle.ts rename to src/features/chat/host/bundles/connection-bundle.ts index 53d5aeb2..2e849f70 100644 --- a/src/features/chat/host/connection-bundle.ts +++ b/src/features/chat/host/bundles/connection-bundle.ts @@ -1,26 +1,26 @@ import { Notice } from "obsidian"; -import type { AppServerClient } from "../../../app-server/connection/client"; -import { type ConnectionManager, StaleConnectionError } from "../../../app-server/connection/connection-manager"; -import { isStaleAppServerSharedQueryContextError } from "../../../app-server/query/shared-queries"; -import type { LocalIdSource } from "../../../shared/id/local-id"; -import type { ConnectionWorkTracker } from "../../../shared/lifecycle/connection-work"; -import { type ChatServerDiagnosticsActions, createChatServerDiagnosticsActions } from "../app-server/actions/diagnostics"; -import { type ChatServerMetadataActions, createChatServerMetadataActions } from "../app-server/actions/metadata"; -import { type ChatServerThreadActions, createChatServerThreadActions } from "../app-server/actions/threads"; -import { type ChatInboundHandler, createChatInboundHandler } from "../app-server/inbound/handler"; +import type { AppServerClient } from "../../../../app-server/connection/client"; +import { type ConnectionManager, StaleConnectionError } from "../../../../app-server/connection/connection-manager"; +import { isStaleAppServerSharedQueryContextError } from "../../../../app-server/query/shared-queries"; +import type { LocalIdSource } from "../../../../shared/id/local-id"; +import type { ConnectionWorkTracker } from "../../../../shared/lifecycle/connection-work"; +import { type ChatServerDiagnosticsActions, createChatServerDiagnosticsActions } from "../../app-server/actions/diagnostics"; +import { type ChatServerMetadataActions, createChatServerMetadataActions } from "../../app-server/actions/metadata"; +import { type ChatServerThreadActions, createChatServerThreadActions } from "../../app-server/actions/threads"; +import { type ChatInboundHandler, createChatInboundHandler } from "../../app-server/inbound/handler"; import { type ChatConnectionController, createChatConnectionController, handleChatConnectionExit, -} from "../application/connection/connection-controller"; -import { runtimeSnapshotForChatState } from "../application/runtime/snapshot"; -import type { ChatConnectionPhase } from "../application/state/root-reducer"; -import type { ChatStateStore } from "../application/state/store"; -import type { AutoTitleCoordinator } from "../application/threads/auto-title-coordinator"; -import type { createThreadGoalSyncActions } from "../application/threads/goal-actions"; -import type { ChatPanelEnvironment } from "./contracts"; -import type { ChatViewDeferredTasks } from "./deferred-work"; +} from "../../application/connection/connection-controller"; +import { runtimeSnapshotForChatState } from "../../application/runtime/snapshot"; +import type { ChatConnectionPhase } from "../../application/state/root-reducer"; +import type { ChatStateStore } from "../../application/state/store"; +import type { AutoTitleCoordinator } from "../../application/threads/auto-title-coordinator"; +import type { createThreadGoalSyncActions } from "../../application/threads/goal-actions"; +import type { ChatPanelEnvironment } from "../contracts"; +import type { ChatViewDeferredTasks } from "../session/deferred-work"; type CurrentAppServerClient = () => AppServerClient | null; diff --git a/src/features/chat/host/bundles/reconnect-bundle.ts b/src/features/chat/host/bundles/reconnect-bundle.ts new file mode 100644 index 00000000..0fcbd8b3 --- /dev/null +++ b/src/features/chat/host/bundles/reconnect-bundle.ts @@ -0,0 +1,53 @@ +import type { ConnectionManager } from "../../../../app-server/connection/connection-manager"; +import type { ConnectionWorkTracker } from "../../../../shared/lifecycle/connection-work"; +import { type ChatReconnectActionsHost, reconnectPanel } from "../../application/connection/reconnect-actions"; +import type { ChatConnectionPhase } from "../../application/state/root-reducer"; +import type { ChatStateStore } from "../../application/state/store"; +import type { ChatViewDeferredTasks } from "../session/deferred-work"; + +interface ChatPanelReconnectStatus { + set: (statusText: string, phase?: ChatConnectionPhase) => void; + addSystemMessage: (text: string) => void; +} + +interface ChatPanelReconnectHost { + stateStore: ChatStateStore; + connectionWork: ConnectionWorkTracker; + deferredTasks: ChatViewDeferredTasks; +} + +interface ChatPanelReconnectInput { + connection: ConnectionManager; + ensureConnected: () => Promise; + invalidateThreadWork: () => void; + resumeThread: (threadId: string) => Promise; + status: ChatPanelReconnectStatus; +} + +export function createReconnectAction(host: ChatPanelReconnectHost, input: ChatPanelReconnectInput): () => Promise { + const reconnectHost: ChatReconnectActionsHost = { + stateStore: host.stateStore, + invalidateConnectionWork: () => { + host.connectionWork.invalidate(); + }, + invalidateThreadWork: () => { + input.invalidateThreadWork(); + }, + clearDeferredDiagnostics: () => { + host.deferredTasks.clearDiagnostics(); + }, + resetConnection: () => { + input.connection.resetConnection(); + }, + setStatus: (statusText, phase) => { + input.status.set(statusText, phase); + }, + ensureConnected: input.ensureConnected, + resumeThread: input.resumeThread, + addSystemMessage: (text) => { + input.status.addSystemMessage(text); + }, + }; + + return () => reconnectPanel(reconnectHost); +} diff --git a/src/features/chat/host/runtime-bundle.ts b/src/features/chat/host/bundles/runtime-bundle.ts similarity index 79% rename from src/features/chat/host/runtime-bundle.ts rename to src/features/chat/host/bundles/runtime-bundle.ts index dd67abaf..7a8a9e72 100644 --- a/src/features/chat/host/runtime-bundle.ts +++ b/src/features/chat/host/bundles/runtime-bundle.ts @@ -1,11 +1,11 @@ -import type { ConnectionManager } from "../../../app-server/connection/connection-manager"; -import type { ChatAppServerGateway } from "../app-server/session-gateway"; -import { type ChatRuntimeSettingsActions, createChatRuntimeSettingsActions } from "../application/runtime/settings-actions"; -import { runtimeSnapshotForChatState } from "../application/runtime/snapshot"; -import type { ChatStateStore } from "../application/state/store"; -import { collaborationModeLabel as formatCollaborationModeLabel } from "../domain/runtime/labels"; -import { type ChatPanelRuntimeProjection, createChatPanelRuntimeProjection } from "../panel/runtime-status-projection"; -import type { ChatPanelEnvironment } from "./contracts"; +import type { ConnectionManager } from "../../../../app-server/connection/connection-manager"; +import type { ChatAppServerGateway } from "../../app-server/session-gateway"; +import { type ChatRuntimeSettingsActions, createChatRuntimeSettingsActions } from "../../application/runtime/settings-actions"; +import { runtimeSnapshotForChatState } from "../../application/runtime/snapshot"; +import type { ChatStateStore } from "../../application/state/store"; +import { collaborationModeLabel as formatCollaborationModeLabel } from "../../domain/runtime/labels"; +import { type ChatPanelRuntimeProjection, createChatPanelRuntimeProjection } from "../../panel/runtime-status-projection"; +import type { ChatPanelEnvironment } from "../contracts"; export type ChatPanelRuntimeSettingsActions = ChatRuntimeSettingsActions; diff --git a/src/features/chat/host/shell-bundle.ts b/src/features/chat/host/bundles/shell-bundle.ts similarity index 80% rename from src/features/chat/host/shell-bundle.ts rename to src/features/chat/host/bundles/shell-bundle.ts index 1187070b..50da27d5 100644 --- a/src/features/chat/host/shell-bundle.ts +++ b/src/features/chat/host/bundles/shell-bundle.ts @@ -1,18 +1,18 @@ -import type { ConnectionManager } from "../../../app-server/connection/connection-manager"; -import type { PendingRequestActions } from "../application/pending-requests/pending-request-actions"; -import type { ChatStateStore } from "../application/state/store"; -import type { HistoryController } from "../application/threads/history-controller"; -import type { ThreadRenameEditorActions } from "../application/threads/rename-editor-actions"; -import type { ChatPanelShellParts } from "../panel/shell.dom"; -import type { ChatPanelGoalSurface } from "../panel/surface/goal-projection"; -import { MessageStreamPresenter } from "../panel/surface/message-stream-presenter"; -import type { ChatMessageScrollController } from "../panel/surface/message-stream-scroll"; -import type { ChatPanelToolbarSurface } from "../panel/surface/toolbar-projection"; -import { createToolbarUiActions, type ToolbarPanelActions } from "../panel/toolbar-actions"; -import { toolbarOutsidePointerHit } from "../panel/toolbar-hit-test.dom"; +import type { ConnectionManager } from "../../../../app-server/connection/connection-manager"; +import type { PendingRequestActions } from "../../application/pending-requests/pending-request-actions"; +import type { ChatStateStore } from "../../application/state/store"; +import type { HistoryController } from "../../application/threads/history-controller"; +import type { ThreadRenameEditorActions } from "../../application/threads/rename-editor-actions"; +import type { ChatPanelShellParts } from "../../panel/shell.dom"; +import type { ChatPanelGoalSurface } from "../../panel/surface/goal-projection"; +import { MessageStreamPresenter } from "../../panel/surface/message-stream-presenter"; +import type { ChatMessageScrollController } from "../../panel/surface/message-stream-scroll"; +import type { ChatPanelToolbarSurface } from "../../panel/surface/toolbar-projection"; +import { createToolbarUiActions, type ToolbarPanelActions } from "../../panel/toolbar-actions"; +import { toolbarOutsidePointerHit } from "../../panel/toolbar-hit-test.dom"; +import type { ChatPanelEnvironment } from "../contracts"; import type { ChatPanelComposerBundle } from "./composer-bundle"; import type { ChatPanelConnectionBundle } from "./connection-bundle"; -import type { ChatPanelEnvironment } from "./contracts"; import type { ChatPanelGoalActions, ChatPanelThreadActions, ChatPanelThreadNavigationActions } from "./thread-bundle"; import type { ChatPanelTurnBundle } from "./turn-bundle"; diff --git a/src/features/chat/host/thread-bundle.ts b/src/features/chat/host/bundles/thread-bundle.ts similarity index 88% rename from src/features/chat/host/thread-bundle.ts rename to src/features/chat/host/bundles/thread-bundle.ts index 08191308..ca635da4 100644 --- a/src/features/chat/host/thread-bundle.ts +++ b/src/features/chat/host/bundles/thread-bundle.ts @@ -1,33 +1,33 @@ import { Notice } from "obsidian"; -import { recoverRolloutTokenUsage } from "../../../app-server/services/rollout-token-usage"; -import { normalizeExplicitThreadName } from "../../../domain/threads/model"; -import type { LocalIdSource } from "../../../shared/id/local-id"; -import { createThreadOperations, type ThreadOperations } from "../../threads/workflows/thread-operations"; -import { createThreadTitleService, type ThreadTitleService } from "../../threads/workflows/thread-title-service"; -import type { ChatServerThreadActions } from "../app-server/actions/threads"; -import type { ChatAppServerGateway } from "../app-server/session-gateway"; -import { messageStreamItems } from "../application/state/message-stream"; -import type { ChatStateStore } from "../application/state/store"; -import type { ActiveThreadIdentitySync } from "../application/threads/active-thread-identity-sync"; -import { type AutoTitleCoordinator, createAutoTitleCoordinator } from "../application/threads/auto-title-coordinator"; -import { createGoalActions, createThreadGoalSyncActions } from "../application/threads/goal-actions"; -import { HistoryController } from "../application/threads/history-controller"; -import { createThreadLifecycleParts } from "../application/threads/lifecycle-parts"; +import { recoverRolloutTokenUsage } from "../../../../app-server/services/rollout-token-usage"; +import { normalizeExplicitThreadName } from "../../../../domain/threads/model"; +import type { LocalIdSource } from "../../../../shared/id/local-id"; +import { createThreadOperations, type ThreadOperations } from "../../../threads/workflows/thread-operations"; +import { createThreadTitleService, type ThreadTitleService } from "../../../threads/workflows/thread-title-service"; +import type { ChatServerThreadActions } from "../../app-server/actions/threads"; +import type { ChatAppServerGateway } from "../../app-server/session-gateway"; +import { messageStreamItems } from "../../application/state/message-stream"; +import type { ChatStateStore } from "../../application/state/store"; +import type { ActiveThreadIdentitySync } from "../../application/threads/active-thread-identity-sync"; +import { type AutoTitleCoordinator, createAutoTitleCoordinator } from "../../application/threads/auto-title-coordinator"; +import { createGoalActions, createThreadGoalSyncActions } from "../../application/threads/goal-actions"; +import { HistoryController } from "../../application/threads/history-controller"; +import { createThreadLifecycleParts } from "../../application/threads/lifecycle-parts"; import { activeThreadRenameTitleContext, createThreadRenameEditorActions, type ThreadRenameEditorActions, -} from "../application/threads/rename-editor-actions"; -import type { RestorationController } from "../application/threads/restoration-controller"; -import type { ResumeActions } from "../application/threads/resume-actions"; -import type { ChatResumeWorkTracker } from "../application/threads/resume-work"; -import { createThreadManagementActions, type ThreadManagementActionsHost } from "../application/threads/thread-management-actions"; -import { createThreadNavigationActions } from "../application/threads/thread-navigation-actions"; -import { threadTitleContextFromMessageStreamItems } from "../application/threads/title-context"; -import type { ChatComposerController } from "../panel/composer-controller"; -import { createToolbarPanelActions, type ToolbarPanelActions } from "../panel/toolbar-actions"; -import type { ChatPanelEnvironment } from "./contracts"; +} from "../../application/threads/rename-editor-actions"; +import type { RestorationController } from "../../application/threads/restoration-controller"; +import type { ResumeActions } from "../../application/threads/resume-actions"; +import type { ChatResumeWorkTracker } from "../../application/threads/resume-work"; +import { createThreadManagementActions, type ThreadManagementActionsHost } from "../../application/threads/thread-management-actions"; +import { createThreadNavigationActions } from "../../application/threads/thread-navigation-actions"; +import { threadTitleContextFromMessageStreamItems } from "../../application/threads/title-context"; +import type { ChatComposerController } from "../../panel/composer-controller"; +import { createToolbarPanelActions, type ToolbarPanelActions } from "../../panel/toolbar-actions"; +import type { ChatPanelEnvironment } from "../contracts"; type ChatPanelGoalSyncActions = ReturnType; export type ChatPanelGoalActions = ReturnType; diff --git a/src/features/chat/host/turn-bundle.ts b/src/features/chat/host/bundles/turn-bundle.ts similarity index 66% rename from src/features/chat/host/turn-bundle.ts rename to src/features/chat/host/bundles/turn-bundle.ts index 95776f1d..96a261ca 100644 --- a/src/features/chat/host/turn-bundle.ts +++ b/src/features/chat/host/bundles/turn-bundle.ts @@ -1,23 +1,17 @@ -import type { ConnectionManager } from "../../../app-server/connection/connection-manager"; -import type { LocalIdSource } from "../../../shared/id/local-id"; -import type { ConnectionWorkTracker } from "../../../shared/lifecycle/connection-work"; -import type { ChatServerThreadActions } from "../app-server/actions/threads"; -import type { ChatInboundHandler } from "../app-server/inbound/handler"; -import type { ChatAppServerGateway } from "../app-server/session-gateway"; -import { type ChatReconnectActionsHost, reconnectPanel } from "../application/connection/reconnect-actions"; +import type { LocalIdSource } from "../../../../shared/id/local-id"; +import type { ChatServerThreadActions } from "../../app-server/actions/threads"; +import type { ChatInboundHandler } from "../../app-server/inbound/handler"; +import type { ChatAppServerGateway } from "../../app-server/session-gateway"; import { type ConversationTurnActions as ChatPanelConversationTurnActions, createConversationTurnActions, -} from "../application/conversation/composition"; -import { createPendingRequestActions, type PendingRequestActions } from "../application/pending-requests/pending-request-actions"; -import type { ChatConnectionPhase } from "../application/state/root-reducer"; -import type { ChatStateStore } from "../application/state/store"; -import type { AutoTitleCoordinator } from "../application/threads/auto-title-coordinator"; -import type { MessageStreamNoticeSection } from "../domain/message-stream/items"; -import type { ChatComposerController } from "../panel/composer-controller"; -import type { ChatPanelRuntimeProjection } from "../panel/runtime-status-projection"; -import type { ChatPanelEnvironment } from "./contracts"; -import type { ChatViewDeferredTasks } from "./deferred-work"; +} from "../../application/conversation/composition"; +import { createPendingRequestActions, type PendingRequestActions } from "../../application/pending-requests/pending-request-actions"; +import type { ChatStateStore } from "../../application/state/store"; +import type { AutoTitleCoordinator } from "../../application/threads/auto-title-coordinator"; +import type { MessageStreamNoticeSection } from "../../domain/message-stream/items"; +import type { ChatComposerController } from "../../panel/composer-controller"; +import type { ChatPanelRuntimeProjection } from "../../panel/runtime-status-projection"; import type { ChatPanelRuntimeSettingsActions } from "./runtime-bundle"; import type { ChatPanelGoalActions, @@ -27,16 +21,13 @@ import type { } from "./thread-bundle"; interface ChatPanelTurnStatus { - set: (statusText: string, phase?: ChatConnectionPhase) => void; + set: (statusText: string) => void; addSystemMessage: (text: string) => void; addStructuredSystemMessage: (text: string, details: MessageStreamNoticeSection[]) => void; } interface ChatPanelTurnHost { - environment: ChatPanelEnvironment; stateStore: ChatStateStore; - deferredTasks: ChatViewDeferredTasks; - connectionWork: ConnectionWorkTracker; messageScrollController: { showLatest(): void; }; @@ -44,14 +35,11 @@ interface ChatPanelTurnHost { export interface ChatPanelTurnBundle { pendingRequests: PendingRequestActions; - reconnect: () => Promise; turnActions: ChatPanelConversationTurnActions; } interface ChatPanelTurnInput { - connection: ConnectionManager; localItemIds: LocalIdSource; - ensureConnected: () => Promise; appServer: ChatAppServerGateway; status: ChatPanelTurnStatus; inboundHandler: ChatInboundHandler; @@ -63,7 +51,7 @@ interface ChatPanelTurnInput { serverThreads: ChatServerThreadActions; goals: ChatPanelGoalActions; autoTitleCoordinator: AutoTitleCoordinator; - invalidateThreadWork: () => void; + reconnect: () => Promise; runtimeProjection: ChatPanelRuntimeProjection; refreshDiagnostics: () => Promise; refreshLiveState: () => void; @@ -72,9 +60,7 @@ interface ChatPanelTurnInput { export function createTurnBundle(host: ChatPanelTurnHost, input: ChatPanelTurnInput): ChatPanelTurnBundle { const { - connection, localItemIds, - ensureConnected, appServer, status, inboundHandler, @@ -86,7 +72,7 @@ export function createTurnBundle(host: ChatPanelTurnHost, input: ChatPanelTurnIn serverThreads, goals, autoTitleCoordinator, - invalidateThreadWork, + reconnect, runtimeProjection, refreshDiagnostics, refreshLiveState, @@ -101,30 +87,6 @@ export function createTurnBundle(host: ChatPanelTurnHost, input: ChatPanelTurnIn }, refreshLiveState, }); - const reconnectHost: ChatReconnectActionsHost = { - stateStore: host.stateStore, - invalidateConnectionWork: () => { - host.connectionWork.invalidate(); - }, - invalidateThreadWork: () => { - invalidateThreadWork(); - }, - clearDeferredDiagnostics: () => { - host.deferredTasks.clearDiagnostics(); - }, - resetConnection: () => { - connection.resetConnection(); - }, - setStatus: (statusText, phase) => { - status.set(statusText, phase); - }, - ensureConnected, - resumeThread: (threadId) => threadLifecycle.resume.resumeThread(threadId), - addSystemMessage: (text) => { - status.addSystemMessage(text); - }, - }; - const reconnect = () => reconnectPanel(reconnectHost); const threadReferenceResolver = appServer.threadReferences({ prepareInput: (text) => composerController.preparedInput(text), addSystemMessage: status.addSystemMessage, @@ -187,7 +149,6 @@ export function createTurnBundle(host: ChatPanelTurnHost, input: ChatPanelTurnIn return { pendingRequests, - reconnect, turnActions, }; } diff --git a/src/features/chat/host/composer-attachments.obsidian.ts b/src/features/chat/host/obsidian/composer-attachments.obsidian.ts similarity index 98% rename from src/features/chat/host/composer-attachments.obsidian.ts rename to src/features/chat/host/obsidian/composer-attachments.obsidian.ts index 4577e36d..d6dbeb9e 100644 --- a/src/features/chat/host/composer-attachments.obsidian.ts +++ b/src/features/chat/host/obsidian/composer-attachments.obsidian.ts @@ -1,7 +1,7 @@ import { type App, normalizePath, type Vault } from "obsidian"; -import { DEFAULT_ATTACHMENT_FOLDER } from "../../../settings/model"; -import type { ComposerAttachment, ComposerAttachmentHandler } from "../application/composer/attachments"; +import { DEFAULT_ATTACHMENT_FOLDER } from "../../../../settings/model"; +import type { ComposerAttachment, ComposerAttachmentHandler } from "../../application/composer/attachments"; interface VaultComposerAttachmentHandlerOptions { app: App; diff --git a/src/features/chat/host/vault-composer-context-reference-provider.obsidian.ts b/src/features/chat/host/obsidian/vault-composer-context-reference-provider.obsidian.ts similarity index 98% rename from src/features/chat/host/vault-composer-context-reference-provider.obsidian.ts rename to src/features/chat/host/obsidian/vault-composer-context-reference-provider.obsidian.ts index 77da28d1..49a3a452 100644 --- a/src/features/chat/host/vault-composer-context-reference-provider.obsidian.ts +++ b/src/features/chat/host/obsidian/vault-composer-context-reference-provider.obsidian.ts @@ -5,7 +5,7 @@ import type { ComposerContextRange, ComposerContextReferenceProvider, ComposerContextReferences, -} from "../application/composer/context-references"; +} from "../../application/composer/context-references"; import { displayNameForFile, linktextForFile } from "./vault-note-links.obsidian"; interface EventSource { diff --git a/src/features/chat/host/vault-note-candidate-provider.obsidian.ts b/src/features/chat/host/obsidian/vault-note-candidate-provider.obsidian.ts similarity index 96% rename from src/features/chat/host/vault-note-candidate-provider.obsidian.ts rename to src/features/chat/host/obsidian/vault-note-candidate-provider.obsidian.ts index b440ebfc..f82ba581 100644 --- a/src/features/chat/host/vault-note-candidate-provider.obsidian.ts +++ b/src/features/chat/host/obsidian/vault-note-candidate-provider.obsidian.ts @@ -1,8 +1,8 @@ import type { App, EventRef } from "obsidian"; import { stripHeadingForLink, TFile } from "obsidian"; -import type { NoteCandidateProvider, WikiLinkMention } from "../application/composer/note-context"; -import type { NoteCandidate } from "../application/composer/suggestions"; +import type { NoteCandidateProvider, WikiLinkMention } from "../../application/composer/note-context"; +import type { NoteCandidate } from "../../application/composer/suggestions"; import { displayNameForFile, linktextForFile } from "./vault-note-links.obsidian"; interface FileCandidate { diff --git a/src/features/chat/host/vault-note-links.obsidian.ts b/src/features/chat/host/obsidian/vault-note-links.obsidian.ts similarity index 100% rename from src/features/chat/host/vault-note-links.obsidian.ts rename to src/features/chat/host/obsidian/vault-note-links.obsidian.ts diff --git a/src/features/chat/host/session-graph.ts b/src/features/chat/host/session-graph.ts index 7aa5b0d2..c876c405 100644 --- a/src/features/chat/host/session-graph.ts +++ b/src/features/chat/host/session-graph.ts @@ -13,15 +13,16 @@ import { createStructuredSystemItem, createSystemItem } from "../domain/message- import type { MessageStreamNoticeSection } from "../domain/message-stream/items"; import type { ChatComposerController } from "../panel/composer-controller"; import type { ChatMessageScrollController } from "../panel/surface/message-stream-scroll"; -import { createComposerBundle } from "./composer-bundle"; -import { type ChatPanelConnectionBundle, createConnectionBundle } from "./connection-bundle"; +import { createComposerBundle } from "./bundles/composer-bundle"; +import { type ChatPanelConnectionBundle, createConnectionBundle } from "./bundles/connection-bundle"; +import { createReconnectAction } from "./bundles/reconnect-bundle"; +import { createRuntimeBundle } from "./bundles/runtime-bundle"; +import { type ChatPanelShellBundle, createShellBundle } from "./bundles/shell-bundle"; +import { createThreadActionBundle, createThreadFoundation, createThreadLifecycleBundle } from "./bundles/thread-bundle"; +import { createTurnBundle } from "./bundles/turn-bundle"; import type { ChatPanelEnvironment } from "./contracts"; -import type { ChatViewDeferredTasks } from "./deferred-work"; -import { createRuntimeBundle } from "./runtime-bundle"; -import { type ChatPanelSharedStateBinding, createChatPanelSharedStateBinding } from "./shared-state-binding"; -import { type ChatPanelShellBundle, createShellBundle } from "./shell-bundle"; -import { createThreadActionBundle, createThreadFoundation, createThreadLifecycleBundle } from "./thread-bundle"; -import { createTurnBundle } from "./turn-bundle"; +import type { ChatViewDeferredTasks } from "./session/deferred-work"; +import { type ChatPanelSharedStateBinding, createChatPanelSharedStateBinding } from "./session/shared-state-binding"; export interface ChatPanelSessionGraph { connection: { @@ -163,10 +164,17 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch refreshActiveThreads, notifyActiveThreadIdentityChanged, }); - const turn = createTurnBundle(host, { + const reconnect = createReconnectAction(host, { connection, - localItemIds, ensureConnected, + invalidateThreadWork: () => { + threadFoundation.invalidateThreadWork(); + }, + resumeThread: (threadId) => threadLifecycle.resume.resumeThread(threadId), + status, + }); + const turn = createTurnBundle(host, { + localItemIds, appServer, status, inboundHandler, @@ -178,9 +186,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch serverThreads, goals: threadLifecycle.goals, autoTitleCoordinator: threadFoundation.autoTitleCoordinator, - invalidateThreadWork: () => { - threadFoundation.invalidateThreadWork(); - }, + reconnect, runtimeProjection: runtime.projection, refreshDiagnostics: () => connectionController.refreshDiagnostics(), refreshLiveState, @@ -194,7 +200,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch threadActions: threadActions.actions, toolbarPanelActions: threadActions.toolbarPanelActions, navigation: threadActions.navigation, - reconnect: turn.reconnect, + reconnect, history: threadFoundation.history, pendingRequests: turn.pendingRequests, turn, diff --git a/src/features/chat/host/session.ts b/src/features/chat/host/session.ts index 7c39d622..dd78f2f4 100644 --- a/src/features/chat/host/session.ts +++ b/src/features/chat/host/session.ts @@ -10,7 +10,7 @@ import { ChatResumeWorkTracker } from "../application/threads/resume-work"; import { renderChatPanelShell, unmountChatPanelShell } from "../panel/shell.dom"; import { type ChatMessageScrollController, createChatMessageScrollController } from "../panel/surface/message-stream-scroll"; import type { ChatPanelEnvironment, ChatPanelHandle, ChatWorkspacePanelSnapshot, ChatWorkspacePanelTurnLifecycle } from "./contracts"; -import { type ChatViewDeferredTasks, createChatViewDeferredTasks } from "./deferred-work"; +import { type ChatViewDeferredTasks, createChatViewDeferredTasks } from "./session/deferred-work"; import { type ChatPanelSessionGraph, createChatPanelSessionGraph } from "./session-graph"; export class ChatPanelSession implements ChatPanelHandle { diff --git a/src/features/chat/host/deferred-work.ts b/src/features/chat/host/session/deferred-work.ts similarity index 96% rename from src/features/chat/host/deferred-work.ts rename to src/features/chat/host/session/deferred-work.ts index 74d8cde0..f3fdd5e7 100644 --- a/src/features/chat/host/deferred-work.ts +++ b/src/features/chat/host/session/deferred-work.ts @@ -1,4 +1,4 @@ -import { DeferredTask, type DeferredTaskWindow } from "../../../shared/lifecycle/deferred-task"; +import { DeferredTask, type DeferredTaskWindow } from "../../../../shared/lifecycle/deferred-task"; export interface ChatViewDeferredTasks { scheduleDiagnostics(callback: () => void): void; diff --git a/src/features/chat/host/shared-state-binding.ts b/src/features/chat/host/session/shared-state-binding.ts similarity index 87% rename from src/features/chat/host/shared-state-binding.ts rename to src/features/chat/host/session/shared-state-binding.ts index b8d6dcfc..dfa73bde 100644 --- a/src/features/chat/host/shared-state-binding.ts +++ b/src/features/chat/host/session/shared-state-binding.ts @@ -1,10 +1,10 @@ -import type { ModelMetadata } from "../../../domain/catalog/metadata"; -import type { SharedServerMetadata } from "../../../domain/server/metadata"; -import type { Thread } from "../../../domain/threads/model"; -import type { ObservedResult } from "../../../shared/query/observed-result"; -import { observedValue } from "../../../shared/query/observed-result"; -import type { ChatStateStore } from "../application/state/store"; -import type { ChatPanelConnectionBundle } from "./connection-bundle"; +import type { ModelMetadata } from "../../../../domain/catalog/metadata"; +import type { SharedServerMetadata } from "../../../../domain/server/metadata"; +import type { Thread } from "../../../../domain/threads/model"; +import type { ObservedResult } from "../../../../shared/query/observed-result"; +import { observedValue } from "../../../../shared/query/observed-result"; +import type { ChatStateStore } from "../../application/state/store"; +import type { ChatPanelConnectionBundle } from "../bundles/connection-bundle"; type ThreadObserver = (result: ObservedResult) => void; type MetadataObserver = (result: ObservedResult) => void; diff --git a/tests/features/chat/host/composer-attachments.test.ts b/tests/features/chat/host/composer-attachments.test.ts index ea8cbfbb..16a7851a 100644 --- a/tests/features/chat/host/composer-attachments.test.ts +++ b/tests/features/chat/host/composer-attachments.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; -import { createVaultComposerAttachmentHandler } from "../../../../src/features/chat/host/composer-attachments.obsidian"; +import { createVaultComposerAttachmentHandler } from "../../../../src/features/chat/host/obsidian/composer-attachments.obsidian"; describe("vault composer attachments", () => { it("saves unnamed pasted images with a generated filename and Obsidian embed marker", async () => { diff --git a/tests/features/chat/host/connection-bundle.test.ts b/tests/features/chat/host/connection-bundle.test.ts index 73e519da..de216fcc 100644 --- a/tests/features/chat/host/connection-bundle.test.ts +++ b/tests/features/chat/host/connection-bundle.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { scheduleDeferredDiagnosticsRefresh } from "../../../../src/features/chat/host/connection-bundle"; +import { scheduleDeferredDiagnosticsRefresh } from "../../../../src/features/chat/host/bundles/connection-bundle"; describe("connection bundle deferred diagnostics", () => { it("reports deferred diagnostics failures without returning a blocking promise", async () => { diff --git a/tests/features/chat/host/deferred-work.test.ts b/tests/features/chat/host/deferred-work.test.ts index e8feb2d3..906697a9 100644 --- a/tests/features/chat/host/deferred-work.test.ts +++ b/tests/features/chat/host/deferred-work.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/deferred-work"; +import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/session/deferred-work"; describe("createChatViewDeferredTasks", () => { beforeEach(() => { diff --git a/tests/features/chat/host/reconnect-action.test.ts b/tests/features/chat/host/reconnect-action.test.ts new file mode 100644 index 00000000..0187c3af --- /dev/null +++ b/tests/features/chat/host/reconnect-action.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it, vi } from "vitest"; + +import { ConnectionManager } from "../../../../src/app-server/connection/connection-manager"; +import { createChatStateStore } from "../../../../src/features/chat/application/state/store"; +import { createReconnectAction } from "../../../../src/features/chat/host/bundles/reconnect-bundle"; +import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/session/deferred-work"; +import { ConnectionWorkTracker } from "../../../../src/shared/lifecycle/connection-work"; + +describe("createReconnectAction", () => { + it("wires host connection lifecycle cleanup into panel reconnect", async () => { + const stateStore = createChatStateStore(); + stateStore.dispatch({ + type: "active-thread/resumed", + thread: { id: "thread-1" } as never, + cwd: "/vault", + model: null, + reasoningEffort: null, + serviceTier: null, + approvalsReviewer: null, + }); + const connectionWork = new ConnectionWorkTracker(); + const activeConnectionWork = connectionWork.begin(); + const deferredTasks = createChatViewDeferredTasks(() => ({ setTimeout, clearTimeout }) as Pick); + const connection = new ConnectionManager(() => "codex", "/vault"); + const resetConnection = vi.spyOn(connection, "resetConnection"); + const clearDiagnostics = vi.spyOn(deferredTasks, "clearDiagnostics"); + const status = { + set: vi.fn(), + addSystemMessage: vi.fn(), + }; + const ensureConnected = vi.fn().mockResolvedValue(undefined); + const resumeThread = vi.fn().mockResolvedValue(undefined); + const reconnect = createReconnectAction( + { + stateStore, + connectionWork, + deferredTasks, + }, + { + connection, + ensureConnected, + invalidateThreadWork: vi.fn(), + resumeThread, + status, + }, + ); + + await reconnect(); + + expect(connectionWork.isStale(activeConnectionWork)).toBe(true); + expect(clearDiagnostics).toHaveBeenCalledOnce(); + expect(resetConnection).toHaveBeenCalledOnce(); + expect(status.set).toHaveBeenCalledWith("Reconnecting...", { kind: "connecting" }); + expect(ensureConnected).toHaveBeenCalledOnce(); + expect(resumeThread).toHaveBeenCalledWith("thread-1"); + }); +}); diff --git a/tests/features/chat/host/session-graph.test.ts b/tests/features/chat/host/session-graph.test.ts index adb7c8b9..f01d4ca0 100644 --- a/tests/features/chat/host/session-graph.test.ts +++ b/tests/features/chat/host/session-graph.test.ts @@ -9,7 +9,7 @@ import { type ChatStateStore, createChatStateStore } from "../../../../src/featu import { HistoryController } from "../../../../src/features/chat/application/threads/history-controller"; import { ChatResumeWorkTracker } from "../../../../src/features/chat/application/threads/resume-work"; import type { ChatPanelEnvironment } from "../../../../src/features/chat/host/contracts"; -import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/deferred-work"; +import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/session/deferred-work"; import { createChatPanelSessionGraph } from "../../../../src/features/chat/host/session-graph"; import { ChatComposerController } from "../../../../src/features/chat/panel/composer-controller"; import { MessageStreamPresenter } from "../../../../src/features/chat/panel/surface/message-stream-presenter"; diff --git a/tests/features/chat/host/turn-bundle.test.ts b/tests/features/chat/host/turn-bundle.test.ts index e5bdc6fc..41c34a61 100644 --- a/tests/features/chat/host/turn-bundle.test.ts +++ b/tests/features/chat/host/turn-bundle.test.ts @@ -3,8 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { createServerDiagnostics, diagnosticsWithToolInventory } from "../../../../src/domain/server/diagnostics"; import type { ToolInventorySnapshot } from "../../../../src/domain/server/tool-inventory"; import { createChatStateStore } from "../../../../src/features/chat/application/state/store"; -import { createTurnBundle } from "../../../../src/features/chat/host/turn-bundle"; -import { ConnectionWorkTracker } from "../../../../src/shared/lifecycle/connection-work"; +import { createTurnBundle } from "../../../../src/features/chat/host/bundles/turn-bundle"; describe("createTurnBundle", () => { it("uses cached tool inventory for /tools without refreshing diagnostics", async () => { @@ -51,24 +50,13 @@ function turnBundleFixture(options: { stateStore?: ReturnType "local-id") }, - ensureConnected: vi.fn().mockResolvedValue(undefined), appServer: { connectionAvailable: vi.fn(() => true), threadReferences: vi.fn(() => ({ referThread: vi.fn() })), @@ -100,7 +88,7 @@ function turnBundleFixture(options: { stateStore?: ReturnType { it("builds note candidates from markdown files", () => { diff --git a/tests/scripts/grit-policy.test.mjs b/tests/scripts/grit-policy.test.mjs index e025d02a..4baa5d3c 100644 --- a/tests/scripts/grit-policy.test.mjs +++ b/tests/scripts/grit-policy.test.mjs @@ -1,6 +1,6 @@ import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; -import { copyFile, mkdir, mkdtemp, readdir, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readdir, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; @@ -19,8 +19,6 @@ const APP_SERVER_PROTOCOL_BOUNDARY_MESSAGE = "Source modules outside root src/app-server must use domain models and app-server services instead of app-server protocol modules. Chat turn-item conversion may consume turn protocol at its app-server boundary; feature state and UI must use Panel-owned models."; const RESPONSIBILITY_ROOT_MODULE_FILE_MESSAGE = "Keep responsibility-split source roots free of module files; add modules to the matching subfolder instead of the root."; -const APP_SERVER_ROOT_MODULE_IMPORT_MESSAGE = - "Import app-server boundary modules from responsibility subfolders, not src/app-server root modules."; const APP_SERVER_SUBFOLDER_ROOT_IMPORT_MESSAGE = "App-server subfolders must not import sibling root modules; move the dependency into a responsibility subfolder."; const SETTINGS_APP_SERVER_BOUNDARY_MESSAGE = @@ -889,26 +887,14 @@ export const value = statusText; expect(pluginDiagnostics(report, "src/settings/app-server/dynamic-data.ts")).toEqual([]); }); - it("keeps app-server root from becoming a boundary escape hatch", async () => { - const cwd = await tempBiomeWorkspace([ - "no-responsibility-root-module-files.grit", - "no-app-server-root-module-imports.grit", - "no-app-server-subfolder-root-imports.grit", - ]); + it("keeps app-server root modules from becoming boundary escape hatches", async () => { + const cwd = await tempBiomeWorkspace(["no-responsibility-root-module-files.grit", "no-app-server-subfolder-root-imports.grit"]); await writeFile( path.join(cwd, "src/app-server/escape.ts"), ` import type { AppServerClient } from "./connection/client"; export type Escape = AppServerClient; -`.trimStart(), - ); - await writeFile( - path.join(cwd, "src/features/chat/app-server/root-import.ts"), - ` -import { listThreads } from "../../../../app-server/threads"; - -export const read = listThreads; `.trimStart(), ); await writeFile( @@ -916,6 +902,22 @@ export const read = listThreads; ` import { createChatAppServerGateway } from "../app-server/session-gateway"; +export const gateway = createChatAppServerGateway; +`.trimStart(), + ); + await writeFile( + path.join(cwd, "src/features/chat/host/bundles/chat-app-server-root-import.ts"), + ` +import { createChatAppServerGateway } from "../../app-server/session-gateway"; + +export const gateway = createChatAppServerGateway; +`.trimStart(), + ); + await writeFile( + path.join(cwd, "src/features/chat/host/session/chat-app-server-root-import.ts"), + ` +import { createChatAppServerGateway } from "../../app-server/session-gateway"; + export const gateway = createChatAppServerGateway; `.trimStart(), ); @@ -941,8 +943,9 @@ export const read = listThreads; const report = biomeLint( [ "src/app-server/escape.ts", - "src/features/chat/app-server/root-import.ts", "src/features/chat/host/chat-app-server-root-import.ts", + "src/features/chat/host/bundles/chat-app-server-root-import.ts", + "src/features/chat/host/session/chat-app-server-root-import.ts", "src/app-server/services/root-import.ts", "src/app-server/services/allowed.ts", ], @@ -950,8 +953,9 @@ export const read = listThreads; ); expect(pluginMessages(report, "src/app-server/escape.ts")).toEqual([RESPONSIBILITY_ROOT_MODULE_FILE_MESSAGE]); - expect(pluginMessages(report, "src/features/chat/app-server/root-import.ts")).toEqual([APP_SERVER_ROOT_MODULE_IMPORT_MESSAGE]); expect(pluginDiagnostics(report, "src/features/chat/host/chat-app-server-root-import.ts")).toEqual([]); + expect(pluginDiagnostics(report, "src/features/chat/host/bundles/chat-app-server-root-import.ts")).toEqual([]); + expect(pluginDiagnostics(report, "src/features/chat/host/session/chat-app-server-root-import.ts")).toEqual([]); expect(pluginMessages(report, "src/app-server/services/root-import.ts")).toEqual([APP_SERVER_SUBFOLDER_ROOT_IMPORT_MESSAGE]); expect(pluginDiagnostics(report, "src/app-server/services/allowed.ts")).toEqual([]); }); @@ -963,7 +967,7 @@ export const read = listThreads; "Keep app-server projection RPCs behind app-server facades; consume Panel-owned snapshots or view models here.", ]); expect(pluginDiagnostics(report, "src/app-server/services/threads.ts")).toEqual([]); - expect(pluginDiagnostics(report, "src/features/chat/host/connection-bundle.ts")).toEqual([]); + expect(pluginDiagnostics(report, "src/features/chat/host/bundles/connection-bundle.ts")).toEqual([]); }); it("keeps CSS on design tokens and scoped selectors", async () => { @@ -1397,9 +1401,9 @@ export async function read(client: AppServerClient): Promise { `.trimStart(), ); await writeFile( - path.join(cwd, "src/features/chat/host/connection-bundle.ts"), + path.join(cwd, "src/features/chat/host/bundles/connection-bundle.ts"), ` -import type { AppServerClient } from "../../../app-server/connection/client"; +import type { AppServerClient } from "../../../../app-server/connection/client"; export async function resume(client: AppServerClient): Promise { await client.request("thread/resume", { threadId: "thread", cwd: "/vault" }); @@ -1462,7 +1466,7 @@ export const load = listHookCatalog; "src/shared/connection-client.ts", "src/features/chat/application/threads/history.ts", "src/app-server/services/threads.ts", - "src/features/chat/host/connection-bundle.ts", + "src/features/chat/host/bundles/connection-bundle.ts", "src/settings/dynamic-sections-controller.ts", "src/settings/adapter-leak.ts", "src/settings/app-server/dynamic-data.ts", @@ -1473,7 +1477,6 @@ export const load = listHookCatalog; async function tempBiomeWorkspace(plugins) { const cwd = await mkdtemp(path.join(tmpdir(), "codex-panel-grit-policy-")); - const tempPlugins = await Promise.all(plugins.map((plugin) => tempProjectPluginConfig(cwd, plugin))); await mkdir(cwd, { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/domain/message-stream"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/application/state"), { recursive: true }); @@ -1482,6 +1485,8 @@ async function tempBiomeWorkspace(plugins) { await mkdir(path.join(cwd, "src/features/chat/app-server/inbound"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/app-server/mappers/message-stream"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/host"), { recursive: true }); + await mkdir(path.join(cwd, "src/features/chat/host/bundles"), { recursive: true }); + await mkdir(path.join(cwd, "src/features/chat/host/session"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/panel"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/panel/surface"), { recursive: true }); await mkdir(path.join(cwd, "src/features/chat/presentation"), { recursive: true }); @@ -1506,7 +1511,7 @@ async function tempBiomeWorkspace(plugins) { JSON.stringify({ $schema: "https://biomejs.dev/schemas/2.5.1/schema.json", vcs: { enabled: false }, - plugins: tempPlugins, + plugins: plugins.map((plugin) => projectPluginConfig(plugin)), css: { linter: { enabled: true } }, }), ); @@ -1530,20 +1535,16 @@ function biomeLint(files, cwd, options = {}) { return report; } -async function tempProjectPluginConfig(cwd, plugin) { +function projectPluginConfig(plugin) { const projectPlugin = projectPluginByName.get(plugin); if (!projectPlugin) { throw new Error(`Missing ${plugin} in biome.jsonc plugins`); } - const projectPluginRelativePath = normalizeProjectRelativePath(projectPluginPath(projectPlugin)); - const projectPluginPathAbsolute = path.resolve(repoRoot, projectPluginPath(projectPlugin)); - const tempPluginPath = path.join(cwd, projectPluginRelativePath); - await mkdir(path.dirname(tempPluginPath), { recursive: true }); - await copyFile(projectPluginPathAbsolute, tempPluginPath); + const pluginPath = path.resolve(repoRoot, projectPluginPath(projectPlugin)); if (typeof projectPlugin === "string") { - return tempPluginPath; + return pluginPath; } - return { ...projectPlugin, path: tempPluginPath }; + return { ...projectPlugin, path: pluginPath }; } function projectPluginPath(plugin) {