From 94737297b71bbd20480b7e4535a6a8f585a8fed3 Mon Sep 17 00:00:00 2001 From: Wenzheng Jiang Date: Sat, 9 May 2026 21:21:07 +0900 Subject: [PATCH] fix(miyo): strip vault folder-name prefix from indexed paths (#2390) Miyo returns indexed file paths prefixed with the owning folder name (the vault name), but getVaultRelativeMiyoPath was stripping the absolute filesystem path prefix instead. As a result, the "List Indexed Files" command rendered every entry as a broken Obsidian link (e.g. [[MyVault/notes/foo.md]] instead of [[notes/foo.md]]). Switch the strip logic to match what Miyo actually returns. Files from other vaults pass through unchanged so cross-vault search results stay intact. Always return a forward-slash-normalized path so callers see a consistent separator. Drop the now-unused getVaultBasePath helper. Co-authored-by: Claude Opus 4.7 (1M context) --- src/miyo/miyoUtils.test.ts | 43 +++++++++++++++++++++- src/miyo/miyoUtils.ts | 73 ++++++++++++++------------------------ 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/src/miyo/miyoUtils.test.ts b/src/miyo/miyoUtils.test.ts index a987a558..07e25f8f 100644 --- a/src/miyo/miyoUtils.test.ts +++ b/src/miyo/miyoUtils.test.ts @@ -2,7 +2,7 @@ jest.mock("@/plusUtils", () => ({ isSelfHostAccessValid: jest.fn(), })); -import { getMiyoFolderName } from "@/miyo/miyoUtils"; +import { getMiyoFolderName, getVaultRelativeMiyoPath } from "@/miyo/miyoUtils"; describe("getMiyoFolderName", () => { it("uses the vault folder name even when an adapter exposes an absolute path", () => { @@ -18,3 +18,44 @@ describe("getMiyoFolderName", () => { expect(folderName).toBe("graham-essays-main"); }); }); + +describe("getVaultRelativeMiyoPath", () => { + const buildApp = (vaultName: string) => + ({ + vault: { + getName: () => vaultName, + }, + }) as any; + + it("strips the current vault folder-name prefix", () => { + expect(getVaultRelativeMiyoPath(buildApp("MyVault"), "MyVault/notes/foo.md")).toBe( + "notes/foo.md" + ); + }); + + it("returns the normalized path unchanged when the prefix matches a different vault", () => { + expect(getVaultRelativeMiyoPath(buildApp("MyVault"), "OtherVault/notes/foo.md")).toBe( + "OtherVault/notes/foo.md" + ); + }); + + it("normalizes separators even when the prefix does not match", () => { + expect(getVaultRelativeMiyoPath(buildApp("MyVault"), "OtherVault\\notes\\foo.md")).toBe( + "OtherVault/notes/foo.md" + ); + }); + + it("only strips the leading prefix once", () => { + expect(getVaultRelativeMiyoPath(buildApp("Test"), "Test/Test/foo.md")).toBe("Test/foo.md"); + }); + + it("normalizes backslash separators before stripping", () => { + expect(getVaultRelativeMiyoPath(buildApp("MyVault"), "MyVault\\notes\\foo.md")).toBe( + "notes/foo.md" + ); + }); + + it("returns the normalized path when the vault folder name is empty", () => { + expect(getVaultRelativeMiyoPath(buildApp(""), "notes\\foo.md")).toBe("notes/foo.md"); + }); +}); diff --git a/src/miyo/miyoUtils.ts b/src/miyo/miyoUtils.ts index 72dd9408..4df672cc 100644 --- a/src/miyo/miyoUtils.ts +++ b/src/miyo/miyoUtils.ts @@ -2,6 +2,16 @@ import { isSelfHostAccessValid } from "@/plusUtils"; import { CopilotSettings } from "@/settings/model"; import { App, FileSystemAdapter, Platform } from "obsidian"; +/** + * Convert backslashes to forward slashes and trim trailing slashes. + * + * @param path - Filesystem path. + * @returns Normalized path. + */ +function normalizeFilesystemPath(path: string): string { + return path.replace(/\\/g, "/").replace(/\/+$/, ""); +} + /** * Return the user-configured Miyo server URL, or "" to fall back to local service discovery. * Uses `|| ""` to guard against undefined when loaded from older saved settings. @@ -67,57 +77,28 @@ export function getMiyoAbsolutePath(app: App, vaultRelativePath: string): string } /** - * Convert a Miyo file path back to a vault-relative path when it belongs to the current vault. + * Convert a Miyo file path to a vault-relative path when it belongs to the current vault. + * + * Miyo always returns paths prefixed with their owning folder name. When the + * prefix matches the current vault's folder name, strip it to get a + * vault-relative path. Files from other vaults pass through unchanged. The + * returned path is always normalized to forward slashes. * * @param app - Obsidian application instance. - * @param miyoPath - Path returned by Miyo. - * @returns Vault-relative path when the file is inside the current vault, otherwise the original path. + * @param miyoPath - Path returned by Miyo (e.g., "MyVault/notes/foo.md"). + * @returns Normalized vault-relative path when inside the current vault, otherwise the normalized original path. */ export function getVaultRelativeMiyoPath(app: App, miyoPath: string): string { - const vaultPath = getVaultBasePath(app); - if (!vaultPath) { - return miyoPath; - } - - const normalizedVaultPath = normalizeFilesystemPath(vaultPath); const normalizedMiyoPath = normalizeFilesystemPath(miyoPath); - const prefix = `${normalizedVaultPath}/`; - - if (normalizedMiyoPath.startsWith(prefix)) { - return normalizedMiyoPath.slice(prefix.length); + const folderName = getMiyoFolderName(app); + if (!folderName) { + return normalizedMiyoPath; } - return miyoPath; -} - -/** - * Resolve the base path for the current vault when available. - * - * @param app - Obsidian application instance. - * @returns Vault base path or undefined when unavailable. - */ -export function getVaultBasePath(app: App): string | undefined { - const adapter = app.vault.adapter; - if (adapter instanceof FileSystemAdapter) { - return adapter.getBasePath(); - } - - const adapterAny = adapter as unknown as { getBasePath?: () => string; basePath?: string }; - if (typeof adapterAny.getBasePath === "function") { - return adapterAny.getBasePath(); - } - if (typeof adapterAny.basePath === "string") { - return adapterAny.basePath; - } - return undefined; -} - -/** - * Normalize a filesystem path for prefix comparisons across platforms. - * - * @param path - Filesystem path. - * @returns Normalized path with forward slashes and no trailing slash. - */ -function normalizeFilesystemPath(path: string): string { - return path.replace(/\\/g, "/").replace(/\/+$/, ""); + const folderPrefix = `${folderName}/`; + if (normalizedMiyoPath.startsWith(folderPrefix)) { + return normalizedMiyoPath.slice(folderPrefix.length); + } + + return normalizedMiyoPath; }