From 462b920da17ae942e2af85c05bed9362d62863d5 Mon Sep 17 00:00:00 2001 From: Luca Rossi Date: Wed, 25 Feb 2026 18:30:35 +0100 Subject: [PATCH] fix: show type label for all notes in wiki-link autocomplete (#76) * fix: show type label for all notes in wiki-link autocomplete Notes with the default 'Note' type (isA: null) had noteType set to undefined, hiding their type label. Now every note always shows its type in the autocomplete dropdown. Co-Authored-By: Claude Opus 4.6 * design: add wiki-autocomplete-type-fix wireframe Shows the fixed autocomplete dropdown where every note displays its type label, including default "Note" types in muted color. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- design/wiki-autocomplete-type-fix.pen | 238 ++++++++++++++++++++++++++ src/components/Editor.test.tsx | 29 ++++ src/components/Editor.tsx | 4 +- 3 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 design/wiki-autocomplete-type-fix.pen diff --git a/design/wiki-autocomplete-type-fix.pen b/design/wiki-autocomplete-type-fix.pen new file mode 100644 index 00000000..b5fee680 --- /dev/null +++ b/design/wiki-autocomplete-type-fix.pen @@ -0,0 +1,238 @@ +{ + "children": [ + { + "type": "frame", + "id": "wikiTypeFix", + "x": 0, + "y": 0, + "name": "Wiki-link Autocomplete — All Notes Show Type Labels", + "clip": true, + "width": 400, + "height": 340, + "fill": "#FFFFFF", + "layout": "vertical", + "gap": 0, + "children": [ + { + "type": "frame", + "id": "wtfHeader", + "name": "Context: Editor line with [[query", + "width": "fill_container", + "height": 40, + "fill": "#FAFAFA", + "padding": [10, 16], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "wtfQuery", + "content": "Linking to [[no", + "fontSize": 15, + "fill": "#1a1a1a" + } + ] + }, + { + "type": "frame", + "id": "wtfMenu", + "name": "Suggestion Menu — every item shows type", + "width": 320, + "fill": "#FFFFFF", + "layout": "vertical", + "gap": 0, + "padding": 4, + "cornerRadius": 6, + "stroke": { + "align": "outside", + "thickness": 1, + "fill": "rgba(0,0,0,0.1)" + }, + "children": [ + { + "type": "frame", + "id": "wtfItem1", + "name": "Item: Daily Notes (Note — default type shown)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "fill": "rgba(21,93,255,0.08)", + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle1", + "content": "Daily Notes", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType1", + "content": "Note", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--muted-foreground, #888888)", + "opacity": 0.8 + } + ] + }, + { + "type": "frame", + "id": "wtfItem2", + "name": "Item: Node.js Setup (Note — no frontmatter type)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle2", + "content": "Node.js Setup", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType2", + "content": "Note", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--muted-foreground, #888888)", + "opacity": 0.8 + } + ] + }, + { + "type": "frame", + "id": "wtfItem3", + "name": "Item: Notification System (Project — explicit type)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle3", + "content": "Notification System", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType3", + "content": "Project", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--accent-red, #E53935)", + "opacity": 0.8 + } + ] + }, + { + "type": "frame", + "id": "wtfItem4", + "name": "Item: NoSQL Patterns (Topic — explicit type)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle4", + "content": "NoSQL Patterns", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType4", + "content": "Topic", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--accent-green, #43A047)", + "opacity": 0.8 + } + ] + }, + { + "type": "frame", + "id": "wtfItem5", + "name": "Item: Norman (Person — explicit type)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle5", + "content": "Norman", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType5", + "content": "Person", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--accent-yellow, #FDD835)", + "opacity": 0.8 + } + ] + }, + { + "type": "frame", + "id": "wtfItem6", + "name": "Item: Notes on Architecture (Note — default type shown)", + "width": "fill_container", + "height": 32, + "padding": [6, 10], + "cornerRadius": 4, + "alignItems": "center", + "justifyContent": "space-between", + "children": [ + { + "type": "text", + "id": "wtfTitle6", + "content": "Notes on Architecture", + "fontSize": 13, + "fill": "#1a1a1a", + "fontWeight": "400" + }, + { + "type": "text", + "id": "wtfType6", + "content": "Note", + "fontSize": 11, + "fontWeight": "500", + "fill": "var(--muted-foreground, #888888)", + "opacity": 0.8 + } + ] + } + ] + } + ] + } + ], + "variables": {} +} diff --git a/src/components/Editor.test.tsx b/src/components/Editor.test.tsx index e5bb6fff..2c88521d 100644 --- a/src/components/Editor.test.tsx +++ b/src/components/Editor.test.tsx @@ -390,6 +390,35 @@ describe('wikilink autocomplete', () => { mockFilterSuggestionItems.mockImplementation((items: unknown[]) => items) }) + it('always includes noteType for every item, including default Note type (regression)', async () => { + const mixedEntries: VaultEntry[] = [ + { ...mockEntry, title: 'My Project', filename: 'proj.md', path: '/vault/proj.md', isA: 'Project', aliases: [] }, + { ...mockEntry, title: 'Plain Note', filename: 'plain.md', path: '/vault/plain.md', isA: null, aliases: [] }, + { ...mockEntry, title: 'Explicit Note', filename: 'explicit.md', path: '/vault/explicit.md', isA: 'Note', aliases: [] }, + ] + capturedGetItems = null + mockFilterSuggestionItems.mockImplementation((items: unknown[]) => items) + render( + + ) + const items = await capturedGetItems!('Note') + // Every item must have a defined noteType — none should be blank + for (const item of items) { + expect(item.noteType).toBeTruthy() + expect(item.typeColor).toBeTruthy() + } + // Default notes (isA: null) should show 'Note' as their type + const plainNote = items.find((i: { title: string }) => i.title === 'Plain Note') + expect(plainNote).toBeDefined() + expect(plainNote!.noteType).toBe('Note') + mockFilterSuggestionItems.mockImplementation((items: unknown[]) => items) + }) + it('disambiguates entries with the same title by appending folder name', async () => { const sameTitle: VaultEntry[] = [ { ...mockEntry, title: 'Standup', filename: 'standup.md', path: '/vault/work/standup.md', aliases: [] }, diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 13084bd4..71ae6684 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -165,8 +165,8 @@ function SingleEditorView({ editor, entries, onNavigateWikilink, onChange }: { e const final = disambiguateTitles(deduplicateByPath(filtered)) return final.map(({ group, ...rest }) => ({ ...rest, - noteType: group !== 'Note' ? group : undefined, - typeColor: group !== 'Note' ? getTypeColor(group) : undefined, + noteType: group, + typeColor: getTypeColor(group), })) }, [baseItems, editor])