From ccfa1480141030d712b8487aebb480b32fb77a9d Mon Sep 17 00:00:00 2001 From: lucaronin Date: Tue, 5 May 2026 03:19:51 +0200 Subject: [PATCH] fix(nav): guard stale entity backlink metadata --- src/utils/noteListHelpers.test.ts | 21 +++++++++++++++++++++ src/utils/noteListHelpers.ts | 27 ++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/utils/noteListHelpers.test.ts b/src/utils/noteListHelpers.test.ts index 4118bd48..a665776b 100644 --- a/src/utils/noteListHelpers.test.ts +++ b/src/utils/noteListHelpers.test.ts @@ -250,6 +250,27 @@ describe('buildRelationshipGroups', () => { expect(groups).toEqual([]) }) + it('keeps stale entity views usable when a rapidly switched entry lacks filename metadata', () => { + const staleEntity = { + ...makeEntry({ + path: 'C:\\Users\\luca\\Laputa\\project\\alpha.md', + title: 'Alpha project', + isA: 'Project', + }), + filename: undefined, + } as unknown as ReturnType + const backlink = makeEntry({ + path: '/Users/luca/Laputa/note/backlink.md', + filename: 'backlink.md', + title: 'Backlink', + outgoingLinks: ['project/alpha'], + }) + + const groups = buildRelationshipGroups(staleEntity, [staleEntity, backlink]) + + expect(groups.find((group) => group.label === 'Backlinks')?.entries).toEqual([backlink]) + }) + it('allows the same note to appear in multiple relationship groups', () => { const parent = makeEntry({ path: '/vault/parent.md', diff --git a/src/utils/noteListHelpers.ts b/src/utils/noteListHelpers.ts index 7e0d18a4..edfaefc6 100644 --- a/src/utils/noteListHelpers.ts +++ b/src/utils/noteListHelpers.ts @@ -266,10 +266,31 @@ export function clearListSortFromLocalStorage(): void { } catch { /* ignore */ } } +function stringField(value: unknown): string { + return typeof value === 'string' ? value : '' +} + +function filenameStemFromEntry(entry: VaultEntry): string { + const filename = stringField(entry.filename) + if (filename) return filename.replace(/\.md$/, '') + + const pathLeaf = stringField(entry.path).split(/[\\/]/u).pop() ?? '' + return pathLeaf.replace(/\.md$/, '') +} + +function relativePathStemFromEntry(entry: VaultEntry): string { + const normalizedPath = stringField(entry.path).replaceAll('\\', '/') + return normalizedPath.replace(/^.*\/Laputa\//, '').replace(/\.md$/, '') +} + +function linkTargetsForEntry(entry: VaultEntry): Set { + const title = stringField(entry.title) + const aliases = Array.isArray(entry.aliases) ? entry.aliases : [] + return new Set([title, ...aliases, filenameStemFromEntry(entry), relativePathStemFromEntry(entry)].filter(Boolean)) +} + function findBacklinks(entity: VaultEntry, allEntries: VaultEntry[]): VaultEntry[] { - const stem = entity.filename.replace(/\.md$/, '') - const pathStem = entity.path.replace(/^.*\/Laputa\//, '').replace(/\.md$/, '') - const targets = new Set([entity.title, ...entity.aliases, stem, pathStem]) + const targets = linkTargetsForEntry(entity) return allEntries.filter((e) => { if (e.path === entity.path) return false