From 6bdd9e5972a95289f7ae545e9fa12998d6d9190d Mon Sep 17 00:00:00 2001 From: murashit Date: Tue, 21 Jul 2026 20:47:56 +0900 Subject: [PATCH] refactor(archive): consolidate export ownership --- src/domain/markdown/frontmatter.ts | 7 ------ src/domain/threads/archive-markdown.ts | 23 ++++++++++--------- src/features/chat/host/contracts.ts | 3 +-- src/features/chat/host/view.obsidian.ts | 4 ++-- src/features/threads-view/session.ts | 3 +-- src/features/threads-view/view.obsidian.ts | 4 ++-- .../archive-export-destination.obsidian.ts | 7 ------ .../threads/workflows/archive-export.ts | 7 +++++- .../threads/workflows/thread-operations.ts | 3 +-- tests/domain/threads/archive-markdown.test.ts | 10 +++----- 10 files changed, 28 insertions(+), 43 deletions(-) delete mode 100644 src/domain/markdown/frontmatter.ts delete mode 100644 src/features/threads/obsidian/archive-export-destination.obsidian.ts diff --git a/src/domain/markdown/frontmatter.ts b/src/domain/markdown/frontmatter.ts deleted file mode 100644 index 03e8b194..00000000 --- a/src/domain/markdown/frontmatter.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function yamlFrontmatterString(value: string): string { - return JSON.stringify(value); -} - -export function yamlFrontmatterInlineList(values: readonly string[]): string { - return `[${values.map(yamlFrontmatterString).join(", ")}]`; -} diff --git a/src/domain/threads/archive-markdown.ts b/src/domain/threads/archive-markdown.ts index 841eea49..4dec4630 100644 --- a/src/domain/threads/archive-markdown.ts +++ b/src/domain/threads/archive-markdown.ts @@ -3,7 +3,6 @@ import { fromMarkdown } from "mdast-util-from-markdown"; import { toMarkdown } from "mdast-util-to-markdown"; import { visit } from "unist-util-visit"; -import { yamlFrontmatterInlineList, yamlFrontmatterString } from "../markdown/frontmatter"; import { parseFileHref } from "../vault/file-hrefs"; import { isFilesystemAbsolutePath, isVaultConfigPath, normalizeFilePath, vaultRelativePath } from "../vault/paths"; import type { Thread } from "./model"; @@ -16,9 +15,7 @@ interface MarkdownSourceReplacement { value: string; } -export interface ArchiveExportSettings { - archiveExportFolderTemplate: string; - archiveExportFilenameTemplate: string; +export interface ArchiveMarkdownOptions { archiveExportTags?: string; vaultPath?: string; vaultConfigDir?: string; @@ -28,13 +25,9 @@ export interface ArchiveThreadInput extends Thread { transcriptEntries: readonly ThreadTranscriptEntry[]; } -export function archivedThreadMarkdown( - thread: ArchiveThreadInput, - exportedAt = new Date(), - settings?: Partial, -): string { +export function archivedThreadMarkdown(thread: ArchiveThreadInput, exportedAt = new Date(), settings: ArchiveMarkdownOptions = {}): string { const title = threadArchiveTitle(thread); - const tags = normalizedArchiveTags(settings?.archiveExportTags ?? ""); + const tags = normalizedArchiveTags(settings.archiveExportTags ?? ""); const frontmatter = [ "---", `title: ${yamlFrontmatterString(title)}`, @@ -44,7 +37,7 @@ export function archivedThreadMarkdown( "---", ].join("\n"); const body = `${trimTrailingBlankLines([`# ${title}`, "", ...transcriptMarkdownLines(thread.transcriptEntries)]).join("\n")}\n`; - const normalizedBody = settings?.vaultPath ? normalizeExportedMarkdownLinks(body, settings.vaultPath, settings.vaultConfigDir) : body; + const normalizedBody = settings.vaultPath ? normalizeExportedMarkdownLinks(body, settings.vaultPath, settings.vaultConfigDir) : body; return `${frontmatter}\n\n${normalizedBody}`; } @@ -198,3 +191,11 @@ function trimTrailingBlankLines(lines: string[]): string[] { while (result[result.length - 1] === "") result.pop(); return result; } + +function yamlFrontmatterString(value: string): string { + return JSON.stringify(value); +} + +function yamlFrontmatterInlineList(values: readonly string[]): string { + return `[${values.map(yamlFrontmatterString).join(", ")}]`; +} diff --git a/src/features/chat/host/contracts.ts b/src/features/chat/host/contracts.ts index 550b967a..c3f91654 100644 --- a/src/features/chat/host/contracts.ts +++ b/src/features/chat/host/contracts.ts @@ -6,10 +6,9 @@ import type { ObservedResultListener } from "../../../app-server/query/observed- import type { ModelMetadata } from "../../../domain/catalog/metadata"; import type { SendShortcut } from "../../../domain/input/send-shortcut"; import type { SharedServerMetadata, SharedServerMetadataResource } from "../../../domain/server/metadata"; -import type { ArchiveExportSettings } from "../../../domain/threads/archive-markdown"; import type { KeyedOperationQueue } from "../../../shared/runtime/keyed-operation-queue"; import type { ThreadCatalogEventSink, ThreadCatalogPaginatedActiveReader } from "../../threads/catalog/thread-catalog"; -import type { ArchiveExportDestination } from "../../threads/workflows/archive-export"; +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"; diff --git a/src/features/chat/host/view.obsidian.ts b/src/features/chat/host/view.obsidian.ts index 14aec2c3..5fe39a86 100644 --- a/src/features/chat/host/view.obsidian.ts +++ b/src/features/chat/host/view.obsidian.ts @@ -1,7 +1,7 @@ import { Component, ItemView, type ViewStateResult, type WorkspaceLeaf } from "obsidian"; import { VIEW_TYPE_CODEX_PANEL } from "../../../constants"; -import { createObsidianArchiveExportDestination } from "../../threads/obsidian/archive-export-destination.obsidian"; +import { createObsidianVaultMarkdownDestination } from "../../../shared/obsidian/vault-write-destination.obsidian"; import { createLocalIdSource } from "../application/local-id-source"; import type { ChatPanelHandle, ChatPanelRuntimeSnapshot, ChatViewRuntimeOwner, CodexChatHost } from "./contracts"; import { ChatPanelSession } from "./session"; @@ -46,7 +46,7 @@ export class CodexChatView extends ItemView { registerPointerDown: (handler) => { owner.registerDomEvent(this.containerEl.doc, "pointerdown", handler); }, - archiveDestination: () => createObsidianArchiveExportDestination(this.app.vault), + archiveDestination: () => createObsidianVaultMarkdownDestination(this.app.vault), requestWorkspaceLayoutSave: () => { void this.app.workspace.requestSaveLayout(); }, diff --git a/src/features/threads-view/session.ts b/src/features/threads-view/session.ts index 405c3abf..56f6e397 100644 --- a/src/features/threads-view/session.ts +++ b/src/features/threads-view/session.ts @@ -2,14 +2,13 @@ import { Notice } from "obsidian"; import type { ObservedPaginatedResult } from "../../app-server/query/observed-result"; import { observedInitialError, observedInitialLoading } from "../../app-server/query/observed-result"; -import type { ArchiveExportSettings } from "../../domain/threads/archive-markdown"; 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 { OwnerLifetime } from "../../shared/runtime/owner-lifetime"; import type { ThreadCatalogEventSink, ThreadCatalogPaginatedActiveReader } from "../threads/catalog/thread-catalog"; -import type { ArchiveExportDestination } from "../threads/workflows/archive-export"; +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"; diff --git a/src/features/threads-view/view.obsidian.ts b/src/features/threads-view/view.obsidian.ts index bd2a2a0c..ca8fa2d2 100644 --- a/src/features/threads-view/view.obsidian.ts +++ b/src/features/threads-view/view.obsidian.ts @@ -1,7 +1,7 @@ import { Component, ItemView, type WorkspaceLeaf } from "obsidian"; import { VIEW_TYPE_CODEX_THREADS } from "../../constants"; -import { createObsidianArchiveExportDestination } from "../threads/obsidian/archive-export-destination.obsidian"; +import { createObsidianVaultMarkdownDestination } from "../../shared/obsidian/vault-write-destination.obsidian"; import { type ThreadsViewHost, ThreadsViewSession } from "./session"; export interface ThreadsRuntimeView { @@ -38,7 +38,7 @@ export class CodexThreadsView extends ItemView { registerPointerDown: (handler) => { owner.registerDomEvent(this.containerEl.doc, "pointerdown", handler); }, - archiveDestination: () => createObsidianArchiveExportDestination(this.app.vault), + archiveDestination: () => createObsidianVaultMarkdownDestination(this.app.vault), vaultConfigDir: () => this.app.vault.configDir, viewWindow: () => this.containerEl.doc.defaultView, }); diff --git a/src/features/threads/obsidian/archive-export-destination.obsidian.ts b/src/features/threads/obsidian/archive-export-destination.obsidian.ts deleted file mode 100644 index a229ae3a..00000000 --- a/src/features/threads/obsidian/archive-export-destination.obsidian.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Vault } from "obsidian"; -import { createObsidianVaultMarkdownDestination } from "../../../shared/obsidian/vault-write-destination.obsidian"; -import type { ArchiveExportDestination } from "../workflows/archive-export"; - -export function createObsidianArchiveExportDestination(vault: Vault): ArchiveExportDestination { - return createObsidianVaultMarkdownDestination(vault); -} diff --git a/src/features/threads/workflows/archive-export.ts b/src/features/threads/workflows/archive-export.ts index 3dc48be6..301fafe6 100644 --- a/src/features/threads/workflows/archive-export.ts +++ b/src/features/threads/workflows/archive-export.ts @@ -1,4 +1,4 @@ -import { type ArchiveExportSettings, type ArchiveThreadInput, archivedThreadMarkdown } from "../../../domain/threads/archive-markdown"; +import { type ArchiveMarkdownOptions, type ArchiveThreadInput, archivedThreadMarkdown } from "../../../domain/threads/archive-markdown"; import { shortThreadId } from "../../../domain/threads/id"; import { threadArchiveTitle } from "../../../domain/threads/title"; import { @@ -19,6 +19,11 @@ export interface ArchiveExportResult { path: string; } +export interface ArchiveExportSettings extends ArchiveMarkdownOptions { + archiveExportFolderTemplate: string; + archiveExportFilenameTemplate: string; +} + export type ArchiveExportDestination = VaultMarkdownDestination; interface TemplateContext { diff --git a/src/features/threads/workflows/thread-operations.ts b/src/features/threads/workflows/thread-operations.ts index 4f37b2a4..ef52f29b 100644 --- a/src/features/threads/workflows/thread-operations.ts +++ b/src/features/threads/workflows/thread-operations.ts @@ -1,8 +1,7 @@ -import type { ArchiveExportSettings } from "../../../domain/threads/archive-markdown"; import { normalizeExplicitThreadName, type Thread } from "../../../domain/threads/model"; import { threadDisplayTitle } from "../../../domain/threads/title"; import type { ThreadCatalogEventSink } from "../catalog/thread-catalog"; -import { type ArchiveExportDestination, exportArchivedThreadMarkdown } from "./archive-export"; +import { type ArchiveExportDestination, type ArchiveExportSettings, exportArchivedThreadMarkdown } from "./archive-export"; import type { ThreadOperationsTransport } from "./ports"; import type { ThreadNameMutationCoordinator } from "./thread-name-mutation-coordinator"; diff --git a/tests/domain/threads/archive-markdown.test.ts b/tests/domain/threads/archive-markdown.test.ts index a7490712..18f8bed9 100644 --- a/tests/domain/threads/archive-markdown.test.ts +++ b/tests/domain/threads/archive-markdown.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { type ArchiveExportSettings, archivedThreadMarkdown } from "../../../src/domain/threads/archive-markdown"; +import { type ArchiveMarkdownOptions, archivedThreadMarkdown } from "../../../src/domain/threads/archive-markdown"; import type { Thread } from "../../../src/domain/threads/model"; import type { ThreadTranscriptEntry } from "../../../src/domain/threads/transcript"; @@ -300,13 +300,9 @@ describe("thread archive export", () => { function exportedMarkdown( source: Thread & { transcriptEntries: ThreadTranscriptEntry[] }, now: Date, - settings: Partial = {}, + settings: ArchiveMarkdownOptions = {}, ): string { - return archivedThreadMarkdown(source, now, { - archiveExportFolderTemplate: "Exports", - archiveExportFilenameTemplate: "{{title}}", - ...settings, - }); + return archivedThreadMarkdown(source, now, settings); } function thread(