diff --git a/README.md b/README.md index 072f0cff..24911a44 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ You can also open the plugin page directly: ` to send a message with recent turns from another Codex thread, or `/clip [message]` to save a readable Markdown clipping from a web page and send a wikilink to the saved note. diff --git a/src/domain/chat/input.ts b/src/domain/chat/input.ts index 82454682..f036922b 100644 --- a/src/domain/chat/input.ts +++ b/src/domain/chat/input.ts @@ -3,7 +3,7 @@ export interface RequestMention { path: string; } -export const ACTIVE_NOTE_MENTION_NAME = ""; +export const ACTIVE_FILE_MENTION_NAME = ""; export interface RequestAdditionalContext { key: string; diff --git a/src/features/chat/application/composer/suggestions.ts b/src/features/chat/application/composer/suggestions.ts index 65f28851..8b8f4e1b 100644 --- a/src/features/chat/application/composer/suggestions.ts +++ b/src/features/chat/application/composer/suggestions.ts @@ -130,9 +130,9 @@ function activeContextReferenceSuggestions( const query = rawQuery.toLowerCase(); const start = beforeCursor.length - rawQuery.length - 1; const suggestions: ComposerSuggestion[] = []; - if (references?.activeNote && "active-note".startsWith(query) && query !== "active-note") { + if (references?.activeNote && "active".startsWith(query)) { suggestions.push({ - display: "Active note", + display: "Active file", detail: references.activeNote.path, replacement: activeNoteContextReferenceMarker(references.activeNote), start, diff --git a/src/features/chat/application/composer/wikilink-context.ts b/src/features/chat/application/composer/wikilink-context.ts index 8582c516..866a698c 100644 --- a/src/features/chat/application/composer/wikilink-context.ts +++ b/src/features/chat/application/composer/wikilink-context.ts @@ -1,7 +1,7 @@ import { parseLinktext } from "obsidian"; import type { SkillMetadata } from "../../../../domain/catalog/metadata"; import { - ACTIVE_NOTE_MENTION_NAME, + ACTIVE_FILE_MENTION_NAME, type CodexInput, codexTextInputWithMentions, type RequestAdditionalContext, @@ -83,7 +83,7 @@ export function preparedUserInputWithWikiLinkMentionsSkillsAndContext( } const attachedActiveNote = options.referenceActiveNoteOnSend ? contextReferences.activeNote : null; - if (attachedActiveNote) mentions.push({ name: ACTIVE_NOTE_MENTION_NAME, path: attachedActiveNote.path }); + if (attachedActiveNote) mentions.push({ name: ACTIVE_FILE_MENTION_NAME, path: attachedActiveNote.path }); const skillByName = firstEnabledSkillByName(skills); const resolvedSkills: RequestMention[] = []; @@ -188,7 +188,7 @@ function selectionBodyLines(selections: readonly SelectionContextReference[]): s } function activeNoteContextLines(activeNote: ActiveNoteContextReference): string[] { - return ["Attached active note:", `- ${ACTIVE_NOTE_MENTION_NAME} -> ${activeNote.path}`]; + return ["Referenced active file:", `- ${ACTIVE_FILE_MENTION_NAME} -> ${activeNote.path}`]; } function parsedSkillReferences(text: string): string[] { diff --git a/src/features/chat/domain/message-stream/format/file-mentions.ts b/src/features/chat/domain/message-stream/format/file-mentions.ts index 4bbb4c90..eeb77e8c 100644 --- a/src/features/chat/domain/message-stream/format/file-mentions.ts +++ b/src/features/chat/domain/message-stream/format/file-mentions.ts @@ -1,7 +1,7 @@ -import { ACTIVE_NOTE_MENTION_NAME, type CodexInputItem } from "../../../../../domain/chat/input"; +import { ACTIVE_FILE_MENTION_NAME, type CodexInputItem } from "../../../../../domain/chat/input"; import type { MessageStreamFileMention } from "../items"; -const ACTIVE_NOTE_DISPLAY_NAME = "Active note"; +const ACTIVE_FILE_DISPLAY_NAME = "Active file"; export function fileMentionsFromInput(input: readonly CodexInputItem[]): MessageStreamFileMention[] { const seenFilePaths = new Set(); @@ -9,11 +9,11 @@ export function fileMentionsFromInput(input: readonly CodexInputItem[]): Message const mentions: MessageStreamFileMention[] = []; for (const item of input) { if (item.type !== "mention") continue; - const activeNoteMention = item.name === ACTIVE_NOTE_MENTION_NAME; + const activeNoteMention = item.name === ACTIVE_FILE_MENTION_NAME; const seen = activeNoteMention ? seenActiveNotePaths : seenFilePaths; if (seen.has(item.path)) continue; seen.add(item.path); - mentions.push({ name: activeNoteMention ? ACTIVE_NOTE_DISPLAY_NAME : item.name, path: item.path }); + mentions.push({ name: activeNoteMention ? ACTIVE_FILE_DISPLAY_NAME : item.name, path: item.path }); } return mentions; } diff --git a/src/settings/tab-shell.tsx b/src/settings/tab-shell.tsx index c012c9d7..f238499d 100644 --- a/src/settings/tab-shell.tsx +++ b/src/settings/tab-shell.tsx @@ -121,8 +121,8 @@ function ComposerSettingsSection({ panel, actions }: { panel: SettingsTabPanelSt diff --git a/tests/features/chat/app-server/mappers/message-stream.test.ts b/tests/features/chat/app-server/mappers/message-stream.test.ts index 52709a18..7d913edc 100644 --- a/tests/features/chat/app-server/mappers/message-stream.test.ts +++ b/tests/features/chat/app-server/mappers/message-stream.test.ts @@ -121,7 +121,7 @@ describe("turn item conversion preserves app-server semantics", () => { }); }); - it("keeps active note mentions visible even when a wikilink resolves to the same path", () => { + it("keeps active file mentions visible even when a wikilink resolves to the same path", () => { const userMessage: TurnItem = { type: "userMessage", id: "u1", @@ -130,14 +130,14 @@ describe("turn item conversion preserves app-server semantics", () => { { type: "text", text: "Read [[Alpha]].", text_elements: [] }, { type: "mention", name: "Alpha", path: "thoughts/Alpha.md" }, { type: "mention", name: "Alpha duplicate", path: "thoughts/Alpha.md" }, - { type: "mention", name: "", path: "thoughts/Alpha.md" }, + { type: "mention", name: "", path: "thoughts/Alpha.md" }, ], }; expect(messageStreamItemFromTurnItem(userMessage)).toMatchObject({ mentionedFiles: [ { name: "Alpha", path: "thoughts/Alpha.md" }, - { name: "Active note", path: "thoughts/Alpha.md" }, + { name: "Active file", path: "thoughts/Alpha.md" }, ], }); }); diff --git a/tests/features/chat/app-server/transports.test.ts b/tests/features/chat/app-server/transports.test.ts index 0072f1be..a3c8eb23 100644 --- a/tests/features/chat/app-server/transports.test.ts +++ b/tests/features/chat/app-server/transports.test.ts @@ -36,14 +36,14 @@ describe("chat app-server transports", () => { }); }); - it("starts turns with wikilink and active note context in one app-server payload", async () => { + it("starts turns with wikilink and active file context in one app-server payload", async () => { const request = vi.fn().mockResolvedValue({ turn: { id: "turn-1" } }); const client = { request } as unknown as AppServerClient; const transport = createTestGateway({ currentClient: () => client, connectedClient: vi.fn().mockResolvedValue(client), }).turn; - const text = "Compare [[Alpha]] with the active note."; + const text = "Compare [[Alpha]] with the active file."; const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( text, (target) => (target === "Alpha" ? { name: "Alpha", path: "notes/Alpha.md" } : null), @@ -67,13 +67,13 @@ describe("chat app-server transports", () => { input: [ { type: "text", text, text_elements: [] }, { type: "mention", name: "Alpha", path: "notes/Alpha.md" }, - { type: "mention", name: "", path: "notes/Alpha.md" }, + { type: "mention", name: "", path: "notes/Alpha.md" }, ], additionalContext: { codex_panel_obsidian_context: { kind: "untrusted", value: - "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Alpha]] -> notes/Alpha.md\n\nAttached active note:\n- -> notes/Alpha.md", + "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Alpha]] -> notes/Alpha.md\n\nReferenced active file:\n- -> notes/Alpha.md", }, }, clientUserMessageId: "local-user", diff --git a/tests/features/chat/application/composer/suggestions.test.ts b/tests/features/chat/application/composer/suggestions.test.ts index 94ce557d..f617d32b 100644 --- a/tests/features/chat/application/composer/suggestions.test.ts +++ b/tests/features/chat/application/composer/suggestions.test.ts @@ -229,13 +229,25 @@ describe("composer suggestions", () => { it("uses one active suggestion family at a time", () => { expect(activeComposerSuggestions("[[bet", notes, [])[0]?.replacement).toBe("[[Beta Note]]"); expect( - activeComposerSuggestions("@act", notes, [], [], [], null, { + activeComposerSuggestions("@active", notes, [], [], [], null, { contextReferences: { activeNote: { name: "Beta Note", path: "topics/Beta Note.md", linktext: "Beta Note" }, selection: null, }, - })[0]?.replacement, - ).toBe("[[Beta Note]]"); + })[0], + ).toMatchObject({ + display: "Active file", + detail: "topics/Beta Note.md", + replacement: "[[Beta Note]]", + }); + expect( + activeComposerSuggestions("@active-note", notes, [], [], [], null, { + contextReferences: { + activeNote: { name: "Beta Note", path: "topics/Beta Note.md", linktext: "Beta Note" }, + selection: null, + }, + }), + ).toEqual([]); expect( activeComposerSuggestions("@sel", notes, [], [], [], null, { contextReferences: { diff --git a/tests/features/chat/application/composer/wikilink-context.test.ts b/tests/features/chat/application/composer/wikilink-context.test.ts index 3b22b367..82bd5291 100644 --- a/tests/features/chat/application/composer/wikilink-context.test.ts +++ b/tests/features/chat/application/composer/wikilink-context.test.ts @@ -167,7 +167,7 @@ describe("wikilink context", () => { }); it("leaves bare context references as raw text", () => { - const text = "整理して @active-note and @selection"; + const text = "整理して @active and @selection"; const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( text, (target) => (target === "notes/Alpha" ? { name: "Alpha", path: "notes/Alpha.md" } : null), @@ -189,7 +189,7 @@ describe("wikilink context", () => { expect(prepared.input).toEqual([{ type: "text", text }]); }); - it("resolves completed active-note snapshots without depending on the current link context", () => { + it("resolves completed active snapshots without depending on the current link context", () => { const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( "整理して [[Alpha]]", () => null, @@ -205,7 +205,7 @@ describe("wikilink context", () => { expect(prepared.input).toContainEqual({ type: "mention", name: "Alpha", path: "notes/Alpha.md" }); }); - it("references the active note on send when enabled without changing visible text", () => { + it("references the active file on send when enabled without changing visible text", () => { const text = "Rewrite the introduction."; const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( text, @@ -221,18 +221,18 @@ describe("wikilink context", () => { expect(prepared.text).toBe(text); expect(prepared.input).toEqual([ { type: "text", text }, - { type: "mention", name: "", path: "notes/Alpha.md" }, + { type: "mention", name: "", path: "notes/Alpha.md" }, { type: "additionalContext", key: "codex_panel_obsidian_context", kind: "untrusted", - value: "Obsidian context for the current user input:\nAttached active note:\n- -> notes/Alpha.md", + value: "Obsidian context for the current user input:\nReferenced active file:\n- -> notes/Alpha.md", }, ]); }); - it("keeps wikilinks and active note in one Obsidian context when both are present", () => { - const text = "Compare [[Beta]] with the current note."; + it("keeps wikilinks and active file in one Obsidian context when both are present", () => { + const text = "Compare [[Beta]] with the active file."; const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( text, (target) => (target === "Beta" ? { name: "Beta", path: "notes/Beta.md" } : null), @@ -248,18 +248,18 @@ describe("wikilink context", () => { expect(prepared.input).toEqual([ { type: "text", text }, { type: "mention", name: "Beta", path: "notes/Beta.md" }, - { type: "mention", name: "", path: "notes/Alpha.md" }, + { type: "mention", name: "", path: "notes/Alpha.md" }, { type: "additionalContext", key: "codex_panel_obsidian_context", kind: "untrusted", value: - "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n\nAttached active note:\n- -> notes/Alpha.md", + "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n\nReferenced active file:\n- -> notes/Alpha.md", }, ]); }); - it("keeps wikilinks, selections, and active note in one Obsidian context", () => { + it("keeps wikilinks, selections, and active file in one Obsidian context", () => { const text = "Compare [[Beta]] with [[Gamma]] (L2:C1-L2:C6)."; const prepared = preparedUserInputWithWikiLinkMentionsSkillsAndContext( text, @@ -289,13 +289,13 @@ describe("wikilink context", () => { { type: "text", text }, { type: "mention", name: "Beta", path: "notes/Beta.md" }, { type: "mention", name: "Gamma", path: "notes/Gamma.md" }, - { type: "mention", name: "", path: "notes/Alpha.md" }, + { type: "mention", name: "", path: "notes/Alpha.md" }, { type: "additionalContext", key: "codex_panel_obsidian_context", kind: "untrusted", value: - "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n- [[Gamma]] -> notes/Gamma.md\n\nReferenced selections:\n- [[Gamma]] (L2:C1-L2:C6) -> notes/Gamma.md L2:C1-L2:C6\n\n[[Gamma]] (L2:C1-L2:C6):\nselected gamma\n\nAttached active note:\n- -> notes/Alpha.md", + "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n- [[Gamma]] -> notes/Gamma.md\n\nReferenced selections:\n- [[Gamma]] (L2:C1-L2:C6) -> notes/Gamma.md L2:C1-L2:C6\n\n[[Gamma]] (L2:C1-L2:C6):\nselected gamma\n\nReferenced active file:\n- -> notes/Alpha.md", }, ]); }); diff --git a/tests/features/chat/application/conversation/composition.test.ts b/tests/features/chat/application/conversation/composition.test.ts index 8620ab0e..393ff683 100644 --- a/tests/features/chat/application/conversation/composition.test.ts +++ b/tests/features/chat/application/conversation/composition.test.ts @@ -52,7 +52,7 @@ function resumeThread(stateStore: ReturnType, items } describe("createConversationTurnActions", () => { - it("does not attach composer-only active note context when implementing a plan", async () => { + it("does not attach composer-only active file context when implementing a plan", async () => { const stateStore = createChatStateStore(createChatState()); const plan = planItem("plan"); resumeThread(stateStore, [plan]); diff --git a/tests/features/chat/application/conversation/optimistic-turn-start.test.ts b/tests/features/chat/application/conversation/optimistic-turn-start.test.ts index 0f5cdc78..fff65d91 100644 --- a/tests/features/chat/application/conversation/optimistic-turn-start.test.ts +++ b/tests/features/chat/application/conversation/optimistic-turn-start.test.ts @@ -55,19 +55,19 @@ describe("optimistic turn start helpers", () => { }); }); - it("keeps active note mentions visible even when the same file is mentioned explicitly", () => { + it("keeps active file mentions visible even when the same file is mentioned explicitly", () => { const text = "Read [[Note]]."; const input = [ { type: "text" as const, text }, { type: "mention" as const, name: "Note", path: "Note.md" }, { type: "mention" as const, name: "Note duplicate", path: "Note.md" }, - { type: "mention" as const, name: "", path: "Note.md" }, + { type: "mention" as const, name: "", path: "Note.md" }, ]; expect(localUserMessageItemFromInput({ id: "local-user", text, codexInput: input })).toMatchObject({ mentionedFiles: [ { name: "Note", path: "Note.md" }, - { name: "Active note", path: "Note.md" }, + { name: "Active file", path: "Note.md" }, ], }); }); diff --git a/tests/features/chat/host/vault-note-candidate-provider.test.ts b/tests/features/chat/host/vault-note-candidate-provider.test.ts index c2fa33fa..ddd76e53 100644 --- a/tests/features/chat/host/vault-note-candidate-provider.test.ts +++ b/tests/features/chat/host/vault-note-candidate-provider.test.ts @@ -147,7 +147,7 @@ describe("VaultNoteCandidateProvider", () => { expect(provider.resolveMention("Missing", "")).toBeNull(); }); - it("uses the active note only as Obsidian link-resolution context", () => { + it("uses the active file only as Obsidian link-resolution context", () => { const linked = tFile("notes/Project.md", "Project"); const getFirstLinkpathDest = vi.fn(() => linked); const app = appFixture({ @@ -171,7 +171,7 @@ describe("VaultNoteCandidateProvider", () => { expect(getFirstLinkpathDest).toHaveBeenCalledWith("Bases/Projects.base", "Daily/Today.md"); }); - it("returns active note and selection references from the last markdown view", () => { + it("returns active file and selection references from the last markdown view", () => { const file = tFile("notes/Alpha.md", "Alpha"); const app = appFixture({ activeView: markdownView(file, { diff --git a/tests/features/chat/panel/composer-controller.test.ts b/tests/features/chat/panel/composer-controller.test.ts index c6465e25..0cdfffb4 100644 --- a/tests/features/chat/panel/composer-controller.test.ts +++ b/tests/features/chat/panel/composer-controller.test.ts @@ -431,7 +431,7 @@ describe("ChatComposerController", () => { ]); }); - it("freezes active note context when inserting the active-note suggestion", async () => { + it("freezes active file context when inserting the active suggestion", async () => { const stateStore = createChatStateStore(); const parent = document.createElement("div"); let references: ComposerContextReferences = { @@ -488,7 +488,7 @@ describe("ChatComposerController", () => { }); }); - it("uses the captured active note when slash commands prepare input asynchronously", () => { + it("uses the captured active file when slash commands prepare input asynchronously", () => { const stateStore = createChatStateStore(); let references: ComposerContextReferences = { activeNote: { name: "Alpha", path: "notes/Alpha.md", linktext: "Alpha" }, @@ -522,12 +522,12 @@ describe("ChatComposerController", () => { expect(controller.preparedInput("Rewrite intro", snapshot).input).toEqual([ { type: "text", text: "Rewrite intro" }, - { type: "mention", name: "", path: "notes/Alpha.md" }, + { type: "mention", name: "", path: "notes/Alpha.md" }, { type: "additionalContext", key: "codex_panel_obsidian_context", kind: "untrusted", - value: "Obsidian context for the current user input:\nAttached active note:\n- -> notes/Alpha.md", + value: "Obsidian context for the current user input:\nReferenced active file:\n- -> notes/Alpha.md", }, ]); }); diff --git a/tests/settings/settings-tab.test.ts b/tests/settings/settings-tab.test.ts index ab7da143..6c789f14 100644 --- a/tests/settings/settings-tab.test.ts +++ b/tests/settings/settings-tab.test.ts @@ -69,7 +69,7 @@ describe("settings tab", () => { "Composer", "Send shortcut", "Scroll thread from composer line edges", - "Reference active note on send", + "Reference active file on send", "Attachment folder", "Web clipping", "Clipped note folder", @@ -142,13 +142,13 @@ describe("settings tab", () => { expect(settingDesc(tab, "Scroll thread from composer line edges")).toContain("Up/Ctrl+P"); }); - it("saves the active note reference setting", async () => { + it("saves the active file reference setting", async () => { const saveSettings = vi.fn().mockResolvedValue(undefined); const tab = newSettingsTab({ saveSettings }); tab.display(); - const toggle = inputForSetting(tab, "Reference active note on send"); - if (!toggle) throw new Error("Missing active note reference toggle"); + const toggle = inputForSetting(tab, "Reference active file on send"); + if (!toggle) throw new Error("Missing active file reference toggle"); expect(toggle.parentElement?.classList.contains("checkbox-container")).toBe(true); toggle.checked = true; @@ -156,7 +156,7 @@ describe("settings tab", () => { await flushPromises(); expect(saveSettings).toHaveBeenCalledOnce(); - expect(settingDesc(tab, "Reference active note on send")).toContain("current active note"); + expect(settingDesc(tab, "Reference active file on send")).toContain("current active file"); }); it("saves the attachment folder setting", async () => { diff --git a/tests/settings/settings.test.ts b/tests/settings/settings.test.ts index 65957439..b47e6f0d 100644 --- a/tests/settings/settings.test.ts +++ b/tests/settings/settings.test.ts @@ -132,7 +132,7 @@ describe("settings", () => { ); }); - it("normalizes active note reference on send", () => { + it("normalizes active file reference on send", () => { expect(normalizeSettings({}).referenceActiveNoteOnSend).toBe(false); expect(normalizeSettings({ referenceActiveNoteOnSend: true }).referenceActiveNoteOnSend).toBe(true); expect(normalizeSettings({ referenceActiveNoteOnSend: "yes" }).referenceActiveNoteOnSend).toBe(