mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Narrow chat host boundary dependencies
This commit is contained in:
parent
32fb1d629c
commit
a14e3d19e7
25 changed files with 370 additions and 218 deletions
|
|
@ -75,6 +75,10 @@
|
|||
"path": "./scripts/lint/no-workspace-chat-internal-imports.grit",
|
||||
"includes": ["**/src/workspace/**/*.ts"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-chat-host-rendering-layer-imports.grit",
|
||||
"includes": ["**/src/features/chat/host/**/*.ts"]
|
||||
},
|
||||
{
|
||||
"path": "./scripts/lint/no-chat-panel-runtime-boundary-imports.grit",
|
||||
"includes": ["**/src/features/chat/panel/**/*.ts", "**/src/features/chat/panel/**/*.tsx"]
|
||||
|
|
|
|||
18
scripts/lint/no-chat-host-rendering-layer-imports.grit
Normal file
18
scripts/lint/no-chat-host-rendering-layer-imports.grit
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
language js
|
||||
|
||||
private pattern js_module_reference() {
|
||||
or {
|
||||
JsImport(),
|
||||
JsExportNamedFromClause(),
|
||||
JsExportFromClause(),
|
||||
TsImportType(),
|
||||
JsImportCallExpression()
|
||||
}
|
||||
}
|
||||
|
||||
js_module_reference() as $stmt where {
|
||||
$stmt <: contains `$source` where {
|
||||
$source <: r"^[\"'](?:(?:\.\./)+(?:presentation|ui)|src/features/chat/(?:presentation|ui))(?:/.*)?[\"']$"
|
||||
},
|
||||
register_diagnostic(span=$stmt, message="Chat host modules must not import chat presentation or UI layers; route render-facing projections through panel-owned adapters.", severity="error")
|
||||
}
|
||||
|
|
@ -65,8 +65,8 @@ function createSessionComposerController(
|
|||
sourcePath: () => environment.obsidian.app.workspace.getActiveFile()?.path ?? "",
|
||||
stateStore,
|
||||
viewId: environment.obsidian.viewId,
|
||||
sendShortcut: () => environment.plugin.settingsRef.settings.sendShortcut,
|
||||
scrollThreadFromComposerEdges: () => environment.plugin.settingsRef.settings.scrollThreadFromComposerEdges,
|
||||
sendShortcut: () => environment.plugin.settingsRef.settings.sendShortcut(),
|
||||
scrollThreadFromComposerEdges: () => environment.plugin.settingsRef.settings.scrollThreadFromComposerEdges(),
|
||||
canInterrupt: (state) => {
|
||||
return state.turnBusy && Boolean(state.activeThreadId && state.activeTurnId);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export function createConnectionBundle(
|
|||
},
|
||||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
configuredCommand: () => environment.plugin.settingsRef.settings.codexPath,
|
||||
configuredCommand: () => environment.plugin.settingsRef.settings.codexPath(),
|
||||
refreshLiveState: () => {
|
||||
host.refreshLiveState();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,25 +4,37 @@ import type { AppServerClient } from "../../../app-server/connection/client";
|
|||
import type { AppServerQueryContext } from "../../../app-server/query/keys";
|
||||
import type { ThreadCatalogActiveReader, ThreadCatalogEventSink } from "../../../app-server/query/thread-catalog";
|
||||
import type { ArchiveExportDestination } from "../../../app-server/services/thread-archive-markdown";
|
||||
import type { ModelMetadata } from "../../../domain/catalog/metadata";
|
||||
import type { ModelMetadata, ReasoningEffort } from "../../../domain/catalog/metadata";
|
||||
import type { PendingRequestCounts } from "../../../domain/pending-requests/aggregate";
|
||||
import type { SharedServerMetadata } from "../../../domain/server/metadata";
|
||||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
import type { ArchiveExportSettings } from "../../../domain/threads/archive-markdown";
|
||||
import type { ObservedResultListener } from "../../../shared/query/observed-result";
|
||||
import type { SendShortcut } from "../../../shared/ui/keyboard";
|
||||
import type { ChatTurnDiffViewState } from "../domain/turn-diff";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settingsRef: PluginSettingsRef;
|
||||
readonly settingsRef: ChatPanelSettingsRef;
|
||||
readonly workspace: WorkspacePanels;
|
||||
readonly appServerQueries: ChatAppServerQueries;
|
||||
readonly threadCatalog: ChatThreadCatalog;
|
||||
}
|
||||
|
||||
export interface PluginSettingsRef {
|
||||
readonly settings: CodexPanelSettings;
|
||||
interface ChatPanelSettingsRef {
|
||||
readonly settings: ChatPanelSettingsAccess;
|
||||
readonly vaultPath: string;
|
||||
}
|
||||
|
||||
export interface ChatPanelSettingsAccess {
|
||||
archiveExportEnabled(): boolean;
|
||||
archiveExportSettings(): ArchiveExportSettings;
|
||||
codexPath(): string;
|
||||
scrollThreadFromComposerEdges(): boolean;
|
||||
sendShortcut(): SendShortcut;
|
||||
showToolbar(): boolean;
|
||||
threadNamingEffort(): ReasoningEffort | null;
|
||||
threadNamingModel(): string | null;
|
||||
}
|
||||
|
||||
interface WorkspacePanels {
|
||||
openThreadInNewView(threadId: string): Promise<void>;
|
||||
focusThreadInOpenView(threadId: string): Promise<boolean>;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,10 @@
|
|||
import type { ConnectionManager } from "../../../app-server/connection/connection-manager";
|
||||
import { createChatRuntimeSettingsTransport } from "../app-server/runtime/thread-settings-transport";
|
||||
import { connectionDiagnosticSectionsFromState } from "../application/connection/diagnostic-sections";
|
||||
import { toolInventoryDiagnosticSections } from "../application/connection/tool-inventory-diagnostic-sections";
|
||||
import { type ChatRuntimeSettingsActions, createChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../domain/runtime/labels";
|
||||
import type { RuntimeSnapshot } from "../domain/runtime/snapshot";
|
||||
import {
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
} from "../presentation/runtime/status";
|
||||
import { type ChatPanelRuntimeProjection, createChatPanelRuntimeProjection } from "../panel/runtime-status-projection";
|
||||
import type { CurrentAppServerClient } from "./connection-bundle";
|
||||
import type { ChatPanelEnvironment } from "./contracts";
|
||||
|
||||
|
|
@ -27,14 +19,6 @@ interface ChatPanelRuntimeHost {
|
|||
stateStore: ChatStateStore;
|
||||
}
|
||||
|
||||
export interface ChatPanelRuntimeProjection {
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
modelStatusLines: () => string[];
|
||||
effortStatusLines: () => string[];
|
||||
statusSummaryLines: () => string[];
|
||||
toolInventoryDetails: () => MessageStreamNoticeSection[];
|
||||
}
|
||||
|
||||
interface ChatPanelRuntimeBundle {
|
||||
settings: ChatPanelRuntimeSettingsActions;
|
||||
projection: ChatPanelRuntimeProjection;
|
||||
|
|
@ -77,63 +61,10 @@ function collaborationModeLabel(stateStore: ChatStateStore): string {
|
|||
}
|
||||
|
||||
function createSessionRuntimeProjection(host: ChatPanelRuntimeHost, connection: ConnectionManager): ChatPanelRuntimeProjection {
|
||||
return {
|
||||
connectionDiagnosticDetails: () => connectionDiagnosticDetails(host, connection),
|
||||
modelStatusLines: () => modelStatusLines(host),
|
||||
effortStatusLines: () => effortStatusLines(host),
|
||||
statusSummaryLines: () => statusSummaryLines(host),
|
||||
toolInventoryDetails: () => toolInventoryDetails(host),
|
||||
};
|
||||
}
|
||||
|
||||
function statusSummaryLines(host: ChatPanelRuntimeHost): string[] {
|
||||
const state = host.stateStore.getState();
|
||||
return buildStatusSummaryLines({
|
||||
activeThreadId: state.activeThread.id,
|
||||
snapshot: runtimeSnapshot(host),
|
||||
nowMs: Date.now(),
|
||||
return createChatPanelRuntimeProjection({
|
||||
state: () => host.stateStore.getState(),
|
||||
connected: () => connection.isConnected(),
|
||||
configuredCommand: () => host.environment.plugin.settingsRef.settings.codexPath(),
|
||||
nowMs: () => Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
function modelStatusLines(host: ChatPanelRuntimeHost): string[] {
|
||||
const state = host.stateStore.getState();
|
||||
return buildModelStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingModel: state.runtime.pending.model,
|
||||
snapshot: runtimeSnapshot(host),
|
||||
collaborationModeLabel: collaborationModeLabel(host.stateStore),
|
||||
});
|
||||
}
|
||||
|
||||
function effortStatusLines(host: ChatPanelRuntimeHost): string[] {
|
||||
const state = host.stateStore.getState();
|
||||
return buildEffortStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingReasoningEffort: state.runtime.pending.reasoningEffort,
|
||||
snapshot: runtimeSnapshot(host),
|
||||
});
|
||||
}
|
||||
|
||||
function connectionDiagnosticDetails(host: ChatPanelRuntimeHost, connection: ConnectionManager): MessageStreamNoticeSection[] {
|
||||
const sections = connectionDiagnosticSectionsFromState({
|
||||
state: host.stateStore.getState(),
|
||||
connected: connection.isConnected(),
|
||||
configuredCommand: host.environment.plugin.settingsRef.settings.codexPath,
|
||||
});
|
||||
return sections.map((section) => ({
|
||||
title: section.title,
|
||||
auditFacts: section.rows.map((row) => ({ key: row.label, value: row.value })),
|
||||
}));
|
||||
}
|
||||
|
||||
function toolInventoryDetails(host: ChatPanelRuntimeHost): MessageStreamNoticeSection[] {
|
||||
const sections = toolInventoryDiagnosticSections(host.stateStore.getState().connection.serverDiagnostics);
|
||||
return sections.map((section) => ({
|
||||
title: section.title,
|
||||
auditFacts: section.rows.map((row) => ({ key: row.label, value: row.value })),
|
||||
}));
|
||||
}
|
||||
|
||||
function runtimeSnapshot(host: ChatPanelRuntimeHost): RuntimeSnapshot {
|
||||
return runtimeSnapshotForChatState(host.stateStore.getState());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
}
|
||||
|
||||
function createConnectionManager(environment: ChatPanelEnvironment): ConnectionManager {
|
||||
return new ConnectionManager(() => environment.plugin.settingsRef.settings.codexPath, environment.plugin.settingsRef.vaultPath);
|
||||
return new ConnectionManager(() => environment.plugin.settingsRef.settings.codexPath(), environment.plugin.settingsRef.vaultPath);
|
||||
}
|
||||
|
||||
function createSessionStatus(stateStore: ChatStateStore, localItemIds: LocalIdSource): ChatPanelSessionStatus {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@ import { parseRestoredThreadState, type RestoredThreadPlaceholderState } from ".
|
|||
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 } from "./contracts";
|
||||
import type { ChatPanelEnvironment, ChatPanelHandle, ChatWorkspacePanelSnapshot, ChatWorkspacePanelTurnLifecycle } from "./contracts";
|
||||
import { type ChatViewDeferredTasks, createChatViewDeferredTasks } from "./deferred-work";
|
||||
import { type ChatPanelSessionGraph, createChatPanelSessionGraph } from "./session-graph";
|
||||
import { openPanelTurnLifecycle } from "./workspace-panel-snapshot";
|
||||
|
||||
export class ChatPanelSession implements ChatPanelHandle {
|
||||
private readonly stateStore: ChatStateStore = createChatStateStore();
|
||||
|
|
@ -188,7 +187,7 @@ export class ChatPanelSession implements ChatPanelHandle {
|
|||
if (!root) return;
|
||||
renderChatPanelShell(root, {
|
||||
stateStore: this.stateStore,
|
||||
showToolbar: this.environment.plugin.settingsRef.settings.showToolbar,
|
||||
showToolbar: this.environment.plugin.settingsRef.settings.showToolbar(),
|
||||
parts: this.graph.shell.parts,
|
||||
});
|
||||
}
|
||||
|
|
@ -209,7 +208,7 @@ export class ChatPanelSession implements ChatPanelHandle {
|
|||
|
||||
private currentAppServerContext(): AppServerQueryContext {
|
||||
return {
|
||||
codexPath: this.environment.plugin.settingsRef.settings.codexPath,
|
||||
codexPath: this.environment.plugin.settingsRef.settings.codexPath(),
|
||||
vaultPath: this.environment.plugin.settingsRef.vaultPath,
|
||||
};
|
||||
}
|
||||
|
|
@ -254,3 +253,9 @@ export class ChatPanelSession implements ChatPanelHandle {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
function openPanelTurnLifecycle(state: ChatState["turn"]["lifecycle"]): ChatWorkspacePanelTurnLifecycle {
|
||||
if (state.kind === "running") return { kind: "running", turnId: state.turnId };
|
||||
if (state.kind === "starting") return { kind: "starting" };
|
||||
return { kind: "idle" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { MessageStreamPresenter } from "../panel/surface/message-stream-presente
|
|||
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 "../ui/toolbar.dom";
|
||||
import { toolbarOutsidePointerHit } from "../panel/toolbar-hit-test.dom";
|
||||
import type { ChatPanelComposerBundle } from "./composer-bundle";
|
||||
import type { ChatPanelConnectionBundle } from "./connection-bundle";
|
||||
import type { ChatPanelEnvironment } from "./contracts";
|
||||
|
|
@ -75,12 +75,12 @@ export function createShellBundle(host: ChatPanelShellBundleHost, input: ChatPan
|
|||
},
|
||||
settings: {
|
||||
vaultPath: () => environment.plugin.settingsRef.vaultPath,
|
||||
configuredCommand: () => environment.plugin.settingsRef.settings.codexPath,
|
||||
archiveExportEnabled: () => environment.plugin.settingsRef.settings.archiveExportEnabled,
|
||||
configuredCommand: () => environment.plugin.settingsRef.settings.codexPath(),
|
||||
archiveExportEnabled: () => environment.plugin.settingsRef.settings.archiveExportEnabled(),
|
||||
},
|
||||
};
|
||||
const goalSurface: ChatPanelGoalSurface = {
|
||||
sendShortcut: () => environment.plugin.settingsRef.settings.sendShortcut,
|
||||
sendShortcut: () => environment.plugin.settingsRef.settings.sendShortcut(),
|
||||
actions: goals,
|
||||
};
|
||||
const messageStreamPresenter = new MessageStreamPresenter({
|
||||
|
|
|
|||
|
|
@ -243,10 +243,10 @@ export function createThreadActionBundle(host: ChatPanelThreadHost, input: ChatP
|
|||
function createSessionThreadTitleService(host: ChatPanelThreadHost, currentClient: CurrentAppServerClient): ThreadTitleService {
|
||||
const { environment, stateStore } = host;
|
||||
return createThreadTitleService({
|
||||
codexPath: () => environment.plugin.settingsRef.settings.codexPath,
|
||||
codexPath: () => environment.plugin.settingsRef.settings.codexPath(),
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
threadNamingModel: () => environment.plugin.settingsRef.settings.threadNamingModel,
|
||||
threadNamingEffort: () => environment.plugin.settingsRef.settings.threadNamingEffort,
|
||||
threadNamingModel: () => environment.plugin.settingsRef.settings.threadNamingModel(),
|
||||
threadNamingEffort: () => environment.plugin.settingsRef.settings.threadNamingEffort(),
|
||||
clientAccess: createCurrentClientAccess(currentClient),
|
||||
visibleContext: (threadId) => activeThreadRenameTitleContext(stateStore.getState(), threadId),
|
||||
visibleCompletedTurnContext: (turnId) =>
|
||||
|
|
@ -327,12 +327,8 @@ function createSessionThreadOperations(environment: ChatPanelEnvironment, curren
|
|||
return createThreadOperations({
|
||||
clientAccess: createCurrentClientAccess(currentClient),
|
||||
archiveExport: {
|
||||
settings: () => ({
|
||||
archiveExportFolderTemplate: environment.plugin.settingsRef.settings.archiveExportFolderTemplate,
|
||||
archiveExportFilenameTemplate: environment.plugin.settingsRef.settings.archiveExportFilenameTemplate,
|
||||
archiveExportTags: environment.plugin.settingsRef.settings.archiveExportTags,
|
||||
}),
|
||||
enabled: () => environment.plugin.settingsRef.settings.archiveExportEnabled,
|
||||
settings: () => environment.plugin.settingsRef.settings.archiveExportSettings(),
|
||||
enabled: () => environment.plugin.settingsRef.settings.archiveExportEnabled(),
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
vaultConfigDir: environment.obsidian.app.vault.configDir,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ 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 { CurrentAppServerClient } from "./connection-bundle";
|
||||
import type { ChatPanelEnvironment } from "./contracts";
|
||||
import type { ChatViewDeferredTasks } from "./deferred-work";
|
||||
import type { ChatPanelRuntimeProjection, ChatPanelRuntimeSettingsActions } from "./runtime-bundle";
|
||||
import type { ChatPanelRuntimeSettingsActions } from "./runtime-bundle";
|
||||
import type {
|
||||
ChatPanelGoalActions,
|
||||
ChatPanelThreadActions,
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import type { ChatState } from "../application/state/root-reducer";
|
||||
import type { ChatWorkspacePanelTurnLifecycle } from "./contracts";
|
||||
|
||||
export function openPanelTurnLifecycle(state: ChatState["turn"]["lifecycle"]): ChatWorkspacePanelTurnLifecycle {
|
||||
if (state.kind === "running") return { kind: "running", turnId: state.turnId };
|
||||
if (state.kind === "starting") return { kind: "starting" };
|
||||
return { kind: "idle" };
|
||||
}
|
||||
95
src/features/chat/panel/runtime-status-projection.ts
Normal file
95
src/features/chat/panel/runtime-status-projection.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { connectionDiagnosticSectionsFromState } from "../application/connection/diagnostic-sections";
|
||||
import { toolInventoryDiagnosticSections } from "../application/connection/tool-inventory-diagnostic-sections";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import type { ChatState } from "../application/state/root-reducer";
|
||||
import type { MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../domain/runtime/labels";
|
||||
import type { RuntimeSnapshot } from "../domain/runtime/snapshot";
|
||||
import {
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
statusSummaryLines as buildStatusSummaryLines,
|
||||
} from "../presentation/runtime/status";
|
||||
|
||||
export interface ChatPanelRuntimeProjection {
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
modelStatusLines: () => string[];
|
||||
effortStatusLines: () => string[];
|
||||
statusSummaryLines: () => string[];
|
||||
toolInventoryDetails: () => MessageStreamNoticeSection[];
|
||||
}
|
||||
|
||||
interface ChatPanelRuntimeProjectionInput {
|
||||
state: () => ChatState;
|
||||
connected: () => boolean;
|
||||
configuredCommand: () => string;
|
||||
nowMs: () => number;
|
||||
}
|
||||
|
||||
export function createChatPanelRuntimeProjection(input: ChatPanelRuntimeProjectionInput): ChatPanelRuntimeProjection {
|
||||
return {
|
||||
connectionDiagnosticDetails: () => connectionDiagnosticDetails(input),
|
||||
modelStatusLines: () => modelStatusLines(input),
|
||||
effortStatusLines: () => effortStatusLines(input),
|
||||
statusSummaryLines: () => statusSummaryLines(input),
|
||||
toolInventoryDetails: () => toolInventoryDetails(input),
|
||||
};
|
||||
}
|
||||
|
||||
function statusSummaryLines(input: ChatPanelRuntimeProjectionInput): string[] {
|
||||
const state = input.state();
|
||||
return buildStatusSummaryLines({
|
||||
activeThreadId: state.activeThread.id,
|
||||
snapshot: runtimeSnapshot(state),
|
||||
nowMs: input.nowMs(),
|
||||
});
|
||||
}
|
||||
|
||||
function modelStatusLines(input: ChatPanelRuntimeProjectionInput): string[] {
|
||||
const state = input.state();
|
||||
return buildModelStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingModel: state.runtime.pending.model,
|
||||
snapshot: runtimeSnapshot(state),
|
||||
collaborationModeLabel: collaborationModeLabel(state),
|
||||
});
|
||||
}
|
||||
|
||||
function effortStatusLines(input: ChatPanelRuntimeProjectionInput): string[] {
|
||||
const state = input.state();
|
||||
return buildEffortStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingReasoningEffort: state.runtime.pending.reasoningEffort,
|
||||
snapshot: runtimeSnapshot(state),
|
||||
});
|
||||
}
|
||||
|
||||
function connectionDiagnosticDetails(input: ChatPanelRuntimeProjectionInput): MessageStreamNoticeSection[] {
|
||||
const sections = connectionDiagnosticSectionsFromState({
|
||||
state: input.state(),
|
||||
connected: input.connected(),
|
||||
configuredCommand: input.configuredCommand(),
|
||||
});
|
||||
return noticeSectionsFromDiagnostics(sections);
|
||||
}
|
||||
|
||||
function toolInventoryDetails(input: ChatPanelRuntimeProjectionInput): MessageStreamNoticeSection[] {
|
||||
return noticeSectionsFromDiagnostics(toolInventoryDiagnosticSections(input.state().connection.serverDiagnostics));
|
||||
}
|
||||
|
||||
function noticeSectionsFromDiagnostics(
|
||||
sections: readonly { title: string; rows: readonly { label: string; value: string }[] }[],
|
||||
): MessageStreamNoticeSection[] {
|
||||
return sections.map((section) => ({
|
||||
title: section.title,
|
||||
auditFacts: section.rows.map((row) => ({ key: row.label, value: row.value })),
|
||||
}));
|
||||
}
|
||||
|
||||
function collaborationModeLabel(state: ChatState): string {
|
||||
return formatCollaborationModeLabel(state.runtime.pending.collaborationMode);
|
||||
}
|
||||
|
||||
function runtimeSnapshot(state: ChatState): RuntimeSnapshot {
|
||||
return runtimeSnapshotForChatState(state);
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import type { ThreadRenameEditorActions } from "../application/threads/rename-ed
|
|||
import type { ThreadManagementActions } from "../application/threads/thread-management-actions";
|
||||
import type { ThreadNavigationActions } from "../application/threads/thread-navigation-actions";
|
||||
import type { ToolbarActions } from "../ui/toolbar";
|
||||
import type { ToolbarOutsidePointerHit } from "../ui/toolbar.dom";
|
||||
|
||||
export interface ToolbarPanelActionsHost {
|
||||
stateStore: ChatStateStore;
|
||||
|
|
@ -36,6 +35,11 @@ export interface ToolbarUiActionDependencies {
|
|||
navigation: ThreadNavigationActions;
|
||||
}
|
||||
|
||||
export interface ToolbarOutsidePointerHit {
|
||||
insideToolbarPanel: boolean;
|
||||
insideArchiveConfirm: boolean;
|
||||
}
|
||||
|
||||
interface ToolbarOutsidePointerContext {
|
||||
hit: ToolbarOutsidePointerHit;
|
||||
renameEditing: boolean;
|
||||
|
|
|
|||
23
src/features/chat/panel/toolbar-hit-test.dom.ts
Normal file
23
src/features/chat/panel/toolbar-hit-test.dom.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import type { ToolbarOutsidePointerHit } from "./toolbar-actions";
|
||||
|
||||
export function toolbarOutsidePointerHit(
|
||||
event: PointerEvent,
|
||||
root: HTMLElement | null,
|
||||
viewWindow: Window | null,
|
||||
): ToolbarOutsidePointerHit {
|
||||
const target = event.target;
|
||||
const domWindow = viewWindow as (Window & { Element: typeof Element }) | null;
|
||||
if (!root || !domWindow || !(target instanceof domWindow.Element)) {
|
||||
return { insideToolbarPanel: false, insideArchiveConfirm: false };
|
||||
}
|
||||
|
||||
const toolbarPanel = target.closest(".codex-panel__toolbar-primary, .codex-panel__toolbar-panel");
|
||||
if (!toolbarPanel || !root.contains(toolbarPanel)) {
|
||||
return { insideToolbarPanel: false, insideArchiveConfirm: false };
|
||||
}
|
||||
|
||||
return {
|
||||
insideToolbarPanel: true,
|
||||
insideArchiveConfirm: Boolean(target.closest(".codex-panel__archive-confirm")),
|
||||
};
|
||||
}
|
||||
|
|
@ -4,30 +4,3 @@ export function focusToolbarRenameInput(input: HTMLInputElement | null): void {
|
|||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
export interface ToolbarOutsidePointerHit {
|
||||
insideToolbarPanel: boolean;
|
||||
insideArchiveConfirm: boolean;
|
||||
}
|
||||
|
||||
export function toolbarOutsidePointerHit(
|
||||
event: PointerEvent,
|
||||
root: HTMLElement | null,
|
||||
viewWindow: Window | null,
|
||||
): ToolbarOutsidePointerHit {
|
||||
const target = event.target;
|
||||
const domWindow = viewWindow as (Window & { Element: typeof Element }) | null;
|
||||
if (!root || !domWindow || !(target instanceof domWindow.Element)) {
|
||||
return { insideToolbarPanel: false, insideArchiveConfirm: false };
|
||||
}
|
||||
|
||||
const toolbarPanel = target.closest(".codex-panel__toolbar-primary, .codex-panel__toolbar-panel");
|
||||
if (!toolbarPanel || !root.contains(toolbarPanel)) {
|
||||
return { insideToolbarPanel: false, insideArchiveConfirm: false };
|
||||
}
|
||||
|
||||
return {
|
||||
insideToolbarPanel: true,
|
||||
insideArchiveConfirm: Boolean(target.closest(".codex-panel__archive-confirm")),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ import type { ChatTurnDiffViewState } from "./features/chat/domain/turn-diff";
|
|||
import { persistedChatTurnDiffViewState } from "./features/chat/domain/turn-diff";
|
||||
import type {
|
||||
ChatPanelClientSurface,
|
||||
ChatPanelSettingsAccess,
|
||||
ChatSharedThreadSurface,
|
||||
ChatViewLifecycleSurface,
|
||||
ChatWorkspacePanelSurface,
|
||||
CodexChatHost,
|
||||
PluginSettingsRef,
|
||||
} from "./features/chat/host/contracts";
|
||||
import { CodexChatTurnDiffView } from "./features/chat/ui/turn-diff/view.obsidian";
|
||||
import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal.obsidian";
|
||||
|
|
@ -24,11 +24,17 @@ import type { ThreadsViewHost, ThreadsViewSettingsAccess } from "./features/thre
|
|||
import type { ThreadsViewPanelActivity } from "./features/threads-view/state";
|
||||
import { CodexThreadsView } from "./features/threads-view/view.obsidian";
|
||||
import type { CodexPanelSettingTabHost } from "./settings/host";
|
||||
import type { CodexPanelSettings } from "./settings/model";
|
||||
import { WorkspacePanelCoordinator } from "./workspace/panel-coordinator";
|
||||
|
||||
interface CodexPanelRuntimeSettingsRef {
|
||||
readonly settings: CodexPanelSettings;
|
||||
readonly vaultPath: string;
|
||||
}
|
||||
|
||||
export interface CodexPanelRuntimeOptions {
|
||||
app: App;
|
||||
settingsRef: PluginSettingsRef;
|
||||
settingsRef: CodexPanelRuntimeSettingsRef;
|
||||
saveSettings(): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +113,10 @@ export class CodexPanelRuntime implements AppServerClientAccess {
|
|||
|
||||
chatHost(): CodexChatHost {
|
||||
return {
|
||||
settingsRef: this.options.settingsRef,
|
||||
settingsRef: {
|
||||
settings: this.chatSettings(),
|
||||
vaultPath: this.options.settingsRef.vaultPath,
|
||||
},
|
||||
workspace: {
|
||||
openThreadInNewView: (threadId) => this.panels.openThreadInNewView(threadId),
|
||||
focusThreadInOpenView: (threadId) => this.panels.focusThreadInOpenView(threadId),
|
||||
|
|
@ -121,6 +130,23 @@ export class CodexPanelRuntime implements AppServerClientAccess {
|
|||
};
|
||||
}
|
||||
|
||||
private chatSettings(): ChatPanelSettingsAccess {
|
||||
return {
|
||||
archiveExportEnabled: () => this.options.settingsRef.settings.archiveExportEnabled,
|
||||
archiveExportSettings: () => ({
|
||||
archiveExportFolderTemplate: this.options.settingsRef.settings.archiveExportFolderTemplate,
|
||||
archiveExportFilenameTemplate: this.options.settingsRef.settings.archiveExportFilenameTemplate,
|
||||
archiveExportTags: this.options.settingsRef.settings.archiveExportTags,
|
||||
}),
|
||||
codexPath: () => this.options.settingsRef.settings.codexPath,
|
||||
scrollThreadFromComposerEdges: () => this.options.settingsRef.settings.scrollThreadFromComposerEdges,
|
||||
sendShortcut: () => this.options.settingsRef.settings.sendShortcut,
|
||||
showToolbar: () => this.options.settingsRef.settings.showToolbar,
|
||||
threadNamingEffort: () => this.options.settingsRef.settings.threadNamingEffort,
|
||||
threadNamingModel: () => this.options.settingsRef.settings.threadNamingModel,
|
||||
};
|
||||
}
|
||||
|
||||
threadsHost(): ThreadsViewHost {
|
||||
return {
|
||||
settings: this.threadsSettings(),
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import { createChatPanelSessionGraph } from "../../../../src/features/chat/host/
|
|||
import { ChatComposerController } from "../../../../src/features/chat/panel/composer-controller";
|
||||
import { MessageStreamPresenter } from "../../../../src/features/chat/panel/surface/message-stream-presenter";
|
||||
import { createChatMessageScrollController } from "../../../../src/features/chat/panel/surface/message-stream-scroll";
|
||||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { ConnectionWorkTracker } from "../../../../src/shared/lifecycle/connection-work";
|
||||
import { installObsidianDomShims } from "../../../support/dom";
|
||||
import { chatPanelSettingsAccess } from "../support/settings";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
|
|
@ -238,6 +239,12 @@ describe("createChatPanelSessionGraph actions", () => {
|
|||
function chatPanelEnvironmentFixture(overrides: PartialChatPanelEnvironment = {}): ChatPanelEnvironment {
|
||||
const threadCatalog = threadCatalogFixture(overrides.plugin?.threadCatalog);
|
||||
const appServerQueries = appServerQueriesFixture(overrides.plugin?.appServerQueries);
|
||||
const settingsRef = overrides.plugin?.settingsRef;
|
||||
const settingsSource: CodexPanelSettings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
codexPath: "codex",
|
||||
sendShortcut: "enter",
|
||||
};
|
||||
return {
|
||||
obsidian: {
|
||||
app: {
|
||||
|
|
@ -273,13 +280,8 @@ describe("createChatPanelSessionGraph actions", () => {
|
|||
},
|
||||
plugin: {
|
||||
settingsRef: {
|
||||
settings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
codexPath: "codex",
|
||||
sendShortcut: "enter",
|
||||
},
|
||||
vaultPath: "/vault",
|
||||
...overrides.plugin?.settingsRef,
|
||||
settings: settingsRef?.settings ?? chatPanelSettingsAccess(settingsSource),
|
||||
vaultPath: settingsRef?.vaultPath ?? "/vault",
|
||||
},
|
||||
workspace: {
|
||||
openThreadInNewView: vi.fn().mockResolvedValue(undefined),
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ import { createServerDiagnostics } from "../../../../src/domain/server/diagnosti
|
|||
import type { SharedServerMetadata } from "../../../../src/domain/server/metadata";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
import type { CodexChatHost } from "../../../../src/features/chat/host/contracts";
|
||||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import type { ObservedResult } from "../../../../src/shared/query/observed-result";
|
||||
import { notices } from "../../../mocks/obsidian";
|
||||
import { deferred, waitForAsyncWork } from "../../../support/async";
|
||||
import { installObsidianDomShims } from "../../../support/dom";
|
||||
import { chatPanelSettingsAccess } from "../support/settings";
|
||||
|
||||
type TestCodexChatHost = CodexChatHost;
|
||||
interface TestCodexChatHost extends CodexChatHost {
|
||||
readonly settingsSource: CodexPanelSettings;
|
||||
}
|
||||
let CodexChatView: typeof import("../../../../src/features/chat/host/view.obsidian")["CodexChatView"];
|
||||
interface TrackedView {
|
||||
view: { onClose(): Promise<void> | void };
|
||||
|
|
@ -199,11 +202,11 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
await view.surface.connect();
|
||||
expect(view.surface.openPanelSnapshot()).toMatchObject({ connected: true });
|
||||
|
||||
host.settingsRef.settings.showToolbar = false;
|
||||
host.settingsSource.showToolbar = false;
|
||||
view.surface.refreshSettings();
|
||||
expect(view.surface.openPanelSnapshot()).toMatchObject({ connected: true });
|
||||
|
||||
host.settingsRef.settings.codexPath = "codex-next";
|
||||
host.settingsSource.codexPath = "codex-next";
|
||||
view.surface.refreshSettings();
|
||||
expect(view.surface.openPanelSnapshot()).toMatchObject({ connected: false });
|
||||
});
|
||||
|
|
@ -484,7 +487,25 @@ describe("CodexChatView connection lifecycle", () => {
|
|||
clientUserMessageId: expect.stringMatching(/^local-user-\d+-[A-Za-z0-9_-]+-[a-z0-9]+$/),
|
||||
});
|
||||
});
|
||||
expect(view.surface.openPanelSnapshot()).toMatchObject({ turnLifecycle: { kind: "starting" } });
|
||||
expect(view.getState()).toEqual({ version: 1, threadId: "thread-1", threadTitle: "Restored thread" });
|
||||
connectionMock.state.onNotification?.({
|
||||
method: "turn/started",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turn: {
|
||||
id: "turn-1",
|
||||
status: "inProgress",
|
||||
startedAt: 1,
|
||||
completedAt: null,
|
||||
durationMs: null,
|
||||
error: null,
|
||||
itemsView: "full",
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
} satisfies Extract<ServerNotification, { method: "turn/started" }>);
|
||||
expect(view.surface.openPanelSnapshot()).toMatchObject({ turnLifecycle: { kind: "running", turnId: "turn-1" } });
|
||||
});
|
||||
|
||||
it("requests a workspace layout save after resuming a thread", async () => {
|
||||
|
|
@ -913,7 +934,7 @@ async function flushAsyncTicks(): Promise<void> {
|
|||
}
|
||||
|
||||
interface ChatHostFixtureOverrides {
|
||||
settings?: Partial<typeof DEFAULT_SETTINGS>;
|
||||
settings?: Partial<CodexPanelSettings>;
|
||||
vaultPath?: string;
|
||||
openThreadInNewView?: CodexChatHost["workspace"]["openThreadInNewView"];
|
||||
focusThreadInOpenView?: CodexChatHost["workspace"]["focusThreadInOpenView"];
|
||||
|
|
@ -1021,8 +1042,9 @@ function chatHost(overrides: ChatHostFixtureOverrides = {}): TestCodexChatHost {
|
|||
}
|
||||
};
|
||||
return {
|
||||
settingsSource: settings,
|
||||
settingsRef: {
|
||||
settings,
|
||||
settings: chatPanelSettingsAccess(settings),
|
||||
vaultPath,
|
||||
},
|
||||
workspace: {
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { openPanelTurnLifecycle } from "../../../../src/features/chat/host/workspace-panel-snapshot";
|
||||
|
||||
describe("chat workspace panel snapshots", () => {
|
||||
it("projects open panel turn lifecycle without exposing full chat state", () => {
|
||||
expect(openPanelTurnLifecycle({ kind: "idle" })).toEqual({ kind: "idle" });
|
||||
expect(openPanelTurnLifecycle({ kind: "starting", pendingTurnStart: { anchorItemId: "local", promptSubmitHookItemIds: [] } })).toEqual({
|
||||
kind: "starting",
|
||||
});
|
||||
expect(openPanelTurnLifecycle({ kind: "running", turnId: "turn" })).toEqual({ kind: "running", turnId: "turn" });
|
||||
});
|
||||
});
|
||||
61
tests/features/chat/panel/runtime-status-projection.test.ts
Normal file
61
tests/features/chat/panel/runtime-status-projection.test.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { type ConfigReadResult, runtimeConfigSnapshotFromAppServerConfig } from "../../../../src/app-server/protocol/runtime-config";
|
||||
import type { ModelMetadata } from "../../../../src/domain/catalog/metadata";
|
||||
import type { RuntimeConfigSnapshot } from "../../../../src/domain/runtime/config";
|
||||
import { createChatPanelRuntimeProjection } from "../../../../src/features/chat/panel/runtime-status-projection";
|
||||
import { chatStateFixture, chatStateWith } from "../support/state";
|
||||
|
||||
describe("createChatPanelRuntimeProjection", () => {
|
||||
it("builds slash-command status lines from chat state", () => {
|
||||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, { activeThread: { id: "thread-1" } });
|
||||
state = chatStateWith(state, {
|
||||
connection: {
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
model: "gpt-5.5",
|
||||
model_provider: "openai",
|
||||
model_reasoning_effort: "high",
|
||||
service_tier: "fast",
|
||||
}),
|
||||
},
|
||||
});
|
||||
state = chatStateWith(state, { connection: { availableModels: [modelFixture("gpt-5.5")] } });
|
||||
const projection = createChatPanelRuntimeProjection({
|
||||
state: () => state,
|
||||
connected: () => true,
|
||||
configuredCommand: () => "codex",
|
||||
nowMs: () => 0,
|
||||
});
|
||||
|
||||
expect(projection.statusSummaryLines()[1]).toBe("Thread: thread-1");
|
||||
expect(projection.modelStatusLines()).toContain("Model: gpt-5.5");
|
||||
expect(projection.modelStatusLines()).toContain("Mode: Default");
|
||||
expect(projection.effortStatusLines()).toContain("Supported: high");
|
||||
});
|
||||
});
|
||||
|
||||
function runtimeConfigFixture(config: Record<string, unknown>): RuntimeConfigSnapshot {
|
||||
return runtimeConfigSnapshotFromAppServerConfig({
|
||||
config: config as ConfigReadResult["config"],
|
||||
origins: {},
|
||||
layers: null,
|
||||
});
|
||||
}
|
||||
|
||||
function modelFixture(model: string): ModelMetadata {
|
||||
return {
|
||||
id: model,
|
||||
model,
|
||||
displayName: model,
|
||||
description: "",
|
||||
hidden: false,
|
||||
supportedReasoningEfforts: ["high"],
|
||||
defaultReasoningEffort: "high",
|
||||
inputModalities: [],
|
||||
additionalSpeedTiers: [],
|
||||
serviceTiers: [],
|
||||
defaultServiceTier: null,
|
||||
isDefault: true,
|
||||
};
|
||||
}
|
||||
|
|
@ -8,14 +8,12 @@ import type { RuntimeConfigSnapshot } from "../../../../../src/domain/runtime/co
|
|||
import { createServerDiagnostics } from "../../../../../src/domain/server/diagnostics";
|
||||
import type { ThreadGoal } from "../../../../../src/domain/threads/goal";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
import { runtimeSnapshotForChatState } from "../../../../../src/features/chat/application/runtime/snapshot";
|
||||
import type { ChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
||||
import { ChatPanelShellStateContext, createChatPanelShellState } from "../../../../../src/features/chat/panel/shell-state";
|
||||
import type { ChatPanelComposerSurface } from "../../../../../src/features/chat/panel/surface/composer-projection";
|
||||
import { chatPanelComposerProjection } from "../../../../../src/features/chat/panel/surface/composer-projection";
|
||||
import { ChatPanelGoal, type ChatPanelGoalSurface } from "../../../../../src/features/chat/panel/surface/goal-projection";
|
||||
import { ChatPanelToolbar } from "../../../../../src/features/chat/panel/surface/toolbar-projection";
|
||||
import { effortStatusLines, modelStatusLines, statusSummaryLines } from "../../../../../src/features/chat/presentation/runtime/status";
|
||||
import type { ToolbarActions } from "../../../../../src/features/chat/ui/toolbar";
|
||||
import { renderUiRoot, unmountUiRoot } from "../../../../../src/shared/ui/ui-root.dom";
|
||||
import { installObsidianDomShims } from "../../../../support/dom";
|
||||
|
|
@ -233,48 +231,6 @@ describe("chat panel surface projections", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("builds slash-command status lines from chat state", () => {
|
||||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, { activeThread: { id: "thread-1" } });
|
||||
state = chatStateWith(state, {
|
||||
connection: {
|
||||
runtimeConfig: runtimeConfigFixture({
|
||||
model: "gpt-5.5",
|
||||
model_provider: "openai",
|
||||
model_reasoning_effort: "high",
|
||||
service_tier: "fast",
|
||||
}),
|
||||
},
|
||||
});
|
||||
state = chatStateWith(state, { connection: { availableModels: [modelFixture("gpt-5.5")] } });
|
||||
const snapshot = runtimeSnapshotFixture(state);
|
||||
|
||||
expect(statusSummaryLines({ activeThreadId: state.activeThread.id, snapshot, nowMs: 0 })[1]).toBe("Thread: thread-1");
|
||||
expect(
|
||||
modelStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingModel: state.runtime.pending.model,
|
||||
snapshot,
|
||||
collaborationModeLabel: "Default",
|
||||
}),
|
||||
).toContain("Model: gpt-5.5");
|
||||
expect(
|
||||
modelStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingModel: state.runtime.pending.model,
|
||||
snapshot,
|
||||
collaborationModeLabel: "Default",
|
||||
}),
|
||||
).toContain("Mode: Default");
|
||||
expect(
|
||||
effortStatusLines({
|
||||
runtimeConfig: state.connection.runtimeConfig,
|
||||
pendingReasoningEffort: state.runtime.pending.reasoningEffort,
|
||||
snapshot,
|
||||
}),
|
||||
).toContain("Supported: high");
|
||||
});
|
||||
|
||||
it("builds runtime composer choices from immutable chat state snapshots", () => {
|
||||
let state = chatStateFixture();
|
||||
state = chatStateWith(state, {
|
||||
|
|
@ -520,10 +476,6 @@ function runtimeConfigFixture(config: Record<string, unknown>): RuntimeConfigSna
|
|||
});
|
||||
}
|
||||
|
||||
function runtimeSnapshotFixture(state: ChatState) {
|
||||
return runtimeSnapshotForChatState(state);
|
||||
}
|
||||
|
||||
function threadFixture(id: string, name: string | null): Thread {
|
||||
return {
|
||||
id,
|
||||
|
|
|
|||
19
tests/features/chat/support/settings.ts
Normal file
19
tests/features/chat/support/settings.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { ChatPanelSettingsAccess } from "../../../../src/features/chat/host/contracts";
|
||||
import type { CodexPanelSettings } from "../../../../src/settings/model";
|
||||
|
||||
export function chatPanelSettingsAccess(settings: CodexPanelSettings): ChatPanelSettingsAccess {
|
||||
return {
|
||||
archiveExportEnabled: () => settings.archiveExportEnabled,
|
||||
archiveExportSettings: () => ({
|
||||
archiveExportFolderTemplate: settings.archiveExportFolderTemplate,
|
||||
archiveExportFilenameTemplate: settings.archiveExportFilenameTemplate,
|
||||
archiveExportTags: settings.archiveExportTags,
|
||||
}),
|
||||
codexPath: () => settings.codexPath,
|
||||
scrollThreadFromComposerEdges: () => settings.scrollThreadFromComposerEdges,
|
||||
sendShortcut: () => settings.sendShortcut,
|
||||
showToolbar: () => settings.showToolbar,
|
||||
threadNamingEffort: () => settings.threadNamingEffort,
|
||||
threadNamingModel: () => settings.threadNamingModel,
|
||||
};
|
||||
}
|
||||
|
|
@ -8,8 +8,9 @@ import type { Thread } from "../src/domain/threads/model";
|
|||
import type { CodexChatHost } from "../src/features/chat/host/contracts";
|
||||
import type { CodexChatView } from "../src/features/chat/host/view.obsidian";
|
||||
import type CodexPanelPlugin from "../src/main";
|
||||
import { DEFAULT_SETTINGS } from "../src/settings/model";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../src/settings/model";
|
||||
import { WorkspacePanelCoordinator } from "../src/workspace/panel-coordinator";
|
||||
import { chatPanelSettingsAccess } from "./features/chat/support/settings";
|
||||
import { waitForAsyncWork } from "./support/async";
|
||||
import { installObsidianDomShims } from "./support/dom";
|
||||
|
||||
|
|
@ -814,9 +815,10 @@ function chatView(CodexChatViewCtor: typeof CodexChatView, leaf: TestLeaf) {
|
|||
}
|
||||
|
||||
function chatHostFixture(): CodexChatHost {
|
||||
const settings: CodexPanelSettings = { ...DEFAULT_SETTINGS, codexPath: "codex", sendShortcut: "enter" };
|
||||
return {
|
||||
settingsRef: {
|
||||
settings: { ...DEFAULT_SETTINGS, codexPath: "codex", sendShortcut: "enter" },
|
||||
settings: chatPanelSettingsAccess(settings),
|
||||
vaultPath: "/vault",
|
||||
},
|
||||
workspace: {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ const CHAT_WORKSPACE_BOUNDARY_MESSAGE =
|
|||
const FEATURE_WORKSPACE_BOUNDARY_MESSAGE =
|
||||
"Feature modules must not import workspace modules; pass workspace capabilities through feature host contracts.";
|
||||
const WORKSPACE_CHAT_INTERNAL_MESSAGE = "Workspace modules may coordinate chat only through chat host contracts and Obsidian views.";
|
||||
const CHAT_HOST_RENDERING_LAYER_MESSAGE =
|
||||
"Chat host modules must not import chat presentation or UI layers; route render-facing projections through panel-owned adapters.";
|
||||
const CHAT_PANEL_RUNTIME_BOUNDARY_MESSAGE = "Chat panel modules must not import app-server adapters or chat host internals.";
|
||||
const CHAT_PRESENTATION_OUTER_LAYER_MESSAGE =
|
||||
"Chat presentation modules must stay pure view-model projection; keep application, app-server, host, panel, and UI dependencies outward.";
|
||||
|
|
@ -407,6 +409,7 @@ export function timestamp(): number {
|
|||
"no-chat-workspace-boundary-imports.grit",
|
||||
"no-feature-workspace-boundary-imports.grit",
|
||||
"no-workspace-chat-internal-imports.grit",
|
||||
"no-chat-host-rendering-layer-imports.grit",
|
||||
"no-chat-panel-runtime-boundary-imports.grit",
|
||||
"no-chat-presentation-outer-layer-imports.grit",
|
||||
"no-chat-ui-outer-layer-imports.grit",
|
||||
|
|
@ -476,6 +479,24 @@ export type Escape = WorkspacePanelCoordinator;
|
|||
import type { ChatStateStore } from "../application/state/store";
|
||||
|
||||
export type Allowed = ChatStateStore;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/host/rendering-escape.ts"),
|
||||
`
|
||||
import { statusText } from "../presentation/runtime/status";
|
||||
import { Toolbar } from "../ui/toolbar";
|
||||
|
||||
export const values = [statusText, Toolbar] satisfies unknown[];
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(cwd, "src/features/chat/host/rendering-allowed.ts"),
|
||||
`
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { ChatPanelToolbarSurface } from "../panel/surface/toolbar-projection";
|
||||
|
||||
export type Allowed = ChatStateStore | ChatPanelToolbarSurface;
|
||||
`.trimStart(),
|
||||
);
|
||||
await writeFile(
|
||||
|
|
@ -588,6 +609,8 @@ export const value = statusText;
|
|||
"src/features/chat/app-server/allowed.ts",
|
||||
"src/features/chat/host/workspace-escape.ts",
|
||||
"src/features/chat/host/workspace-allowed.ts",
|
||||
"src/features/chat/host/rendering-escape.ts",
|
||||
"src/features/chat/host/rendering-allowed.ts",
|
||||
"src/features/threads-view/workspace-escape.ts",
|
||||
"src/features/threads-view/workspace-allowed.ts",
|
||||
"src/workspace/chat-internal-escape.ts",
|
||||
|
|
@ -613,6 +636,10 @@ export const value = statusText;
|
|||
expect(pluginDiagnostics(report, "src/features/chat/app-server/allowed.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/chat/host/workspace-escape.ts")).toEqual([CHAT_WORKSPACE_BOUNDARY_MESSAGE]);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/host/workspace-allowed.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/chat/host/rendering-escape.ts")).toEqual(
|
||||
Array.from({ length: 2 }, () => CHAT_HOST_RENDERING_LAYER_MESSAGE),
|
||||
);
|
||||
expect(pluginDiagnostics(report, "src/features/chat/host/rendering-allowed.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/features/threads-view/workspace-escape.ts")).toEqual([FEATURE_WORKSPACE_BOUNDARY_MESSAGE]);
|
||||
expect(pluginDiagnostics(report, "src/features/threads-view/workspace-allowed.ts")).toEqual([]);
|
||||
expect(pluginMessages(report, "src/workspace/chat-internal-escape.ts")).toEqual([WORKSPACE_CHAT_INTERNAL_MESSAGE]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue