mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
refactor: consolidate thin module ownership
This commit is contained in:
parent
67289765c6
commit
f4616cf945
23 changed files with 84 additions and 105 deletions
|
|
@ -17,7 +17,6 @@ import type { SelectionRewriteTransport } from "./features/selection-rewrite/tra
|
|||
import { openThreadPicker, type ThreadPickerController } from "./features/thread-picker/modal.obsidian";
|
||||
import { createThreadOperationsTransport, createThreadTitleTransport } from "./features/threads/app-server/workflow-transports";
|
||||
import { createThreadCatalog, type ThreadCatalog, type ThreadCatalogEvent } from "./features/threads/catalog/thread-catalog";
|
||||
import { createThreadNameMutationCoordinator } from "./features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import type { ThreadsViewHost, ThreadsViewSettingsAccess } from "./features/threads-view/session";
|
||||
import type { ThreadsViewPanelActivity } from "./features/threads-view/state";
|
||||
import type { ThreadsRuntimeView } from "./features/threads-view/view.obsidian";
|
||||
|
|
@ -49,7 +48,7 @@ export class CodexExecutionRuntime implements AppServerClientAccess {
|
|||
private readonly appServerQueries: AppServerQueryCache;
|
||||
private readonly threadCatalog: ThreadCatalog;
|
||||
readonly settingsDynamicData: SettingsDynamicDataAccess;
|
||||
private readonly threadNameMutations = createThreadNameMutationCoordinator();
|
||||
private readonly threadNameMutations = createKeyedOperationQueue<string>();
|
||||
private readonly threadGoalOperations = createThreadGoalOperationCoordinator();
|
||||
private readonly runtimeSettingsCommitQueue = createKeyedOperationQueue<string>();
|
||||
private readonly shortLivedClients = new Set<AppServerClient>();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { RUNNING_EXECUTION_STATE, type ThreadStreamExecutionState } from "../../../domain/thread-stream/execution-state";
|
||||
import type { ExecutionState } from "../../../domain/thread-stream/items";
|
||||
import { type ExecutionState, RUNNING_EXECUTION_STATE, type ThreadStreamExecutionState } from "../../../domain/thread-stream/items";
|
||||
|
||||
export { RUNNING_EXECUTION_STATE };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
export interface DailyNoteReferenceCandidate {
|
||||
keyword: "today" | "tomorrow" | "yesterday";
|
||||
display: string;
|
||||
name: string;
|
||||
path: string;
|
||||
linktext: string;
|
||||
}
|
||||
|
|
@ -1,6 +1,28 @@
|
|||
import type { VaultFileReference } from "../../../../domain/chat/input";
|
||||
import type { DailyNoteReferenceCandidate } from "./daily-note-references";
|
||||
import type { NoteCandidate } from "./suggestions";
|
||||
|
||||
export interface DailyNoteReferenceCandidate {
|
||||
keyword: "today" | "tomorrow" | "yesterday";
|
||||
display: string;
|
||||
name: string;
|
||||
path: string;
|
||||
linktext: string;
|
||||
}
|
||||
|
||||
export interface NoteHeadingCandidate {
|
||||
heading: string;
|
||||
linkHeading: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface NoteCandidate {
|
||||
basename: string;
|
||||
displayName: string;
|
||||
path: string;
|
||||
mtime: number;
|
||||
linktext: string;
|
||||
headings: NoteHeadingCandidate[];
|
||||
recentIndex: number | null;
|
||||
}
|
||||
|
||||
export interface NoteCandidateProvider {
|
||||
candidates(sourcePath: string): readonly NoteCandidate[];
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
type SelectionContextReference,
|
||||
selectionContextReferenceMarker,
|
||||
} from "./context-references";
|
||||
import type { DailyNoteReferenceCandidate } from "./daily-note-references";
|
||||
import type { DailyNoteReferenceCandidate, NoteCandidate, NoteHeadingCandidate } from "./note-context";
|
||||
import { isSlashCommandName, SLASH_COMMANDS, type SlashCommandName, slashCommandSubcommands } from "./slash-commands";
|
||||
import {
|
||||
partialThreadTitleQuery,
|
||||
|
|
@ -51,22 +51,6 @@ export interface ComposerSuggestionOptions {
|
|||
tagCandidates?: readonly string[] | (() => readonly string[]);
|
||||
}
|
||||
|
||||
export interface NoteCandidate {
|
||||
basename: string;
|
||||
displayName: string;
|
||||
path: string;
|
||||
mtime: number;
|
||||
linktext: string;
|
||||
headings: NoteHeadingCandidate[];
|
||||
recentIndex: number | null;
|
||||
}
|
||||
|
||||
interface NoteHeadingCandidate {
|
||||
heading: string;
|
||||
linkHeading: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
interface NoteCandidateMatch {
|
||||
file: NoteCandidate;
|
||||
match: SearchResult;
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
import type { ExecutionState } from "./items";
|
||||
|
||||
export type ThreadStreamExecutionState = Exclude<ExecutionState, null>;
|
||||
export const RUNNING_EXECUTION_STATE: ThreadStreamExecutionState = "running";
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { RUNNING_EXECUTION_STATE } from "../execution-state";
|
||||
import type { ThreadStreamItem, ThreadStreamItemKind } from "../items";
|
||||
import { RUNNING_EXECUTION_STATE, type ThreadStreamItem, type ThreadStreamItemKind } from "../items";
|
||||
|
||||
export const STREAMED_COMMAND_RUNNING_TEXT = "Command running";
|
||||
export const STREAMED_MCP_PROGRESS_LABEL = "mcp progress";
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export type ThreadStreamItemKind =
|
|||
| "reviewResult";
|
||||
type ThreadStreamRole = "user" | "assistant" | "system" | "tool";
|
||||
export type ExecutionState = "running" | "completed" | "failed" | null;
|
||||
export type ThreadStreamExecutionState = Exclude<ExecutionState, null>;
|
||||
export const RUNNING_EXECUTION_STATE: ThreadStreamExecutionState = "running";
|
||||
type DialogueState = "streaming" | "completed";
|
||||
|
||||
interface ThreadStreamBase {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import type { KeyedOperationQueue } from "../../../shared/runtime/keyed-operatio
|
|||
import type { ThreadCatalogEventSink, ThreadCatalogPaginatedActiveReader } from "../../threads/catalog/thread-catalog";
|
||||
import type { ArchiveExportDestination, ArchiveExportSettings } from "../../threads/workflows/archive-export";
|
||||
import type { ThreadTitleTransport } from "../../threads/workflows/ports";
|
||||
import type { ThreadNameMutationCoordinator } from "../../threads/workflows/thread-name-mutation-coordinator";
|
||||
import type { TurnDiffViewState } from "../../turn-diff/model";
|
||||
import type { ThreadGoalOperationCoordinator } from "../application/threads/goal-actions";
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ export interface CodexChatHost {
|
|||
readonly workspace: WorkspacePanels;
|
||||
readonly appServerQueries: ChatAppServerQueries;
|
||||
readonly threadCatalog: ChatThreadCatalog;
|
||||
readonly threadNameMutations: ThreadNameMutationCoordinator;
|
||||
readonly threadNameMutations: KeyedOperationQueue<string>;
|
||||
readonly threadTitleTransport: ThreadTitleTransport;
|
||||
readonly threadGoalOperations: ThreadGoalOperationCoordinator;
|
||||
readonly runtimeSettingsCommitQueue: KeyedOperationQueue<string>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { type App, moment, normalizePath, TFile } from "obsidian";
|
||||
import { appHasDailyNotesPluginLoaded, getDailyNoteSettings, type IPeriodicNoteSettings } from "obsidian-daily-notes-interface";
|
||||
|
||||
import type { DailyNoteReferenceCandidate } from "../../application/composer/daily-note-references";
|
||||
import type { DailyNoteReferenceCandidate } from "../../application/composer/note-context";
|
||||
import { displayNameForFile, linktextForFile } from "./vault-note-links.obsidian";
|
||||
|
||||
const RELATIVE_DAILY_NOTES = [
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import type { App, EventRef } from "obsidian";
|
|||
import { stripHeadingForLink, TFile } from "obsidian";
|
||||
|
||||
import type { VaultFileReference } from "../../../../domain/chat/input";
|
||||
import type { NoteCandidateProvider } from "../../application/composer/note-context";
|
||||
import type { NoteCandidate } from "../../application/composer/suggestions";
|
||||
import type { NoteCandidate, NoteCandidateProvider } from "../../application/composer/note-context";
|
||||
import { configuredDailyNoteReferences } from "./vault-daily-note-references.obsidian";
|
||||
import { displayNameForFile, linktextForFile } from "./vault-note-links.obsidian";
|
||||
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ import {
|
|||
selectionContextReferenceMarker,
|
||||
} from "../application/composer/context-references";
|
||||
import type { ComposerInputSnapshot } from "../application/composer/input-snapshot";
|
||||
import type { NoteCandidateProvider } from "../application/composer/note-context";
|
||||
import type { NoteCandidate, NoteCandidateProvider } from "../application/composer/note-context";
|
||||
import { activePanelOperationForSlashCommandSuggestion } from "../application/composer/slash-commands";
|
||||
import {
|
||||
activeComposerSuggestions,
|
||||
applyComposerSuggestionInsertion,
|
||||
type ComposerSuggestion,
|
||||
composerSuggestionNavigationDirection,
|
||||
type NoteCandidate,
|
||||
nextComposerSuggestionIndex,
|
||||
} from "../application/composer/suggestions";
|
||||
import { type ThreadCommandTarget, threadCommandTargetForDraft } from "../application/composer/thread-title-argument";
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import type { Thread } from "../../domain/threads/model";
|
|||
import type { ThreadRenameLifecycleEvent } from "../../domain/threads/rename-lifecycle";
|
||||
import { DeferredTask } from "../../shared/runtime/deferred-task";
|
||||
import { isStaleExecutionRuntimeError } from "../../shared/runtime/execution-runtime-lifetime";
|
||||
import type { KeyedOperationQueue } from "../../shared/runtime/keyed-operation-queue";
|
||||
import { OwnerLifetime } from "../../shared/runtime/owner-lifetime";
|
||||
import type { ThreadCatalogEventSink, ThreadCatalogPaginatedActiveReader } from "../threads/catalog/thread-catalog";
|
||||
import type { ArchiveExportDestination, ArchiveExportSettings } from "../threads/workflows/archive-export";
|
||||
import type { ThreadOperationsTransport, ThreadTitleTransport } from "../threads/workflows/ports";
|
||||
import type { ThreadNameMutationCoordinator } from "../threads/workflows/thread-name-mutation-coordinator";
|
||||
import { createThreadOperations, type ThreadOperations } from "../threads/workflows/thread-operations";
|
||||
import { createThreadTitleService, type ThreadTitleService } from "../threads/workflows/thread-title-service";
|
||||
import { isThreadsArchiveConfirmPointer, renderThreadsViewShell, unmountThreadsViewShell } from "./shell.dom";
|
||||
|
|
@ -19,7 +19,7 @@ export interface ThreadsViewHost {
|
|||
readonly settings: ThreadsViewSettingsAccess;
|
||||
readonly vaultPath: string;
|
||||
readonly threadCatalog: ThreadsViewThreadCatalog;
|
||||
readonly threadNameMutations: ThreadNameMutationCoordinator;
|
||||
readonly threadNameMutations: KeyedOperationQueue<string>;
|
||||
readonly threadOperationsTransport: ThreadOperationsTransport;
|
||||
readonly threadTitleTransport: ThreadTitleTransport;
|
||||
openNewPanel(): Promise<unknown>;
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import { createKeyedOperationQueue } from "../../../shared/runtime/keyed-operation-queue";
|
||||
|
||||
export interface ThreadNameMutationCoordinator {
|
||||
run<T>(threadId: string, operation: () => Promise<T>): Promise<T>;
|
||||
}
|
||||
|
||||
export function createThreadNameMutationCoordinator(): ThreadNameMutationCoordinator {
|
||||
return createKeyedOperationQueue();
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { normalizeExplicitThreadName, type Thread } from "../../../domain/threads/model";
|
||||
import { threadDisplayTitle } from "../../../domain/threads/title";
|
||||
import type { KeyedOperationQueue } from "../../../shared/runtime/keyed-operation-queue";
|
||||
import type { ThreadCatalogEventSink } from "../catalog/thread-catalog";
|
||||
import { type ArchiveExportDestination, type ArchiveExportSettings, exportArchivedThreadMarkdown } from "./archive-export";
|
||||
import type { ThreadOperationsTransport } from "./ports";
|
||||
import type { ThreadNameMutationCoordinator } from "./thread-name-mutation-coordinator";
|
||||
|
||||
export interface ThreadOperationsHost {
|
||||
transport: ThreadOperationsTransport;
|
||||
nameMutations: ThreadNameMutationCoordinator;
|
||||
nameMutations: KeyedOperationQueue<string>;
|
||||
archiveExport: {
|
||||
settings(): ArchiveExportSettings;
|
||||
enabled(): boolean;
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import {
|
|||
} from "../../../../../src/features/chat/application/threads/auto-title-coordinator";
|
||||
import { threadTitleContextFromThreadStreamItems } from "../../../../../src/features/chat/application/threads/title-context";
|
||||
import { createThreadOperationsTransport } from "../../../../../src/features/threads/app-server/workflow-transports";
|
||||
import { createThreadNameMutationCoordinator } from "../../../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import { createThreadOperations } from "../../../../../src/features/threads/workflows/thread-operations";
|
||||
import { createThreadTitleService } from "../../../../../src/features/threads/workflows/thread-title-service";
|
||||
import { DEFAULT_SETTINGS } from "../../../../../src/settings/model";
|
||||
import { createKeyedOperationQueue } from "../../../../../src/shared/runtime/keyed-operation-queue";
|
||||
import { deferred } from "../../../../support/async";
|
||||
|
||||
describe("AutoTitleCoordinator", () => {
|
||||
|
|
@ -193,7 +193,7 @@ function coordinatorFixture(
|
|||
transport: createThreadOperationsTransport({
|
||||
withClient: async (operation) => operation(currentClient()),
|
||||
}),
|
||||
nameMutations: createThreadNameMutationCoordinator(),
|
||||
nameMutations: createKeyedOperationQueue(),
|
||||
archiveExport: {
|
||||
settings: () => DEFAULT_SETTINGS,
|
||||
enabled: () => false,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import type { ChatPanelEnvironment } from "../../../../src/features/chat/host/co
|
|||
import { createChatViewDeferredTasks } from "../../../../src/features/chat/host/session/deferred-work";
|
||||
import { ChatPanelSessionRuntime } from "../../../../src/features/chat/host/session-runtime";
|
||||
import { createChatThreadStreamScrollBinding } from "../../../../src/features/chat/panel/thread-stream-scroll-binding";
|
||||
import { createThreadNameMutationCoordinator } from "../../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { StaleExecutionRuntimeError } from "../../../../src/shared/runtime/execution-runtime-lifetime";
|
||||
import { createKeyedOperationQueue } from "../../../../src/shared/runtime/keyed-operation-queue";
|
||||
|
|
@ -269,7 +268,7 @@ describe("ChatPanelSessionRuntime actions", () => {
|
|||
},
|
||||
appServerQueries,
|
||||
threadCatalog,
|
||||
threadNameMutations: createThreadNameMutationCoordinator(),
|
||||
threadNameMutations: createKeyedOperationQueue(),
|
||||
threadGoalOperations: createThreadGoalOperationCoordinator(),
|
||||
runtimeSettingsCommitQueue: createKeyedOperationQueue(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import type { Thread } from "../../../../src/domain/threads/model";
|
|||
import { createThreadGoalOperationCoordinator } from "../../../../src/features/chat/application/threads/goal-actions";
|
||||
import type { ChatRuntimeView, ChatViewRuntimeOwner, CodexChatHost } from "../../../../src/features/chat/host/contracts";
|
||||
import type { ThreadCatalogEvent } from "../../../../src/features/threads/catalog/thread-catalog";
|
||||
import { createThreadNameMutationCoordinator } from "../../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { createKeyedOperationQueue } from "../../../../src/shared/runtime/keyed-operation-queue";
|
||||
import { notices } from "../../../mocks/obsidian";
|
||||
|
|
@ -558,7 +557,7 @@ export function chatHost(overrides: ChatHostFixtureOverrides = {}): TestCodexCha
|
|||
activeThreads = threads;
|
||||
emitActiveThreads();
|
||||
},
|
||||
threadNameMutations: overrides.threadNameMutations ?? createThreadNameMutationCoordinator(),
|
||||
threadNameMutations: overrides.threadNameMutations ?? createKeyedOperationQueue(),
|
||||
threadTitleTransport: {
|
||||
persistedContext: vi.fn().mockResolvedValue(null),
|
||||
generateTitle: vi.fn().mockResolvedValue(null),
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import type { ObservedPaginatedResult } from "../../../src/app-server/query/obse
|
|||
import type * as ThreadTitleGeneratorModule from "../../../src/app-server/services/thread-title-generation";
|
||||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import { createThreadOperationsTransport, createThreadTitleTransport } from "../../../src/features/threads/app-server/workflow-transports";
|
||||
import { createThreadNameMutationCoordinator } from "../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import type { ThreadsViewHost } from "../../../src/features/threads-view/session";
|
||||
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
|
||||
import { createKeyedOperationQueue } from "../../../src/shared/runtime/keyed-operation-queue";
|
||||
import { notices } from "../../mocks/obsidian";
|
||||
import { deferred, waitForAsyncWork } from "../../support/async";
|
||||
import { changeInputValue, installObsidianDomShims } from "../../support/dom";
|
||||
|
|
@ -844,7 +844,7 @@ function threadsHost(overrides: Record<string, unknown> = {}) {
|
|||
}),
|
||||
},
|
||||
vaultPath: "/vault",
|
||||
threadNameMutations: createThreadNameMutationCoordinator(),
|
||||
threadNameMutations: createKeyedOperationQueue(),
|
||||
threadOperationsTransport: createThreadOperationsTransport(clientAccess),
|
||||
threadTitleTransport: createThreadTitleTransport({
|
||||
clientAccess,
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createThreadNameMutationCoordinator } from "../../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("ThreadNameMutationCoordinator", () => {
|
||||
it("does not let a failed mutation block the next mutation for the same thread", async () => {
|
||||
const coordinator = createThreadNameMutationCoordinator();
|
||||
const firstMutation = deferred<void>();
|
||||
const nextMutation = vi.fn().mockResolvedValue("saved");
|
||||
|
||||
const failed = coordinator.run("thread", async () => {
|
||||
await firstMutation.promise;
|
||||
throw new Error("Save failed.");
|
||||
});
|
||||
const failure = expect(failed).rejects.toThrow("Save failed.");
|
||||
const next = coordinator.run("thread", nextMutation);
|
||||
|
||||
expect(nextMutation).not.toHaveBeenCalled();
|
||||
firstMutation.resolve();
|
||||
|
||||
await failure;
|
||||
await expect(next).resolves.toBe("saved");
|
||||
expect(nextMutation).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not serialize mutations for different threads", async () => {
|
||||
const coordinator = createThreadNameMutationCoordinator();
|
||||
const firstMutation = deferred<void>();
|
||||
const blocked = coordinator.run("first", () => firstMutation.promise);
|
||||
|
||||
await expect(coordinator.run("second", async () => "saved")).resolves.toBe("saved");
|
||||
|
||||
firstMutation.resolve();
|
||||
await blocked;
|
||||
});
|
||||
});
|
||||
|
|
@ -5,13 +5,13 @@ import type { ThreadRecord } from "../../../../src/app-server/protocol/thread";
|
|||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
import { createThreadOperationsTransport } from "../../../../src/features/threads/app-server/workflow-transports";
|
||||
import type { ArchiveExportDestination } from "../../../../src/features/threads/workflows/archive-export";
|
||||
import { createThreadNameMutationCoordinator } from "../../../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import {
|
||||
type ArchiveThreadResult,
|
||||
createThreadOperations,
|
||||
type ThreadOperationsHost,
|
||||
} from "../../../../src/features/threads/workflows/thread-operations";
|
||||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { createKeyedOperationQueue } from "../../../../src/shared/runtime/keyed-operation-queue";
|
||||
import { deferred } from "../../../support/async";
|
||||
import { legacyTurnContextManifestText } from "../../../support/legacy-turn-context-manifest";
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ function operationsFixture(options: { client?: MockClient | null | (() => MockCl
|
|||
return result;
|
||||
},
|
||||
}),
|
||||
nameMutations: createThreadNameMutationCoordinator(),
|
||||
nameMutations: createKeyedOperationQueue(),
|
||||
archiveExport: {
|
||||
settings: archiveExportSettings,
|
||||
enabled: () => false,
|
||||
|
|
|
|||
37
tests/shared/runtime/keyed-operation-queue.test.ts
Normal file
37
tests/shared/runtime/keyed-operation-queue.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createKeyedOperationQueue } from "../../../src/shared/runtime/keyed-operation-queue";
|
||||
import { deferred } from "../../support/async";
|
||||
|
||||
describe("KeyedOperationQueue", () => {
|
||||
it("does not let a failed operation block the next operation for the same key", async () => {
|
||||
const queue = createKeyedOperationQueue<string>();
|
||||
const firstOperation = deferred<void>();
|
||||
const nextOperation = vi.fn().mockResolvedValue("saved");
|
||||
|
||||
const failed = queue.run("key", async () => {
|
||||
await firstOperation.promise;
|
||||
throw new Error("Save failed.");
|
||||
});
|
||||
const failure = expect(failed).rejects.toThrow("Save failed.");
|
||||
const next = queue.run("key", nextOperation);
|
||||
|
||||
expect(nextOperation).not.toHaveBeenCalled();
|
||||
firstOperation.resolve();
|
||||
|
||||
await failure;
|
||||
await expect(next).resolves.toBe("saved");
|
||||
expect(nextOperation).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not serialize operations for different keys", async () => {
|
||||
const queue = createKeyedOperationQueue<string>();
|
||||
const firstOperation = deferred<void>();
|
||||
const blocked = queue.run("first", () => firstOperation.promise);
|
||||
|
||||
await expect(queue.run("second", async () => "saved")).resolves.toBe("saved");
|
||||
|
||||
firstOperation.resolve();
|
||||
await blocked;
|
||||
});
|
||||
});
|
||||
|
|
@ -8,7 +8,6 @@ import type { Thread } from "../../src/domain/threads/model";
|
|||
import { createThreadGoalOperationCoordinator } from "../../src/features/chat/application/threads/goal-actions";
|
||||
import type { CodexChatHost } from "../../src/features/chat/host/contracts";
|
||||
import type { CodexChatView } from "../../src/features/chat/host/view.obsidian";
|
||||
import { createThreadNameMutationCoordinator } from "../../src/features/threads/workflows/thread-name-mutation-coordinator";
|
||||
import type CodexPanelPlugin from "../../src/main";
|
||||
import { type CodexPanelSettings, DEFAULT_SETTINGS } from "../../src/settings/model";
|
||||
import { createKeyedOperationQueue } from "../../src/shared/runtime/keyed-operation-queue";
|
||||
|
|
@ -129,7 +128,7 @@ function chatHostFixture(): CodexChatHost {
|
|||
withClient: vi.fn(() => Promise.reject(new Error("Unexpected app-server client request."))),
|
||||
},
|
||||
appServerContext: { codexPath: settings.codexPath, vaultPath: "/vault" },
|
||||
threadNameMutations: createThreadNameMutationCoordinator(),
|
||||
threadNameMutations: createKeyedOperationQueue(),
|
||||
threadTitleTransport: {
|
||||
persistedContext: vi.fn().mockResolvedValue(null),
|
||||
generateTitle: vi.fn().mockResolvedValue(null),
|
||||
|
|
|
|||
Loading…
Reference in a new issue