mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Inline thin chat host and thread surface adapters
This commit is contained in:
parent
de913e9d9d
commit
3737dcf0bd
15 changed files with 142 additions and 261 deletions
|
|
@ -1,37 +0,0 @@
|
|||
import type { CodexPanelSettings } from "../../../../settings/model";
|
||||
import type { SharedThreadCatalog } from "../../../../workspace/shared-thread-catalog";
|
||||
import type { ChatTurnDiffViewState } from "../../domain/turn-diff";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settingsRef: PluginSettingsRef;
|
||||
readonly workspace: WorkspacePanels;
|
||||
readonly threadCatalog: ThreadCatalogFacade;
|
||||
}
|
||||
|
||||
export interface PluginSettingsRef {
|
||||
readonly settings: CodexPanelSettings;
|
||||
readonly vaultPath: string;
|
||||
}
|
||||
|
||||
export interface WorkspacePanels {
|
||||
openThreadInNewView(threadId: string): Promise<unknown>;
|
||||
focusThreadInOpenView(threadId: string): Promise<boolean>;
|
||||
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
|
||||
}
|
||||
|
||||
export type ThreadCatalogFacade = Pick<
|
||||
SharedThreadCatalog,
|
||||
| "archiveThreadInCatalog"
|
||||
| "renameThreadInCatalog"
|
||||
| "refreshThreadsViewLiveState"
|
||||
| "refreshFromOpenSurface"
|
||||
| "setActiveThreads"
|
||||
| "setAppServerMetadata"
|
||||
| "fetchActiveThreads"
|
||||
| "activeThreadsSnapshot"
|
||||
| "appServerMetadataSnapshot"
|
||||
| "modelsSnapshot"
|
||||
| "observeActiveThreads"
|
||||
| "observeAppServerMetadata"
|
||||
| "observeModels"
|
||||
>;
|
||||
|
|
@ -5,16 +5,20 @@ import type { GoalActions } from "./goal-actions";
|
|||
import { createSelectionActions } from "./selection-actions";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import type { PluginSettingsRef, ThreadCatalogFacade, WorkspacePanels } from "../ports/chat-host";
|
||||
import type { AutoTitleController } from "./auto-title-controller";
|
||||
import { ThreadRenameEditorController } from "./rename-editor-controller";
|
||||
import { createThreadManagementActions, type ThreadManagementActions, type ThreadManagementActionsHost } from "./thread-management-actions";
|
||||
import { createThreadLifecycleParts } from "./lifecycle-parts";
|
||||
|
||||
interface ThreadPartsContext {
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: Pick<WorkspacePanels, "focusThreadInOpenView" | "openThreadInNewView">;
|
||||
threadCatalog: Pick<ThreadCatalogFacade, "refreshFromOpenSurface">;
|
||||
settingsRef: { readonly vaultPath: string };
|
||||
workspace: {
|
||||
focusThreadInOpenView: (threadId: string) => Promise<boolean>;
|
||||
openThreadInNewView: (threadId: string) => Promise<unknown>;
|
||||
};
|
||||
threadCatalog: {
|
||||
refreshFromOpenSurface(): void;
|
||||
};
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
@ -134,7 +138,9 @@ export function createThreadParts(context: ThreadPartsContext) {
|
|||
}
|
||||
|
||||
interface ThreadSelectionActionsContext {
|
||||
workspace: Pick<WorkspacePanels, "focusThreadInOpenView">;
|
||||
workspace: {
|
||||
focusThreadInOpenView: (threadId: string) => Promise<boolean>;
|
||||
};
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import type { AppServerClient } from "../../../../app-server/connection/client";
|
|||
import { recoverRolloutTokenUsage } from "../../../../app-server/services/rollout-token-usage";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../lifecycle";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import type { PluginSettingsRef } from "../ports/chat-host";
|
||||
import type { GoalActions } from "./goal-actions";
|
||||
import { HistoryController } from "./history-controller";
|
||||
import { createIdentitySync } from "./identity-sync";
|
||||
|
|
@ -10,7 +9,7 @@ import { ResumeController } from "./resume-controller";
|
|||
import { RestorationController } from "./restoration-controller";
|
||||
|
||||
export interface ThreadLifecyclePartsContext {
|
||||
settingsRef: PluginSettingsRef;
|
||||
settingsRef: { readonly vaultPath: string };
|
||||
stateStore: ChatStateStore;
|
||||
client: {
|
||||
currentClient: () => AppServerClient | null;
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
|||
import { activeTurnId } from "../application/state/root-reducer";
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { ChatPanelComposerShellState } from "../panel/shell-state";
|
||||
import type { PluginSettingsRef } from "../application/ports/chat-host";
|
||||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
import type { ChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import { ChatComposerController } from "../panel/composer-controller";
|
||||
import type { ChatPanelComposerProjection } from "../panel/surface/model";
|
||||
|
||||
export interface ConversationComposerContext {
|
||||
app: App;
|
||||
settingsRef: PluginSettingsRef;
|
||||
settingsRef: { readonly settings: CodexPanelSettings };
|
||||
stateStore: ChatStateStore;
|
||||
viewId: string;
|
||||
surface: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Notice } from "obsidian";
|
||||
|
||||
import type { ConnectionManager } from "../../../app-server/connection/connection-manager";
|
||||
import type { SharedServerMetadata } from "../../../domain/server/metadata";
|
||||
import type { Thread } from "../../../domain/threads/model";
|
||||
import type { ConnectionWorkTracker } from "../../../shared/lifecycle/connection-work";
|
||||
import type { ThreadCatalogFacade } from "../application/ports/chat-host";
|
||||
import type { ChatViewDeferredTasks } from "../application/lifecycle";
|
||||
import { ChatConnectionController, handleChatConnectionExit } from "../application/connection/connection-controller";
|
||||
import { createChatServerDiagnosticsActions, type ChatServerDiagnosticsActions } from "../app-server/actions/diagnostics";
|
||||
|
|
@ -42,10 +43,13 @@ export interface ChatConnectionBundleContext {
|
|||
vaultPath: string;
|
||||
connectionWork: ConnectionWorkTracker;
|
||||
deferredTasks: ChatViewDeferredTasks;
|
||||
threadCatalog: Pick<
|
||||
ThreadCatalogFacade,
|
||||
"setActiveThreads" | "setAppServerMetadata" | "fetchActiveThreads" | "archiveThreadInCatalog" | "renameThreadInCatalog"
|
||||
>;
|
||||
threadCatalog: {
|
||||
setActiveThreads(threads: readonly Thread[]): void;
|
||||
setAppServerMetadata(metadata: SharedServerMetadata): void;
|
||||
fetchActiveThreads(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
|
||||
archiveThreadInCatalog(threadId: string): void;
|
||||
renameThreadInCatalog(threadId: string, name: string | null): void;
|
||||
};
|
||||
goalSync: ThreadGoalSyncActions;
|
||||
autoTitle: AutoTitleController;
|
||||
status: ChatConnectionBundleStatus;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { GoalActions } from "../application/threads/goal-actions";
|
|||
import type { HistoryController } from "../application/threads/history-controller";
|
||||
import type { ChatInboundController } from "../app-server/inbound/controller";
|
||||
import type { MessageStreamItem, MessageStreamNoticeSection } from "../domain/message-stream/items";
|
||||
import type { PluginSettingsRef, WorkspacePanels } from "../application/ports/chat-host";
|
||||
import type { ChatTurnDiffViewState } from "../domain/turn-diff";
|
||||
import { MessageStreamPresenter } from "../panel/surface/message-stream-presenter";
|
||||
import type { ChatMessageScrollIntentState } from "../panel/surface/message-stream-scroll-intent";
|
||||
import type { ConversationComposerParts } from "./composer";
|
||||
|
|
@ -20,8 +20,10 @@ interface ConversationPartsContext {
|
|||
owner: Component;
|
||||
viewId: string;
|
||||
};
|
||||
settingsRef: PluginSettingsRef;
|
||||
workspace: WorkspacePanels;
|
||||
settingsRef: { readonly vaultPath: string };
|
||||
workspace: {
|
||||
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
|
||||
};
|
||||
state: {
|
||||
stateStore: ChatStateStore;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { Notice, type App, type Component, type EventRef } from "obsidian";
|
|||
|
||||
import { ConnectionManager } from "../../../app-server/connection/connection-manager";
|
||||
import type { ArchiveExportAdapter } from "../../../app-server/services/thread-archive-markdown";
|
||||
import type { CodexPanelSettings } from "../../../settings/model";
|
||||
import type { ConnectionWorkTracker } from "../../../shared/lifecycle/connection-work";
|
||||
import type { SharedThreadCatalog } from "../../../workspace/shared-thread-catalog";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import { createChatRuntimeSettingsActions } from "../application/runtime/settings-actions";
|
||||
import type { ChatConnectionPhase, ChatAction, ChatState } from "../application/state/root-reducer";
|
||||
|
|
@ -34,9 +36,43 @@ import type { MessageStreamItem, MessageStreamNoticeSection } from "../domain/me
|
|||
import { pendingRequestsSignature } from "../domain/pending-requests/signatures";
|
||||
import { ThreadOperations } from "../../threads/thread-operations";
|
||||
import { ThreadTitleService } from "../../threads/thread-title-service";
|
||||
import type { CodexChatHost } from "../application/ports/chat-host";
|
||||
import { messageStreamItems } from "../application/state/message-stream";
|
||||
import { threadTitleContextFromMessageStreamItems } from "../application/threads/title-context";
|
||||
import type { ChatTurnDiffViewState } from "../domain/turn-diff";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settingsRef: PluginSettingsRef;
|
||||
readonly workspace: WorkspacePanels;
|
||||
readonly threadCatalog: ChatThreadCatalog;
|
||||
}
|
||||
|
||||
export interface PluginSettingsRef {
|
||||
readonly settings: CodexPanelSettings;
|
||||
readonly vaultPath: string;
|
||||
}
|
||||
|
||||
interface WorkspacePanels {
|
||||
openThreadInNewView(threadId: string): Promise<unknown>;
|
||||
focusThreadInOpenView(threadId: string): Promise<boolean>;
|
||||
openTurnDiff(state: ChatTurnDiffViewState): Promise<void>;
|
||||
}
|
||||
|
||||
type ChatThreadCatalog = Pick<
|
||||
SharedThreadCatalog,
|
||||
| "archiveThreadInCatalog"
|
||||
| "renameThreadInCatalog"
|
||||
| "refreshThreadsViewLiveState"
|
||||
| "refreshFromOpenSurface"
|
||||
| "setActiveThreads"
|
||||
| "setAppServerMetadata"
|
||||
| "fetchActiveThreads"
|
||||
| "activeThreadsSnapshot"
|
||||
| "appServerMetadataSnapshot"
|
||||
| "modelsSnapshot"
|
||||
| "observeActiveThreads"
|
||||
| "observeAppServerMetadata"
|
||||
| "observeModels"
|
||||
>;
|
||||
|
||||
export interface ChatPanelEnvironment {
|
||||
obsidian: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ItemView, type ViewStateResult, type WorkspaceLeaf } from "obsidian";
|
||||
|
||||
import { VIEW_TYPE_CODEX_PANEL } from "../../../constants";
|
||||
import type { CodexChatHost } from "../application/ports/chat-host";
|
||||
import type { CodexChatHost } from "./runtime";
|
||||
import { ChatPanelSession } from "./session";
|
||||
import type { ChatSurfaceHandle } from "./surface-handle";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ import type { App } from "obsidian";
|
|||
import { VIEW_TYPE_CODEX_THREADS, VIEW_TYPE_CODEX_TURN_DIFF } from "./constants";
|
||||
import { AppServerQueryCache } from "./app-server/query/cache";
|
||||
import type { AppServerQueryContext } from "./app-server/query/keys";
|
||||
import type { CodexChatHost, PluginSettingsRef } from "./features/chat/application/ports/chat-host";
|
||||
import type { ChatTurnDiffViewState } from "./features/chat/domain/turn-diff";
|
||||
import type { CodexChatHost, PluginSettingsRef } from "./features/chat/host/runtime";
|
||||
import { persistedChatTurnDiffViewState } from "./features/chat/domain/turn-diff";
|
||||
import { CodexChatTurnDiffView } from "./features/chat/ui/turn-diff/view";
|
||||
import { openThreadPicker, type ThreadPickerHost } from "./features/thread-picker/modal";
|
||||
import type { CodexThreadsHost, CodexThreadsView } from "./features/threads-view/view";
|
||||
import { CodexThreadsView, type CodexThreadsHost } from "./features/threads-view/view";
|
||||
import type { CodexPanelSettingTabHost } from "./settings/tab";
|
||||
import { WorkspacePanelCoordinator } from "./workspace/panel-coordinator";
|
||||
import { createThreadSurfaceActions, type ThreadSurfaceActions } from "./workspace/thread-surface-actions";
|
||||
import { SharedThreadCatalog } from "./workspace/shared-thread-catalog";
|
||||
|
||||
export interface CodexPanelRuntimeOptions {
|
||||
|
|
@ -23,7 +22,6 @@ export interface CodexPanelRuntimeOptions {
|
|||
export class CodexPanelRuntime {
|
||||
private readonly appServerQueries = new AppServerQueryCache();
|
||||
readonly panels: WorkspacePanelCoordinator;
|
||||
private readonly threadSurfaces: ThreadSurfaceActions;
|
||||
readonly threadCatalog: SharedThreadCatalog;
|
||||
|
||||
constructor(private readonly options: CodexPanelRuntimeOptions) {
|
||||
|
|
@ -33,13 +31,22 @@ export class CodexPanelRuntime {
|
|||
this.threadCatalog.refreshThreadsViewLiveState();
|
||||
},
|
||||
});
|
||||
this.threadSurfaces = createThreadSurfaceActions({
|
||||
app: options.app,
|
||||
panels: this.panels,
|
||||
});
|
||||
this.threadCatalog = new SharedThreadCatalog({
|
||||
cache: this.appServerQueries,
|
||||
surfaces: this.threadSurfaces,
|
||||
surfaces: {
|
||||
invalidateThreadsFromOpenSurface: () => {
|
||||
this.invalidateThreadsFromOpenSurface();
|
||||
},
|
||||
applyThreadArchived: (threadId, archiveOptions) => {
|
||||
this.applyThreadArchived(threadId, archiveOptions);
|
||||
},
|
||||
applyThreadRenamed: (threadId, name) => {
|
||||
this.applyThreadRenamed(threadId, name);
|
||||
},
|
||||
refreshThreadsViewLiveState: () => {
|
||||
this.refreshThreadsViewLiveState();
|
||||
},
|
||||
},
|
||||
context: () => this.appServerQueryContext(),
|
||||
});
|
||||
}
|
||||
|
|
@ -117,7 +124,7 @@ export class CodexPanelRuntime {
|
|||
vaultPath: this.options.settingsRef.vaultPath,
|
||||
saveSettings: () => this.options.saveSettings(),
|
||||
refreshOpenViews: () => {
|
||||
this.threadSurfaces.refreshOpenViews();
|
||||
this.refreshOpenViews();
|
||||
},
|
||||
threadCatalog: this.threadCatalog,
|
||||
};
|
||||
|
|
@ -145,6 +152,51 @@ export class CodexPanelRuntime {
|
|||
await this.options.app.workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
private refreshOpenViews(): void {
|
||||
for (const view of this.panels.panelViews()) {
|
||||
view.surface.refreshSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private invalidateThreadsFromOpenSurface(): void {
|
||||
const chatView = this.panels.panelViews().find((view) => view.surface.openPanelSnapshot().connected);
|
||||
if (chatView) {
|
||||
void chatView.surface.refreshSharedThreadList();
|
||||
return;
|
||||
}
|
||||
|
||||
const threadsView = this.threadsViews().at(0);
|
||||
if (threadsView) void threadsView.refresh();
|
||||
}
|
||||
|
||||
private applyThreadArchived(threadId: string, archiveOptions: { closeOpenPanels?: boolean } = {}): void {
|
||||
const leavesToClose = archiveOptions.closeOpenPanels ? this.panels.panelLeavesForThread(threadId) : [];
|
||||
for (const view of this.panels.panelViews()) {
|
||||
view.surface.applyThreadArchived(threadId);
|
||||
}
|
||||
for (const leaf of leavesToClose) {
|
||||
leaf.detach();
|
||||
}
|
||||
}
|
||||
|
||||
private applyThreadRenamed(threadId: string, name: string | null): void {
|
||||
for (const view of this.panels.panelViews()) {
|
||||
view.surface.applyThreadRenamed(threadId, name);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshThreadsViewLiveState(): void {
|
||||
for (const view of this.threadsViews()) {
|
||||
view.refreshLiveState();
|
||||
}
|
||||
}
|
||||
|
||||
private threadsViews(): CodexThreadsView[] {
|
||||
return this.options.app.workspace
|
||||
.getLeavesOfType(VIEW_TYPE_CODEX_THREADS)
|
||||
.flatMap((leaf) => (leaf.view instanceof CodexThreadsView ? [leaf.view] : []));
|
||||
}
|
||||
|
||||
private appServerQueryContext(): AppServerQueryContext {
|
||||
return {
|
||||
codexPath: this.options.settingsRef.settings.codexPath,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ import type { SharedServerMetadata } from "../domain/server/metadata";
|
|||
import type { Thread } from "../domain/threads/model";
|
||||
import type { AppServerQueryCache } from "../app-server/query/cache";
|
||||
import { appServerQueryContextMatches, cloneAppServerQueryContext, type AppServerQueryContext } from "../app-server/query/keys";
|
||||
import type { ThreadSurfaceActions } from "./thread-surface-actions";
|
||||
|
||||
interface ThreadSurfaceActions {
|
||||
invalidateThreadsFromOpenSurface(): void;
|
||||
applyThreadArchived(threadId: string, options?: { closeOpenPanels?: boolean }): void;
|
||||
applyThreadRenamed(threadId: string, name: string | null): void;
|
||||
refreshThreadsViewLiveState(): void;
|
||||
}
|
||||
|
||||
export interface SharedThreadCatalogOptions {
|
||||
cache: AppServerQueryCache;
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
import type { App } from "obsidian";
|
||||
|
||||
import { VIEW_TYPE_CODEX_THREADS } from "../constants";
|
||||
import { CodexThreadsView } from "../features/threads-view/view";
|
||||
import type { WorkspacePanelCoordinator } from "./panel-coordinator";
|
||||
|
||||
export interface ThreadSurfaceActionsOptions {
|
||||
app: App;
|
||||
panels: WorkspacePanelCoordinator;
|
||||
}
|
||||
|
||||
export interface ThreadSurfaceActions {
|
||||
refreshOpenViews(): void;
|
||||
invalidateThreadsFromOpenSurface(): void;
|
||||
applyThreadArchived(threadId: string, options?: { closeOpenPanels?: boolean }): void;
|
||||
applyThreadRenamed(threadId: string, name: string | null): void;
|
||||
refreshThreadsViewLiveState(): void;
|
||||
}
|
||||
|
||||
export function createThreadSurfaceActions(options: ThreadSurfaceActionsOptions): ThreadSurfaceActions {
|
||||
const threadsViews = (): CodexThreadsView[] =>
|
||||
options.app.workspace
|
||||
.getLeavesOfType(VIEW_TYPE_CODEX_THREADS)
|
||||
.flatMap((leaf) => (leaf.view instanceof CodexThreadsView ? [leaf.view] : []));
|
||||
|
||||
const invalidateThreadsFromOpenSurface = (): void => {
|
||||
const chatView = options.panels.panelViews().find((view) => view.surface.openPanelSnapshot().connected);
|
||||
if (chatView) {
|
||||
void chatView.surface.refreshSharedThreadList();
|
||||
return;
|
||||
}
|
||||
|
||||
const threadsView = threadsViews().at(0);
|
||||
if (threadsView) void threadsView.refresh();
|
||||
};
|
||||
|
||||
return {
|
||||
refreshOpenViews(): void {
|
||||
for (const view of options.panels.panelViews()) {
|
||||
view.surface.refreshSettings();
|
||||
}
|
||||
},
|
||||
|
||||
invalidateThreadsFromOpenSurface,
|
||||
|
||||
applyThreadArchived(threadId: string, archiveOptions: { closeOpenPanels?: boolean } = {}): void {
|
||||
const leavesToClose = archiveOptions.closeOpenPanels ? options.panels.panelLeavesForThread(threadId) : [];
|
||||
for (const view of options.panels.panelViews()) {
|
||||
view.surface.applyThreadArchived(threadId);
|
||||
}
|
||||
for (const leaf of leavesToClose) {
|
||||
leaf.detach();
|
||||
}
|
||||
},
|
||||
|
||||
applyThreadRenamed(threadId: string, name: string | null): void {
|
||||
for (const view of options.panels.panelViews()) {
|
||||
view.surface.applyThreadRenamed(threadId, name);
|
||||
}
|
||||
},
|
||||
|
||||
refreshThreadsViewLiveState(): void {
|
||||
for (const view of threadsViews()) {
|
||||
view.refreshLiveState();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
|
||||
import type { CodexChatHost } from "../../../src/features/chat/application/ports/chat-host";
|
||||
import type { CodexChatHost } from "../../../src/features/chat/host/runtime";
|
||||
import { createServerDiagnostics } from "../../../src/domain/server/diagnostics";
|
||||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { VIEW_TYPE_CODEX_PANEL } from "../src/constants";
|
|||
import { DEFAULT_SETTINGS } from "../src/settings/model";
|
||||
import type CodexPanelPlugin from "../src/main";
|
||||
import type { CodexChatView } from "../src/features/chat/host/view";
|
||||
import type { CodexChatHost } from "../src/features/chat/application/ports/chat-host";
|
||||
import type { CodexChatHost } from "../src/features/chat/host/runtime";
|
||||
import type { Thread } from "../src/domain/threads/model";
|
||||
import type { WorkspacePanelCoordinator } from "../src/workspace/panel-coordinator";
|
||||
import type { SharedThreadCatalog } from "../src/workspace/shared-thread-catalog";
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import { createServerDiagnostics } from "../../src/domain/server/diagnostics";
|
|||
import type { SharedServerMetadata } from "../../src/domain/server/metadata";
|
||||
import type { Thread } from "../../src/domain/threads/model";
|
||||
import { SharedThreadCatalog } from "../../src/workspace/shared-thread-catalog";
|
||||
import type { ThreadSurfaceActions } from "../../src/workspace/thread-surface-actions";
|
||||
|
||||
type MockSurfaceActions = ThreadSurfaceActions & {
|
||||
interface MockSurfaceActions {
|
||||
refreshOpenViews: Mock<() => void>;
|
||||
invalidateThreadsFromOpenSurface: Mock<() => void>;
|
||||
applyThreadArchived: Mock<(threadId: string, options?: { closeOpenPanels?: boolean }) => void>;
|
||||
applyThreadRenamed: Mock<(threadId: string, name: string | null) => void>;
|
||||
};
|
||||
refreshThreadsViewLiveState: Mock<() => void>;
|
||||
}
|
||||
|
||||
describe("SharedThreadCatalog", () => {
|
||||
it("applies thread snapshots to the shared cache and active observers", () => {
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { VIEW_TYPE_CODEX_THREADS } from "../../src/constants";
|
||||
import { CodexThreadsView } from "../../src/features/threads-view/view";
|
||||
import { createThreadSurfaceActions } from "../../src/workspace/thread-surface-actions";
|
||||
import type { OpenCodexPanelSnapshot } from "../../src/workspace/open-panel-snapshot";
|
||||
|
||||
describe("createThreadSurfaceActions", () => {
|
||||
it("falls back to the threads view when no connected chat panel can refresh shared threads", () => {
|
||||
const disconnectedPanelRefresh = vi.fn().mockResolvedValue(undefined);
|
||||
const threadsRefresh = vi.fn().mockResolvedValue(undefined);
|
||||
const threadSurfaces = createThreadSurfaceActions({
|
||||
app: {
|
||||
workspace: {
|
||||
getLeavesOfType: vi.fn((type: string) =>
|
||||
type === VIEW_TYPE_CODEX_THREADS ? [{ view: threadsView({ refresh: threadsRefresh }) }] : [],
|
||||
),
|
||||
},
|
||||
} as never,
|
||||
panels: {
|
||||
panelViews: () => [
|
||||
{
|
||||
surface: {
|
||||
openPanelSnapshot: () => panelSnapshot({ connected: false }),
|
||||
refreshSharedThreadList: disconnectedPanelRefresh,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
});
|
||||
|
||||
threadSurfaces.invalidateThreadsFromOpenSurface();
|
||||
|
||||
expect(disconnectedPanelRefresh).not.toHaveBeenCalled();
|
||||
expect(threadsRefresh).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("uses a connected chat panel before falling back to the threads view", () => {
|
||||
const disconnectedPanelRefresh = vi.fn().mockResolvedValue(undefined);
|
||||
const connectedPanelRefresh = vi.fn().mockResolvedValue(undefined);
|
||||
const threadsRefresh = vi.fn().mockResolvedValue(undefined);
|
||||
const threadSurfaces = createThreadSurfaceActions({
|
||||
app: {
|
||||
workspace: {
|
||||
getLeavesOfType: vi.fn((type: string) =>
|
||||
type === VIEW_TYPE_CODEX_THREADS ? [{ view: threadsView({ refresh: threadsRefresh }) }] : [],
|
||||
),
|
||||
},
|
||||
} as never,
|
||||
panels: {
|
||||
panelViews: () => [
|
||||
{
|
||||
surface: {
|
||||
openPanelSnapshot: () => panelSnapshot({ viewId: "disconnected", connected: false }),
|
||||
refreshSharedThreadList: disconnectedPanelRefresh,
|
||||
},
|
||||
},
|
||||
{
|
||||
surface: {
|
||||
openPanelSnapshot: () => panelSnapshot({ viewId: "connected", connected: true }),
|
||||
refreshSharedThreadList: connectedPanelRefresh,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
});
|
||||
|
||||
threadSurfaces.invalidateThreadsFromOpenSurface();
|
||||
|
||||
expect(disconnectedPanelRefresh).not.toHaveBeenCalled();
|
||||
expect(connectedPanelRefresh).toHaveBeenCalledOnce();
|
||||
expect(threadsRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies known thread mutations without refreshing thread lists", () => {
|
||||
const panel = {
|
||||
surface: {
|
||||
openPanelSnapshot: () => panelSnapshot({ connected: true }),
|
||||
refreshSharedThreadList: vi.fn().mockResolvedValue(undefined),
|
||||
applyThreadArchived: vi.fn(),
|
||||
applyThreadRenamed: vi.fn(),
|
||||
},
|
||||
};
|
||||
const threadSurfaces = createThreadSurfaceActions({
|
||||
app: {
|
||||
workspace: {
|
||||
getLeavesOfType: vi.fn(() => []),
|
||||
},
|
||||
} as never,
|
||||
panels: {
|
||||
panelViews: () => [panel],
|
||||
panelLeavesForThread: vi.fn(() => []),
|
||||
} as never,
|
||||
});
|
||||
|
||||
threadSurfaces.applyThreadRenamed("thread", "Renamed");
|
||||
threadSurfaces.applyThreadArchived("thread");
|
||||
|
||||
expect(panel.surface.applyThreadRenamed).toHaveBeenCalledWith("thread", "Renamed");
|
||||
expect(panel.surface.applyThreadArchived).toHaveBeenCalledWith("thread");
|
||||
expect(panel.surface.refreshSharedThreadList).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function threadsView(overrides: Partial<CodexThreadsView> = {}): CodexThreadsView {
|
||||
return Object.assign(Object.create(CodexThreadsView.prototype), overrides) as CodexThreadsView;
|
||||
}
|
||||
|
||||
function panelSnapshot(overrides: Partial<OpenCodexPanelSnapshot> = {}): OpenCodexPanelSnapshot {
|
||||
return {
|
||||
viewId: "panel",
|
||||
threadId: "thread",
|
||||
lastFocused: false,
|
||||
turnLifecycle: { kind: "idle" },
|
||||
pendingApprovals: 0,
|
||||
pendingUserInputs: 0,
|
||||
hasComposerDraft: false,
|
||||
connected: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue