From 7db3c2f00ac4ede2411f054d1ea14bd7bdc3ec53 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Wed, 18 Mar 2026 23:29:39 +0100 Subject: [PATCH] fix: show all relationships for topics in note list Topics used a separate 'topic' selection kind that only showed reverse relatedTo matches. Now topics use 'entity' kind like all other types, going through buildRelationshipGroups to show all frontmatter relationships, children, events, referenced-by, and backlinks. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/App.tsx | 1 - src/components/NoteList.test.tsx | 23 ++++++++--------------- src/components/Sidebar.test.tsx | 4 ++-- src/components/SidebarParts.tsx | 15 +++++++-------- src/types.ts | 1 - src/utils/noteListHelpers.ts | 3 --- 6 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b4c4a7d4..a855a581 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -505,7 +505,6 @@ function App() { const aiNoteListFilter = useMemo(() => { if (selection.kind === 'sectionGroup') return { type: selection.type, query: '' } - if (selection.kind === 'topic') return { type: null, query: selection.entry.title } if (selection.kind === 'entity') return { type: null, query: selection.entry.title } return { type: null, query: '' } }, [selection]) diff --git a/src/components/NoteList.test.tsx b/src/components/NoteList.test.tsx index 98631dd6..47fcfa04 100644 --- a/src/components/NoteList.test.tsx +++ b/src/components/NoteList.test.tsx @@ -200,13 +200,14 @@ describe('NoteList', () => { expect(screen.getByText('Related to')).toBeInTheDocument() }) - it('filters by topic (relatedTo references)', () => { + it('shows entity view with relationship groups for topics', () => { render( - + ) - // Build Laputa App has relatedTo: [[topic/software-development]] + // Build Laputa App references this topic via relatedTo — should appear in Referenced By expect(screen.getByText('Build Laputa App')).toBeInTheDocument() - expect(screen.queryByText('Facebook Ads Strategy')).not.toBeInTheDocument() + // Entity view shows group headers + expect(screen.getByText('Referenced By')).toBeInTheDocument() }) it('shows search input when search icon is clicked', () => { @@ -806,17 +807,9 @@ describe('filterEntries — trash', () => { expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined() }) - it('topic filter excludes trashed entries', () => { - const topicEntry: VaultEntry = { ...mockEntries[4] } // Software Development topic - const trashedWithTopic: VaultEntry = { - ...trashedEntry, - relatedTo: ['[[topic/software-development]]'], - } - const all = [...mockEntries, trashedWithTopic] - const result = filterEntries(all, { kind: 'topic', entry: topicEntry }) - expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined() - // Normal entry with that topic should still appear - expect(result.find((e) => e.title === 'Build Laputa App')).toBeDefined() + it('entity filter returns empty (entity view uses relationship groups instead)', () => { + const result = filterEntries(mockEntries, { kind: 'entity', entry: mockEntries[4] }) + expect(result).toHaveLength(0) }) }) diff --git a/src/components/Sidebar.test.tsx b/src/components/Sidebar.test.tsx index d498cf92..6e9a733b 100644 --- a/src/components/Sidebar.test.tsx +++ b/src/components/Sidebar.test.tsx @@ -359,13 +359,13 @@ describe('Sidebar', () => { expect(screen.getByText('Trading')).toBeInTheDocument() }) - it('calls onSelect with topic kind when clicking a topic', () => { + it('calls onSelect with entity kind when clicking a topic', () => { const onSelect = vi.fn() render() fireEvent.click(screen.getByLabelText('Expand Topics')) fireEvent.click(screen.getByText('Software Development')) expect(onSelect).toHaveBeenCalledWith({ - kind: 'topic', + kind: 'entity', entry: mockEntries[4], }) }) diff --git a/src/components/SidebarParts.tsx b/src/components/SidebarParts.tsx index 0a0ca484..064b2800 100644 --- a/src/components/SidebarParts.tsx +++ b/src/components/SidebarParts.tsx @@ -18,8 +18,7 @@ export function isSelectionActive(current: SidebarSelection, check: SidebarSelec switch (check.kind) { case 'filter': return (current as typeof check).filter === check.filter case 'sectionGroup': return (current as typeof check).type === check.type - case 'entity': - case 'topic': return (current as typeof check).entry.path === check.entry.path + case 'entity': return (current as typeof check).entry.path === check.entry.path default: return false } } @@ -83,8 +82,8 @@ export interface SectionContentProps { onRenameCancel?: () => void } -function childSelection(type: string, entry: VaultEntry): SidebarSelection { - return type === 'Topic' ? { kind: 'topic', entry } : { kind: 'entity', entry } +function childSelection(entry: VaultEntry): SidebarSelection { + return { kind: 'entity', entry } } function resolveCreateHandler(type: string, onCreateType?: (type: string) => void, onCreateNewType?: () => void): (() => void) | undefined { @@ -123,7 +122,7 @@ export function SectionContent({ /> {!isCollapsed && items.length > 0 && ( @@ -132,15 +131,15 @@ export function SectionContent({ ) } -function SectionChildList({ items, type, selection, sectionColor, sectionLightColor, onSelect, onSelectNote }: { - items: VaultEntry[]; type: string; selection: SidebarSelection +function SectionChildList({ items, selection, sectionColor, sectionLightColor, onSelect, onSelectNote }: { + items: VaultEntry[]; selection: SidebarSelection sectionColor: string; sectionLightColor: string onSelect: (sel: SidebarSelection) => void; onSelectNote?: (entry: VaultEntry) => void }) { return (
{items.map((entry) => { - const sel = childSelection(type, entry) + const sel = childSelection(entry) const active = isSelectionActive(selection, sel) return ( e.isA === selection.type) return subFilter ? applySubFilter(typeEntries, subFilter) : typeEntries.filter(isActive) } - if (selection.kind === 'topic') { - return entries.filter((e) => refsMatch(e.relatedTo, selection.entry) && isActive(e)) - } return filterByFilterType(entries, selection.filter) }