diff --git a/design/hide-backlinks-empty.pen b/design/hide-backlinks-empty.pen new file mode 100644 index 00000000..ae27a8df --- /dev/null +++ b/design/hide-backlinks-empty.pen @@ -0,0 +1,65 @@ +{ + "children": [ + { + "type": "frame", + "id": "hideBacklinksEmpty_before", + "x": 0, + "y": 0, + "name": "Hide Empty Backlinks Label — Before (shows No backlinks label)", + "width": 280, + "height": 80, + "fill": "#ffffff", + "gap": 4, + "padding": 12, + "alignItems": "flex-start", + "children": [ + { + "type": "text", + "id": "bl_header_before", + "name": "section-header", + "fill": "#6b7280", + "content": "Backlinks", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "600", + "letterSpacing": 1.2 + }, + { + "type": "text", + "id": "bl_empty_before", + "name": "empty-label", + "fill": "#9ca3af", + "content": "No backlinks", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hideBacklinksEmpty_after", + "x": 320, + "y": 0, + "name": "Hide Empty Backlinks Label — After (section hidden when empty)", + "width": 280, + "height": 80, + "fill": "#ffffff", + "gap": 4, + "padding": 12, + "alignItems": "flex-start", + "children": [ + { + "type": "text", + "id": "bl_note_after", + "name": "note", + "fill": "#9ca3af", + "content": "(Backlinks, Referenced By, and Relations sections are hidden when empty — no placeholder label shown)", + "fontFamily": "Inter", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/components/Inspector.test.tsx b/src/components/Inspector.test.tsx index b95a18c7..edf7e1da 100644 --- a/src/components/Inspector.test.tsx +++ b/src/components/Inspector.test.tsx @@ -159,7 +159,7 @@ describe('Inspector', () => { expect(onNavigate).toHaveBeenCalledWith('responsibility/grow-newsletter') }) - it('shows "No relationships" when entry has no belongsTo/relatedTo', () => { + it('hides relationships label when entry has no belongsTo/relatedTo', () => { const noRels = { ...mockEntry, belongsTo: [], relatedTo: [] } const contentNoRels = `--- title: Test Project @@ -172,7 +172,7 @@ Status: Active This is a test note with some words to count. ` render() - expect(screen.getByText('No relationships')).toBeInTheDocument() + expect(screen.queryByText('No relationships')).not.toBeInTheDocument() }) it('shows backlinks from notes that reference the current note via outgoingLinks', () => { @@ -197,8 +197,8 @@ This is a test note with some words to count. entries={[mockEntry, { ...referrerEntry, outgoingLinks: [] }]} /> ) - // Initially no backlinks because referrer has empty outgoingLinks - expect(screen.getByText('No backlinks')).toBeInTheDocument() + // Initially no backlinks — section is hidden entirely + expect(screen.queryByText('Backlinks')).not.toBeInTheDocument() // Rerender with updated outgoingLinks (simulates adding [[Test Project]] to referrer) rerender( @@ -212,7 +212,7 @@ This is a test note with some words to count. expect(screen.getByText('Referrer Note')).toBeInTheDocument() }) - it('shows "No backlinks" when no notes reference the current note', () => { + it('hides backlinks section when no notes reference the current note', () => { render( ) - expect(screen.getByText('No backlinks')).toBeInTheDocument() + expect(screen.queryByText('No backlinks')).not.toBeInTheDocument() + expect(screen.queryByText('Backlinks')).not.toBeInTheDocument() }) it('navigates when a backlink is clicked', () => { @@ -499,7 +500,7 @@ Status: Active expect(allTwos.some(el => el.classList.contains('ml-1'))).toBe(true) }) - it('shows "No references" when no entries reference the current note', () => { + it('hides referenced-by section when no entries reference the current note', () => { render( ) - expect(screen.getByText('No references')).toBeInTheDocument() + expect(screen.queryByText('No references')).not.toBeInTheDocument() + expect(screen.queryByText('Referenced by')).not.toBeInTheDocument() }) it('navigates when clicking a referenced-by entry', () => { @@ -549,8 +551,8 @@ Status: Active ) // On Writing Well references responsibility via "Belongs to" (path match), not via "Type" // But the Type entry is at type/responsibility.md, so wikilinks to - // responsibility/grow-newsletter won't match. Should show "No references" - expect(screen.getByText('No references')).toBeInTheDocument() + // responsibility/grow-newsletter won't match. Section should be hidden + expect(screen.queryByText('Referenced by')).not.toBeInTheDocument() }) it('resolves references via aliased wikilinks', () => { @@ -612,8 +614,8 @@ Status: Active // noteA shows in Referenced By (via Belongs to) expect(screen.getByText(/via Belongs to/)).toBeInTheDocument() expect(screen.getByText('On Writing Well')).toBeInTheDocument() - // But NOT in Backlinks (even though outgoingLinks matches) - expect(screen.getByText('No backlinks')).toBeInTheDocument() + // But NOT in Backlinks (even though outgoingLinks matches) — section hidden + expect(screen.queryByText('Backlinks')).not.toBeInTheDocument() }) it('does not show self-references', () => { @@ -633,7 +635,7 @@ Status: Active /> ) - expect(screen.getByText('No references')).toBeInTheDocument() + expect(screen.queryByText('Referenced by')).not.toBeInTheDocument() }) }) }) diff --git a/src/components/Inspector.tsx b/src/components/Inspector.tsx index 59815747..3a695faf 100644 --- a/src/components/Inspector.tsx +++ b/src/components/Inspector.tsx @@ -99,22 +99,7 @@ function InspectorHeader({ collapsed, onToggle }: { collapsed: boolean; onToggle function EmptyInspector() { return ( - <> -

No note selected

-

No relationships

-
-

Referenced by

-

No references

-
-
-

Backlinks

-

No backlinks

-
-
-

History

-

No revision history

-
- +

No note selected

) } diff --git a/src/components/InspectorPanels.test.tsx b/src/components/InspectorPanels.test.tsx index 2f03abf3..9744c4c4 100644 --- a/src/components/InspectorPanels.test.tsx +++ b/src/components/InspectorPanels.test.tsx @@ -81,7 +81,7 @@ describe('DynamicRelationshipsPanel', () => { expect(chip!.style.color).toBe(expectedColor) }) - it('shows "No relationships" when frontmatter has no relations', () => { + it('hides empty state label when frontmatter has no relations', () => { render( { onNavigate={onNavigate} /> ) - expect(screen.getByText('No relationships')).toBeInTheDocument() + expect(screen.queryByText('No relationships')).not.toBeInTheDocument() }) it('renders relationship groups with wikilinks', () => { @@ -414,9 +414,9 @@ describe('BacklinksPanel', () => { vi.clearAllMocks() }) - it('shows "No backlinks" when empty', () => { - render() - expect(screen.getByText('No backlinks')).toBeInTheDocument() + it('renders nothing when empty', () => { + const { container } = render() + expect(container.innerHTML).toBe('') }) it('renders backlink entries', () => { @@ -450,9 +450,9 @@ describe('ReferencedByPanel', () => { vi.clearAllMocks() }) - it('shows "No references" when items is empty', () => { - render() - expect(screen.getByText('No references')).toBeInTheDocument() + it('renders nothing when empty', () => { + const { container } = render() + expect(container.innerHTML).toBe('') }) it('renders referenced-by entries grouped by relationship key', () => { diff --git a/src/components/InspectorPanels.tsx b/src/components/InspectorPanels.tsx index 01b449d9..8dfe4035 100644 --- a/src/components/InspectorPanels.tsx +++ b/src/components/InspectorPanels.tsx @@ -364,16 +364,13 @@ export function DynamicRelationshipsPanel({ frontmatter, entries, typeEntryMap, return (
- {relationshipEntries.length === 0 - ?

No relationships

- : relationshipEntries.map(({ key, refs }) => ( - handleRemoveRef(key, ref) : undefined} - onAddRef={canEdit ? (noteTitle) => handleAddRef(key, noteTitle) : undefined} - /> - )) - } + {relationshipEntries.map(({ key, refs }) => ( + handleRemoveRef(key, ref) : undefined} + onAddRef={canEdit ? (noteTitle) => handleAddRef(key, noteTitle) : undefined} + /> + ))} {onAddProperty ? : @@ -383,24 +380,20 @@ export function DynamicRelationshipsPanel({ frontmatter, entries, typeEntryMap, } export function BacklinksPanel({ backlinks, typeEntryMap, onNavigate }: { backlinks: VaultEntry[]; typeEntryMap: Record; onNavigate: (target: string) => void }) { + if (backlinks.length === 0) return null return (

- Backlinks {backlinks.length > 0 && {backlinks.length}} + Backlinks {backlinks.length}

- {backlinks.length === 0 - ?

No backlinks

- : ( -
- {backlinks.map((e) => { - const te = typeEntryMap[e.isA ?? ''] - return ( - onNavigate(e.title)} title={e.trashed ? 'Trashed' : e.archived ? 'Archived' : undefined} TypeIcon={getTypeIcon(e.isA, te?.icon)} /> - ) - })} -
- ) - } +
+ {backlinks.map((e) => { + const te = typeEntryMap[e.isA ?? ''] + return ( + onNavigate(e.title)} title={e.trashed ? 'Trashed' : e.archived ? 'Archived' : undefined} TypeIcon={getTypeIcon(e.isA, te?.icon)} /> + ) + })} +
) } @@ -425,42 +418,39 @@ export function ReferencedByPanel({ items, typeEntryMap, onNavigate }: { return Array.from(map.entries()) }, [items]) + if (items.length === 0) return null + return (

- Referenced by {items.length > 0 && {items.length}} + Referenced by {items.length}

- {items.length === 0 - ?

No references

- : ( -
- {grouped.map(([viaKey, groupEntries]) => ( -
- - via {viaKey} - -
- {groupEntries.map((e) => { - const te = typeEntryMap[e.isA ?? ''] - return ( - onNavigate(e.title)} - title={e.trashed ? 'Trashed' : e.archived ? 'Archived' : undefined} - TypeIcon={getTypeIcon(e.isA, te?.icon)} - /> - ) - })} -
-
- ))} +
+ {grouped.map(([viaKey, groupEntries]) => ( +
+ + via {viaKey} + +
+ {groupEntries.map((e) => { + const te = typeEntryMap[e.isA ?? ''] + return ( + onNavigate(e.title)} + title={e.trashed ? 'Trashed' : e.archived ? 'Archived' : undefined} + TypeIcon={getTypeIcon(e.isA, te?.icon)} + /> + ) + })} +
- ) - } + ))} +
) }