Clarify chat module ownership

This commit is contained in:
murashit 2026-06-07 13:44:25 +09:00
parent 8c80930124
commit e6daf9db11
24 changed files with 160 additions and 157 deletions

View file

@ -90,12 +90,10 @@ const pureChatModelRestrictions = [
},
];
const chatImperativeDomBridgeFiles = [
"src/features/chat/chat-message-renderer.ts",
"src/features/chat/markdown-message-renderer.ts",
"src/features/chat/rendered-markdown-links.ts",
"src/features/chat/ui/composer.tsx",
"src/features/chat/ui/goal-banner.tsx",
"src/features/chat/ui/message-stream.tsx",
"src/features/chat/ui/message-stream/markdown-renderer.ts",
"src/features/chat/ui/message-stream/rendered-markdown-links.ts",
"src/features/chat/ui/message-stream/**/*.tsx",
"src/features/chat/ui/scroll.ts",
"src/features/chat/ui/shell.tsx",

View file

@ -1,9 +1,13 @@
import type { App, EventRef } from "obsidian";
import { composerBoundaryScrollDirection, type ComposerBoundaryScrollAction } from "./composer/boundary-scroll";
import { isComposerSendKey, type SendShortcut } from "../../shared/ui/keyboard";
import { textareaCursorAtVisualBoundary } from "../../shared/ui/textarea-caret";
import { noteCandidates as appNoteCandidates, resolveWikiLinkMention as resolveAppWikiLinkMention } from "./composer/obsidian-context";
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
import { isComposerSendKey, type SendShortcut } from "../../../shared/ui/keyboard";
import { textareaCursorAtVisualBoundary } from "../../../shared/ui/textarea-caret";
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "../chat-state";
import type { ComposerMetaViewModel } from "../panel/model";
import { renderComposerShell, syncComposerHeight } from "../ui/composer";
import { composerBoundaryScrollDirection, type ComposerBoundaryScrollAction } from "./boundary-scroll";
import { noteCandidates as appNoteCandidates, resolveWikiLinkMention as resolveAppWikiLinkMention } from "./obsidian-context";
import {
activeComposerSuggestions,
applyComposerSuggestionInsertion,
@ -12,12 +16,8 @@ import {
nextComposerSuggestionIndex,
type ComposerSuggestion,
type NoteCandidate,
} from "./composer/suggestions";
import { userInputWithWikiLinkMentionsAndSkills } from "./composer/wikilink-context";
import type { UserInput } from "../../generated/app-server/v2/UserInput";
import { renderComposerShell, syncComposerHeight } from "./ui/composer";
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
import type { ComposerMetaViewModel } from "./panel/model";
} from "./suggestions";
import { userInputWithWikiLinkMentionsAndSkills } from "./wikilink-context";
export interface ChatComposerControllerOptions {
app: App;

View file

@ -1,7 +1,7 @@
import type { ComponentChild as UiNode } from "preact";
import type { ApprovalAction, PendingApproval } from "../../requests/approvals/model";
import type { ChatController } from "../../chat-controller";
import type { ChatInboundController } from "../../inbound/controller";
import { pendingRequestFocusSignature } from "../../requests/view-model";
import { pendingRequestMessageNode } from "../../ui/pending-request-message";
import { userInputDraftKey, userInputOtherDraftKey } from "../../requests/user-input/drafts";
@ -10,7 +10,7 @@ import type { PendingRequestStatePort } from "../state-ports";
export interface PendingRequestControllerHost {
state: PendingRequestStatePort;
controller: ChatController;
controller: ChatInboundController;
composerHasFocus: () => boolean;
refreshLiveState: () => void;
render: () => void;

View file

@ -1,11 +1,11 @@
import type { AppServerClient } from "../../app-server/client";
import { requestedServiceTierRequestValue, type RequestedServiceTier } from "../../app-server/service-tier";
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
import type { ModeKind } from "../../generated/app-server/ModeKind";
import type { ApprovalsReviewer } from "../../generated/app-server/v2/ApprovalsReviewer";
import type { ThreadSettingsUpdateParams } from "../../generated/app-server/v2/ThreadSettingsUpdateParams";
import { collaborationModeToggleMessage, nextCollaborationMode } from "../../runtime/collaboration-mode";
import { readRuntimeConfig } from "../../runtime/config";
import type { AppServerClient } from "../../../../app-server/client";
import { requestedServiceTierRequestValue, type RequestedServiceTier } from "../../../../app-server/service-tier";
import type { ModeKind } from "../../../../generated/app-server/ModeKind";
import type { ReasoningEffort } from "../../../../generated/app-server/ReasoningEffort";
import type { ApprovalsReviewer } from "../../../../generated/app-server/v2/ApprovalsReviewer";
import type { ThreadSettingsUpdateParams } from "../../../../generated/app-server/v2/ThreadSettingsUpdateParams";
import { collaborationModeToggleMessage, nextCollaborationMode } from "../../../../runtime/collaboration-mode";
import { readRuntimeConfig } from "../../../../runtime/config";
import {
autoReviewActive,
fastModeActive,
@ -13,9 +13,9 @@ import {
pendingRuntimeSettingPayload,
requestedTurnRuntimeSettings,
type RuntimeSnapshot,
} from "../../runtime/state";
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../runtime/settings";
import type { ChatAction, ChatState, ChatStateStore } from "./chat-state";
} from "../../../../runtime/state";
import { modelOverrideMessage, reasoningEffortOverrideMessage } from "../../../../runtime/settings";
import type { ChatAction, ChatState, ChatStateStore } from "../../chat-state";
type ThreadSettingsUpdate = Omit<ThreadSettingsUpdateParams, "threadId">;

View file

@ -8,7 +8,7 @@ import type { CodexPanelSettings } from "../../../../settings/model";
import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "../../chat-state";
import { turnsAfterTurnId } from "../../fork";
import { rollbackCandidateFromItems } from "../../rollback";
import type { ThreadHistoryLoader } from "../../thread-history";
import type { ThreadHistoryController } from "./thread-history-controller";
export interface ChatThreadActionControllerHost {
stateStore: ChatStateStore;
@ -17,7 +17,7 @@ export interface ChatThreadActionControllerHost {
archiveAdapter: () => ArchiveExportAdapter;
ensureConnected: () => Promise<void>;
currentClient: () => AppServerClient | null;
history: ThreadHistoryLoader;
history: ThreadHistoryController;
addSystemMessage: (text: string) => void;
setStatus: (status: string) => void;
setComposerText: (text: string) => void;

View file

@ -1,12 +1,12 @@
import type { AppServerClient } from "../../app-server/client";
import type { JsonValue } from "../../generated/app-server/serde_json/JsonValue";
import type { ThreadGoal } from "../../generated/app-server/v2/ThreadGoal";
import type { ThreadGoalStatus } from "../../generated/app-server/v2/ThreadGoalStatus";
import type { ChatStateStore } from "./chat-state";
import type { GoalDisplayItem } from "./display/types";
import { goalChangeItem } from "./goal-messages";
import type { AppServerClient } from "../../../../app-server/client";
import type { JsonValue } from "../../../../generated/app-server/serde_json/JsonValue";
import type { ThreadGoal } from "../../../../generated/app-server/v2/ThreadGoal";
import type { ThreadGoalStatus } from "../../../../generated/app-server/v2/ThreadGoalStatus";
import type { ChatStateStore } from "../../chat-state";
import type { GoalDisplayItem } from "../../display/types";
import { goalChangeItem } from "../../goal-messages";
export interface ChatGoalControllerHost {
export interface ChatThreadGoalControllerHost {
stateStore: ChatStateStore;
currentClient: () => AppServerClient | null;
ensureConnected: () => Promise<void>;
@ -16,8 +16,8 @@ export interface ChatGoalControllerHost {
refreshLiveState: () => void;
}
export class ChatGoalController {
constructor(private readonly host: ChatGoalControllerHost) {}
export class ChatThreadGoalController {
constructor(private readonly host: ChatThreadGoalControllerHost) {}
activeGoal(): ThreadGoal | null {
return this.host.stateStore.getState().activeThread.goal;

View file

@ -1,9 +1,9 @@
import type { AppServerClient } from "../../app-server/client";
import { displayItemsFromTurns } from "./display/thread-items";
import type { ChatAction, ChatState, ChatStateStore } from "./chat-state";
import type { TurnsPage } from "../../generated/app-server/v2/TurnsPage";
import type { AppServerClient } from "../../../../app-server/client";
import type { TurnsPage } from "../../../../generated/app-server/v2/TurnsPage";
import type { ChatAction, ChatState, ChatStateStore } from "../../chat-state";
import { displayItemsFromTurns } from "../../display/thread-items";
export interface ThreadHistoryLoaderHost {
export interface ThreadHistoryControllerHost {
stateStore: ChatStateStore;
currentClient: () => AppServerClient | null;
render: () => void;
@ -20,10 +20,10 @@ type ThreadHistoryLoadLifecycleEvent =
| { type: "finished"; load: ActiveThreadHistoryLoad }
| { type: "invalidated" };
export class ThreadHistoryLoader {
export class ThreadHistoryController {
private lifecycle: ThreadHistoryLoadLifecycleState = { kind: "idle" };
constructor(private readonly host: ThreadHistoryLoaderHost) {}
constructor(private readonly host: ThreadHistoryControllerHost) {}
private get state(): ChatState {
return this.host.stateStore.getState();

View file

@ -3,7 +3,7 @@ import type { ThreadTokenUsage } from "../../../../generated/app-server/v2/Threa
import type { DisplayItem } from "../../display/types";
import type { RestoredThreadController } from "./restored-thread-controller";
import type { ThreadActivationResponse } from "../../thread-resume";
import type { ThreadHistoryLoader } from "../../thread-history";
import type { ThreadHistoryController } from "./thread-history-controller";
import type { ChatResumeWorkTracker, ActiveChatResume } from "../../panel/lifecycle";
import type { ThreadLifecycleStatePort } from "../state-ports";
@ -11,7 +11,7 @@ export interface ThreadResumeControllerHost {
state: ThreadLifecycleStatePort;
vaultPath: string;
resumeWork: ChatResumeWorkTracker;
history: ThreadHistoryLoader;
history: ThreadHistoryController;
restoredThread: RestoredThreadController;
currentClient: () => AppServerClient | null;
ensureConnected: () => Promise<void>;

View file

@ -1,18 +1,18 @@
import { approvalResponse, type ApprovalAction, type PendingApproval } from "./requests/approvals/model";
import { createStructuredSystemItem, createSystemItem } from "./display/system";
import type { DisplayDetailSection } from "./display/types";
import type { RequestId } from "../../generated/app-server/RequestId";
import type { ServerNotification } from "../../generated/app-server/ServerNotification";
import type { ServerRequest } from "../../generated/app-server/ServerRequest";
import type { Turn } from "../../generated/app-server/v2/Turn";
import { activeTurnId, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
import { userInputResponse, type PendingUserInput } from "./requests/user-input/model";
import { classifyAppServerLog } from "./app-server-logs";
import { routeServerRequest } from "./inbound/routing";
import { createApprovalResultItem, createUserInputResultItem } from "./requests/view-model";
import { planChatNotification, type ChatNotificationEffect } from "./inbound/notification-plan";
import type { RequestId } from "../../../generated/app-server/RequestId";
import type { ServerNotification } from "../../../generated/app-server/ServerNotification";
import type { ServerRequest } from "../../../generated/app-server/ServerRequest";
import type { Turn } from "../../../generated/app-server/v2/Turn";
import { classifyAppServerLog } from "../app-server-logs";
import { activeTurnId, type ChatAction, type ChatState, type ChatStateStore } from "../chat-state";
import { createStructuredSystemItem, createSystemItem } from "../display/system";
import type { DisplayDetailSection } from "../display/types";
import { approvalResponse, type ApprovalAction, type PendingApproval } from "../requests/approvals/model";
import { userInputResponse, type PendingUserInput } from "../requests/user-input/model";
import { createApprovalResultItem, createUserInputResultItem } from "../requests/view-model";
import { planChatNotification, type ChatNotificationEffect } from "./notification-plan";
import { routeServerRequest } from "./routing";
export interface ChatControllerActions {
export interface ChatInboundControllerActions {
refreshThreads: () => void;
refreshRateLimits: () => void;
refreshSkills: (forceReload?: boolean) => void;
@ -25,10 +25,10 @@ export interface ChatControllerActions {
rejectServerRequest: (requestId: RequestId, code: number, message: string) => boolean;
}
export class ChatController {
export class ChatInboundController {
constructor(
private readonly store: ChatStateStore,
private readonly actions: ChatControllerActions,
private readonly actions: ChatInboundControllerActions,
) {}
private get state(): ChatState {

View file

@ -6,16 +6,15 @@ import { currentModel } from "../../../runtime/state";
import { ChatAppServerDiagnosticsController } from "../app-server/diagnostics-controller";
import { ChatAppServerMetadataController } from "../app-server/metadata-controller";
import { ChatAppServerThreadController } from "../app-server/thread-controller";
import { ChatComposerController } from "../chat-composer-controller";
import { ChatController } from "../chat-controller";
import { ChatMessageRenderer } from "../chat-message-renderer";
import { ChatComposerController } from "../composer/controller";
import { ChatInboundController } from "../inbound/controller";
import type { ChatState } from "../chat-state";
import { ChatGoalController } from "../goal-controller";
import { ChatRuntimeSettingsController } from "../runtime-settings-controller";
import { ChatThreadGoalController } from "../controllers/thread/thread-goal-controller";
import { ChatRuntimeSettingsController } from "../controllers/runtime/runtime-settings-controller";
import { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
import { ThreadHistoryLoader } from "../thread-history";
import { ThreadHistoryController } from "../controllers/thread/thread-history-controller";
import { ThreadRenameController } from "../controllers/thread/thread-rename-controller";
import { ToolbarPanelController } from "../toolbar-panel-controller";
import { ToolbarPanelController } from "./toolbar-controller";
import { AppServerWarmupController } from "../controllers/connection/app-server-warmup-controller";
import { ChatConnectionController } from "../controllers/connection/connection-controller";
import { ChatReconnectController } from "../controllers/connection/reconnect-controller";
@ -40,6 +39,7 @@ import { ThreadSelectionController } from "../controllers/thread/thread-selectio
import { ChatViewOpenCloseController } from "../controllers/view/view-open-close-controller";
import { ChatViewRenderController } from "../controllers/view/view-render-controller";
import { ChatViewStateController } from "../controllers/view/view-state-controller";
import { ChatMessageRenderer } from "../ui/message-stream";
import type { ChatViewControllerPorts } from "./controller-ports";
export interface ChatViewControllers {
@ -50,7 +50,7 @@ export interface ChatViewControllers {
warmup: AppServerWarmupController;
};
inbound: {
controller: ChatController;
controller: ChatInboundController;
};
appServer: {
threads: ChatAppServerThreadController;
@ -58,7 +58,7 @@ export interface ChatViewControllers {
diagnostics: ChatAppServerDiagnosticsController;
};
thread: {
history: ThreadHistoryLoader;
history: ThreadHistoryController;
resume: ThreadResumeController;
actions: ChatThreadActionController;
restored: RestoredThreadController;
@ -68,7 +68,7 @@ export interface ChatViewControllers {
};
runtime: {
settings: ChatRuntimeSettingsController;
goals: ChatGoalController;
goals: ChatThreadGoalController;
};
requests: {
pending: PendingRequestController;
@ -94,16 +94,16 @@ type ControllerRef<T> = () => T;
export function createChatViewControllers(ports: ChatViewControllerPorts): ChatViewControllers {
const context = createControllerContext(ports);
let connection!: ConnectionManager;
let controller!: ChatController;
let controller!: ChatInboundController;
let appServerThreads!: ChatAppServerThreadController;
let appServerMetadata!: ChatAppServerMetadataController;
let appServerDiagnostics!: ChatAppServerDiagnosticsController;
let connectionController!: ChatConnectionController;
let history!: ThreadHistoryLoader;
let history!: ThreadHistoryController;
let threadResume!: ThreadResumeController;
let threadActions!: ChatThreadActionController;
let runtimeSettings!: ChatRuntimeSettingsController;
let goals!: ChatGoalController;
let goals!: ChatThreadGoalController;
let restoredThread!: RestoredThreadController;
let threadIdentity!: ThreadIdentityController;
let threadRename!: ThreadRenameController;
@ -294,8 +294,8 @@ function createSubmissionControllerGroup(
threadActions: ControllerRef<ChatThreadActionController>;
threadRename: ControllerRef<ThreadRenameController>;
reconnectActions: ControllerRef<ChatReconnectController>;
goals: ControllerRef<ChatGoalController>;
history: ControllerRef<ThreadHistoryLoader>;
goals: ControllerRef<ChatThreadGoalController>;
history: ControllerRef<ThreadHistoryController>;
pendingRequests: ControllerRef<PendingRequestController>;
},
) {
@ -474,7 +474,7 @@ function createSubmissionControllerGroup(
function createConnectionLifecycleControllerGroup(
context: ControllerContext,
refs: {
controller: ControllerRef<ChatController>;
controller: ControllerRef<ChatInboundController>;
connectionController: ControllerRef<ChatConnectionController>;
composerController: ControllerRef<ChatComposerController>;
messageRenderer: ControllerRef<ChatMessageRenderer>;
@ -557,7 +557,7 @@ function createAppServerControllerGroup(
context: ControllerContext,
refs: {
connection: ControllerRef<ConnectionManager>;
goals: ControllerRef<ChatGoalController>;
goals: ControllerRef<ChatThreadGoalController>;
},
) {
const { plugin, runtime, effects, stateStore } = context;
@ -605,7 +605,7 @@ function createInboundController(
) {
const { plugin, thread, effects, stateStore } = context;
return new ChatController(stateStore, {
return new ChatInboundController(stateStore, {
refreshThreads: () => {
void thread.refreshThreads();
},
@ -675,7 +675,7 @@ function createRequestThreadToolbarControllerGroup(
context: ControllerContext,
refs: {
connection: ControllerRef<ConnectionManager>;
controller: ControllerRef<ChatController>;
controller: ControllerRef<ChatInboundController>;
composerController: ControllerRef<ChatComposerController>;
},
) {
@ -690,7 +690,7 @@ function createRequestThreadToolbarControllerGroup(
refreshLiveState: effects.liveState.refresh,
render: effects.render.now,
});
const history = new ThreadHistoryLoader({
const history = new ThreadHistoryController({
stateStore,
currentClient,
render: effects.render.now,
@ -761,7 +761,7 @@ function createRequestThreadToolbarControllerGroup(
collaborationModeLabel: runtime.collaborationModeLabel,
addSystemMessage: effects.status.addSystemMessage,
});
const goals = new ChatGoalController({
const goals = new ChatThreadGoalController({
stateStore,
currentClient,
ensureConnected: effects.client.ensureConnected,

View file

@ -1,6 +1,6 @@
import type { ChatAction, ChatState, ChatStateStore } from "./chat-state";
import type { ChatThreadActionController } from "./controllers/thread/thread-actions-controller";
import type { ChatViewRenderScheduleOptions } from "./panel/lifecycle";
import type { ChatAction, ChatState, ChatStateStore } from "../chat-state";
import type { ChatThreadActionController } from "../controllers/thread/thread-actions-controller";
import type { ChatViewRenderScheduleOptions } from "./lifecycle";
export interface ToolbarPanelControllerHost {
stateStore: ChatStateStore;

View file

@ -1,10 +1,6 @@
export {
createMessageStreamContext,
type ChatMessageStreamActionPort,
type ChatMessageStreamContextPort,
type ChatMessageStreamRequestPort,
} from "./context-builder";
export { createMessageStreamContextPort } from "./context-port";
export { messageStreamBlocks } from "./blocks";
export { renderMessageStreamBlocks } from "./render";
export { ChatMessageRenderer } from "./renderer";
export { bindRenderedWikiLinks, type RenderedMarkdownLinkContext } from "./rendered-markdown-links";
export type { MessageStreamBlock } from "./context";

View file

@ -1,7 +1,7 @@
import { MarkdownRenderer, type App, type Component } from "obsidian";
import { notifyMessageContentRendered } from "../message-content-events";
import { bindRenderedMarkdownFileLinks, bindRenderedWikiLinks } from "./rendered-markdown-links";
import { notifyMessageContentRendered } from "./ui/message-content-events";
export interface MarkdownMessageRendererOptions {
app: App;

View file

@ -1,6 +1,6 @@
import { Notice, type App } from "obsidian";
import { isAbsoluteFileHref, vaultFileLinkTarget, vaultRelativeFileLinkTarget } from "./markdown-file-links";
import { isAbsoluteFileHref, vaultFileLinkTarget, vaultRelativeFileLinkTarget } from "../../markdown-file-links";
export interface RenderedMarkdownLinkContext {
app: App;

View file

@ -1,20 +1,20 @@
import type { App, Component } from "obsidian";
import { copyTextWithNotice } from "../../shared/ui/clipboard";
import { copyTextWithNotice } from "../../../../shared/ui/clipboard";
import { unmountUiRoot } from "../../../../shared/ui/ui-root";
import type { ChatAction, ChatState, ChatStateStore } from "../../chat-state";
import type { ComposerBoundaryScrollAction } from "../../composer/boundary-scroll";
import { MessageScrollController, type MessageScrollIntent } from "../scroll";
import { messageStreamBlocks } from "./blocks";
import {
createMessageStreamContext,
createMessageStreamContextPort,
messageStreamBlocks,
renderMessageStreamBlocks,
type ChatMessageStreamActionPort,
type ChatMessageStreamContextPort,
type ChatMessageStreamRequestPort,
} from "./ui/message-stream";
import type { ComposerBoundaryScrollAction } from "./composer/boundary-scroll";
import { MessageScrollController, type MessageScrollIntent } from "./ui/scroll";
import { MarkdownMessageRenderer } from "./markdown-message-renderer";
import { type ChatAction, type ChatState, type ChatStateStore } from "./chat-state";
import { unmountUiRoot } from "../../shared/ui/ui-root";
} from "./context-builder";
import { createMessageStreamContextPort } from "./context-port";
import { MarkdownMessageRenderer } from "./markdown-renderer";
import { renderMessageStreamBlocks } from "./render";
interface ChatMessageRendererObsidianPort {
app: App;

View file

@ -3,10 +3,10 @@
import type { App } from "obsidian";
import { describe, expect, it, vi } from "vitest";
import { ChatComposerController } from "../../../src/features/chat/chat-composer-controller";
import { createChatStateStore } from "../../../src/features/chat/chat-state";
import type { SkillMetadata } from "../../../src/generated/app-server/v2/SkillMetadata";
import { installObsidianDomShims } from "../../support/dom";
import { ChatComposerController } from "../../../../src/features/chat/composer/controller";
import { createChatStateStore } from "../../../../src/features/chat/chat-state";
import type { SkillMetadata } from "../../../../src/generated/app-server/v2/SkillMetadata";
import { installObsidianDomShims } from "../../../support/dom";
installObsidianDomShims();

View file

@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { ChatController } from "../../../../../src/features/chat/chat-controller";
import { ChatInboundController } from "../../../../../src/features/chat/inbound/controller";
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
import { createPendingRequestStatePort } from "../../../../../src/features/chat/controllers/state-ports";
import { PendingRequestController } from "../../../../../src/features/chat/controllers/requests/pending-request-controller";
@ -18,7 +18,7 @@ describe("PendingRequestController", () => {
const respondToServerRequest = vi.fn().mockReturnValue(true);
const refreshLiveState = vi.fn();
const render = vi.fn();
const controller = new ChatController(stateStore, {
const controller = new ChatInboundController(stateStore, {
refreshThreads: vi.fn(),
refreshRateLimits: vi.fn(),
refreshSkills: vi.fn(),

View file

@ -1,9 +1,9 @@
import { describe, expect, it, vi } from "vitest";
import { ChatRuntimeSettingsController } from "../../../src/features/chat/runtime-settings-controller";
import { createChatState, createChatStateStore, type ActiveThreadSettingsAppliedAction } from "../../../src/features/chat/chat-state";
import type { AppServerClient } from "../../../src/app-server/client";
import type { Model } from "../../../src/generated/app-server/v2/Model";
import { ChatRuntimeSettingsController } from "../../../../../src/features/chat/controllers/runtime/runtime-settings-controller";
import { createChatState, createChatStateStore, type ActiveThreadSettingsAppliedAction } from "../../../../../src/features/chat/chat-state";
import type { AppServerClient } from "../../../../../src/app-server/client";
import type { Model } from "../../../../../src/generated/app-server/v2/Model";
describe("ChatRuntimeSettingsController", () => {
it("applies pending runtime overrides through thread settings and commits them", async () => {

View file

@ -1,11 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import type { AppServerClient } from "../../../src/app-server/client";
import { ChatGoalController } from "../../../src/features/chat/goal-controller";
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
import type { ThreadGoal } from "../../../src/generated/app-server/v2/ThreadGoal";
import type { AppServerClient } from "../../../../../src/app-server/client";
import { ChatThreadGoalController } from "../../../../../src/features/chat/controllers/thread/thread-goal-controller";
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
import type { ThreadGoal } from "../../../../../src/generated/app-server/v2/ThreadGoal";
describe("ChatGoalController", () => {
describe("ChatThreadGoalController", () => {
it("syncs the active thread goal into chat state", async () => {
const state = createChatState();
state.activeThread.id = "thread";
@ -14,7 +14,7 @@ describe("ChatGoalController", () => {
const client = { getThreadGoal: vi.fn().mockResolvedValue({ goal: currentGoal }) } as unknown as AppServerClient;
const render = vi.fn();
const refreshLiveState = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -37,7 +37,7 @@ describe("ChatGoalController", () => {
const stateStore = createChatStateStore(state);
const addSystemMessage = vi.fn();
const client = { getThreadGoal: vi.fn().mockRejectedValue(new Error("offline")) } as unknown as AppServerClient;
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -69,7 +69,7 @@ describe("ChatGoalController", () => {
} as unknown as AppServerClient;
const addSystemMessage = vi.fn();
const addGoalEvent = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -102,7 +102,7 @@ describe("ChatGoalController", () => {
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
const addSystemMessage = vi.fn();
const addGoalEvent = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -142,7 +142,7 @@ describe("ChatGoalController", () => {
const injectThreadItems = vi.fn().mockResolvedValue({});
const client = { setThreadGoal, injectThreadItems } as unknown as AppServerClient;
const addGoalEvent = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -167,7 +167,7 @@ describe("ChatGoalController", () => {
const client = { setThreadGoal } as unknown as AppServerClient;
const addSystemMessage = vi.fn();
const addGoalEvent = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),
@ -190,7 +190,7 @@ describe("ChatGoalController", () => {
const currentGoal = goal();
const client = { getThreadGoal: vi.fn().mockResolvedValue({ goal: currentGoal }) } as unknown as AppServerClient;
const addSystemMessage = vi.fn();
const controller = new ChatGoalController({
const controller = new ChatThreadGoalController({
stateStore,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),

View file

@ -1,13 +1,13 @@
import { describe, expect, it, vi } from "vitest";
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
import { ThreadHistoryLoader } from "../../../src/features/chat/thread-history";
import type { AppServerClient } from "../../../src/app-server/client";
import type { ThreadItem } from "../../../src/generated/app-server/v2/ThreadItem";
import type { Turn } from "../../../src/generated/app-server/v2/Turn";
import { deferred } from "../../support/async";
import { createChatState, createChatStateStore } from "../../../../../src/features/chat/chat-state";
import { ThreadHistoryController } from "../../../../../src/features/chat/controllers/thread/thread-history-controller";
import type { AppServerClient } from "../../../../../src/app-server/client";
import type { ThreadItem } from "../../../../../src/generated/app-server/v2/ThreadItem";
import type { Turn } from "../../../../../src/generated/app-server/v2/Turn";
import { deferred } from "../../../../support/async";
describe("ThreadHistoryLoader", () => {
describe("ThreadHistoryController", () => {
it("keeps the latest history load when an older request resolves later", async () => {
const first = deferred<ThreadTurnsListResponse>();
const second = deferred<ThreadTurnsListResponse>();
@ -98,7 +98,7 @@ function historyFixture(options: { threadTurnsList: ReturnType<typeof vi.fn> })
const stateStore = createChatStateStore(state);
const dispatch = vi.spyOn(stateStore, "dispatch");
const addSystemMessage = vi.fn();
const loader = new ThreadHistoryLoader({
const loader = new ThreadHistoryController({
stateStore,
currentClient: () =>
({

View file

@ -6,7 +6,7 @@ import type { RestoredThreadController } from "../../../../../src/features/chat/
import { createThreadLifecycleStatePort } from "../../../../../src/features/chat/controllers/state-ports";
import type { ThreadActivationResponse } from "../../../../../src/features/chat/thread-resume";
import { ThreadResumeController } from "../../../../../src/features/chat/controllers/thread/thread-resume-controller";
import type { ThreadHistoryLoader } from "../../../../../src/features/chat/thread-history";
import type { ThreadHistoryController } from "../../../../../src/features/chat/controllers/thread/thread-history-controller";
import { ChatResumeWorkTracker } from "../../../../../src/features/chat/panel/lifecycle";
import type { ThreadItem } from "../../../../../src/generated/app-server/v2/ThreadItem";
import type { Thread } from "../../../../../src/generated/app-server/v2/Thread";
@ -65,7 +65,7 @@ function createController(
state: createThreadLifecycleStatePort(stateStore),
vaultPath: "/vault",
resumeWork: new ChatResumeWorkTracker(() => undefined),
history: { loadLatest, applyLatestPage } as unknown as ThreadHistoryLoader,
history: { loadLatest, applyLatestPage } as unknown as ThreadHistoryController,
restoredThread: { clear: restoredClear } as unknown as RestoredThreadController,
currentClient: () => client,
ensureConnected: vi.fn().mockResolvedValue(undefined),

View file

@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { ChatController } from "../../../src/features/chat/chat-controller";
import { attachHookRunsToTurn } from "../../../src/features/chat/hook-display";
import { ChatInboundController } from "../../../../src/features/chat/inbound/controller";
import { attachHookRunsToTurn } from "../../../../src/features/chat/hook-display";
import {
activeTurnId,
chatReducer,
@ -11,17 +11,17 @@ import {
type ChatAction,
type ChatState,
type ChatStateStore,
} from "../../../src/features/chat/chat-state";
import type { ServerNotification } from "../../../src/generated/app-server/ServerNotification";
import type { ServerRequest } from "../../../src/generated/app-server/ServerRequest";
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
import type { Turn } from "../../../src/generated/app-server/v2/Turn";
} from "../../../../src/features/chat/chat-state";
import type { ServerNotification } from "../../../../src/generated/app-server/ServerNotification";
import type { ServerRequest } from "../../../../src/generated/app-server/ServerRequest";
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
import type { Turn } from "../../../../src/generated/app-server/v2/Turn";
function controllerForState(
state = createChatState(),
actions: Partial<ConstructorParameters<typeof ChatController>[1]> = {},
): ChatController {
return new ChatController(testStoreForState(state), {
actions: Partial<ConstructorParameters<typeof ChatInboundController>[1]> = {},
): ChatInboundController {
return new ChatInboundController(testStoreForState(state), {
refreshThreads: vi.fn(),
refreshRateLimits: vi.fn(),
refreshSkills: vi.fn(),
@ -54,7 +54,7 @@ function expectPresent<T>(value: T | null | undefined): T {
return value;
}
describe("ChatController", () => {
describe("ChatInboundController", () => {
describe("active turn routing", () => {
it("applies matching streaming deltas as assistant markdown", () => {
const state = createChatState();

View file

@ -2,9 +2,9 @@
import { describe, expect, it, vi } from "vitest";
import { createChatState, createChatStateStore } from "../../../src/features/chat/chat-state";
import { ToolbarPanelController } from "../../../src/features/chat/toolbar-panel-controller";
import type { ChatThreadActionController } from "../../../src/features/chat/controllers/thread/thread-actions-controller";
import { createChatState, createChatStateStore } from "../../../../src/features/chat/chat-state";
import { ToolbarPanelController } from "../../../../src/features/chat/panel/toolbar-controller";
import type { ChatThreadActionController } from "../../../../src/features/chat/controllers/thread/thread-actions-controller";
describe("ToolbarPanelController", () => {
it("tracks archive confirmation and delegates archive actions", async () => {

View file

@ -3,11 +3,20 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TFile } from "obsidian";
import { ChatMessageRenderer } from "../../../src/features/chat/chat-message-renderer";
import { chatReducer, createChatState, type ChatAction, type ChatState, type ChatStateStore } from "../../../src/features/chat/chat-state";
import { bindRenderedWikiLinks, type RenderedMarkdownLinkContext } from "../../../src/features/chat/rendered-markdown-links";
import { installObsidianDomShims } from "../../support/dom";
import { notices } from "../../mocks/obsidian";
import {
chatReducer,
createChatState,
type ChatAction,
type ChatState,
type ChatStateStore,
} from "../../../../../src/features/chat/chat-state";
import {
ChatMessageRenderer,
bindRenderedWikiLinks,
type RenderedMarkdownLinkContext,
} from "../../../../../src/features/chat/ui/message-stream";
import { notices } from "../../../../mocks/obsidian";
import { installObsidianDomShims } from "../../../../support/dom";
installObsidianDomShims();