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) <noreply@anthropic.com>
This commit is contained in:
@@ -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])
|
||||
|
||||
@@ -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(
|
||||
<NoteList {...defaultFilterProps} entries={mockEntries} selection={{ kind: 'topic', entry: mockEntries[4] }} selectedNote={null} onSelectNote={noopSelect} onReplaceActiveTab={noopReplace} onCreateNote={vi.fn()} />
|
||||
<NoteList {...defaultFilterProps} entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[4] }} selectedNote={null} onSelectNote={noopSelect} onReplaceActiveTab={noopReplace} onCreateNote={vi.fn()} />
|
||||
)
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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(<Sidebar entries={mockEntries} selection={defaultSelection} onSelect={onSelect} />)
|
||||
fireEvent.click(screen.getByLabelText('Expand Topics'))
|
||||
fireEvent.click(screen.getByText('Software Development'))
|
||||
expect(onSelect).toHaveBeenCalledWith({
|
||||
kind: 'topic',
|
||||
kind: 'entity',
|
||||
entry: mockEntries[4],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 && (
|
||||
<SectionChildList
|
||||
items={items} type={type} selection={selection}
|
||||
items={items} selection={selection}
|
||||
sectionColor={sectionColor} sectionLightColor={sectionLightColor}
|
||||
onSelect={onSelect} onSelectNote={onSelectNote}
|
||||
/>
|
||||
@@ -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 (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{items.map((entry) => {
|
||||
const sel = childSelection(type, entry)
|
||||
const sel = childSelection(entry)
|
||||
const active = isSelectionActive(selection, sel)
|
||||
return (
|
||||
<SectionChildItem
|
||||
|
||||
@@ -182,4 +182,3 @@ export type SidebarSelection =
|
||||
| { kind: 'filter'; filter: SidebarFilter }
|
||||
| { kind: 'sectionGroup'; type: string }
|
||||
| { kind: 'entity'; entry: VaultEntry }
|
||||
| { kind: 'topic'; entry: VaultEntry }
|
||||
|
||||
@@ -327,9 +327,6 @@ function filterByKind(entries: VaultEntry[], selection: SidebarSelection, subFil
|
||||
const typeEntries = entries.filter((e) => 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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user