From 05c840d6cff21f34ccc6b06ff2576134b59da91d Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 20 Feb 2026 21:47:42 +0100 Subject: [PATCH] feat(notelist): pin type document at top in section group view When viewing a section group (e.g., Projects), the corresponding type document (type/project.md) is now pinned at the top of the NoteList as a prominent card. Clicking it navigates to the type document. Instances of that type are listed below. This makes the type document the entry point for each category, reinforcing the "types are files" mental model. Co-Authored-By: Claude Opus 4.6 --- src/components/NoteList.tsx | 172 +++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/src/components/NoteList.tsx b/src/components/NoteList.tsx index b7f8f119..36044d56 100644 --- a/src/components/NoteList.tsx +++ b/src/components/NoteList.tsx @@ -215,6 +215,7 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF const [collapsedGroups, setCollapsedGroups] = useState>(new Set()) const isEntityView = selection.kind === 'entity' + const isSectionGroup = selection.kind === 'sectionGroup' const toggleGroup = useCallback((label: string) => { setCollapsedGroups((prev) => { @@ -225,6 +226,13 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF }) }, []) + // Find the type document for this section group (e.g., type/project.md for "Project") + const typeDocument = useMemo(() => { + if (!isSectionGroup) return null + const typeName = (selection as { kind: 'sectionGroup'; type: string }).type + return entries.find((e) => e.isA === 'Type' && e.title === typeName) ?? null + }, [isSectionGroup, selection, entries]) + const entityGroups = useMemo( () => isEntityView ? buildRelationshipGroups(selection.entry, entries, allContent) : [], [isEntityView, selection, entries, allContent] @@ -308,12 +316,77 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF ) }, [selectedNote?.path, onSelectNote]) + const renderPinnedView = useCallback((entity: VaultEntry, groups: RelationshipGroup[]) => { + const entityTypeColor = getTypeColor(entity.isA) + const entityLightColor = getTypeLightColor(entity.isA) + const EntityIcon = getTypeIcon(entity.isA) + return ( +
+ {/* Prominent card */} +
onSelectNote(entity)} + > + +
+ {entity.title} +
+
+ {entity.snippet} +
+
+ {relativeDate(getDisplayDate(entity))} +
+
+ + {/* Relationship groups */} + {groups.length === 0 ? ( +
+ {query ? 'No matching items' : 'No related items'} +
+ ) : ( + groups.map((group) => { + const isGroupCollapsed = collapsedGroups.has(group.label) + return ( +
+ + {!isGroupCollapsed && group.entries.map((groupEntry) => renderItem(groupEntry))} +
+ ) + }) + )} +
+ ) + }, [onSelectNote, query, collapsedGroups, toggleGroup, renderItem]) + return (
{/* Header */}

- {isEntityView ? selection.entry.title : 'Notes'} + {isEntityView ? selection.entry.title : (typeDocument ? typeDocument.title : 'Notes')}

- {!isCollapsed && group.entries.map((entry) => renderItem(entry))} -
- ) - }) - )} -
- ) - })() : ( - searched.length === 0 ? ( -
No notes found
- ) : ( - { - const entry = searched[index] - return entry ? renderItem(entry) : null - }} - /> - ) + )} + {searched.length === 0 ? ( +
No notes found
+ ) : ( + searched.map((entry) => renderItem(entry)) + )} +
)}