From 47af7d9fd051b466e01633aaef2c141b0c42b25e Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 8 May 2026 10:40:01 +0000
Subject: [PATCH 1/8] feat(ux): improve search panel usability and styling
alignment
- Add a custom clear button to the search input for better query management.
- Align search panel buttons with defined design system patterns.
- Improve accessibility by adding ARIA roles and escaping entities in the Search Panel.
- Suppress native browser search decorations for a consistent UI.
Co-authored-by: d-oit <6849456+d-oit@users.noreply.github.com>
---
src/features/search/SearchPanel.tsx | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/features/search/SearchPanel.tsx b/src/features/search/SearchPanel.tsx
index 819fa11..43bc3c3 100644
--- a/src/features/search/SearchPanel.tsx
+++ b/src/features/search/SearchPanel.tsx
@@ -49,7 +49,7 @@ const NoResultsState: React.FC<{ query: string; onClear: () => void }> = ({ quer
We couldn't find anything matching "{query}" in your current library.
+We couldn't find anything matching "{query}" in your current library.
Every note is an entity.
', + onUpdate: ({ editor }) => { + let claimsCount = 0; + let mentionsCount = 0; + editor.state.doc.descendants((node) => { + if (node.marks.some(m => m.type.name === 'claim') && node.isText) { + claimsCount++; + } + if (node.marks.some(m => m.type.name === 'mention')) { + mentionsCount++; + } + return true; + }); + setStats({ claims: claimsCount, mentions: mentionsCount }); + } }); const handleSave = async () => { if (!title.trim() || !editor) return; + setSaveStatus('saving'); try { const content = editor.getHTML(); const entity = await repository.createEntity({ @@ -54,17 +72,13 @@ const Editor: React.FC = () => { }); const { doc } = editor.state; - const claims: { statement: string; source: string; status: string }[] = []; + const claims: string[] = []; const mentions: { id: string, name: string }[] = []; doc.descendants((node) => { const claimMark = node.marks.find(mark => mark.type.name === 'claim'); if (claimMark && node.isText && node.text) { - claims.push({ - statement: node.text, - source: claimMark.attrs.source || 'Manual entry', - status: claimMark.attrs.verification_status || 'unverified' - }); + claims.push(node.text); } const mentionMark = node.marks.find(mark => mark.type.name === 'mention'); @@ -85,14 +99,13 @@ const Editor: React.FC = () => { }); // Persist Claims - for (const claim of claims) { + for (const statement of claims) { await repository.createClaim({ entity_id: entity.id!, - statement: claim.statement, + statement: statement, confidence: 1.0, evidence: 'Extracted from editor', - source: claim.source, - verification_status: claim.status as Claim['verification_status'] + source: 'Manual entry' }); } @@ -112,11 +125,14 @@ const Editor: React.FC = () => { jobCoordinator.enqueue('reindex-document', entity.id, { entityId: entity.id }); setStatus({ type: 'success', message: `Saved successfully! (${claims.length} claims, ${mentions.length} links)` }); + setSaveStatus('saved'); setTitle(''); editor.commands.setContent(''); + setTimeout(() => setSaveStatus('idle'), 3000); } catch (err) { logger.error('Failed to save entity', err); - setStatus({ type: 'error', message: 'Save failed. See console for details.' }); + setSaveStatus('error'); + setStatus({ type: 'error', message: 'Save failed. Content has been preserved for recovery.' }); } }; @@ -124,6 +140,27 @@ const Editor: React.FC = () => { if (!editor || !target.id) return; editor.chain().focus().setMention({ entityId: target.id, entityName: target.name }).run(); setShowMentionMenu(false); + setMentionIndex(0); + }; + + const handleMentionKeyDown = (e: React.KeyboardEvent) => { + if (!showMentionMenu) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setMentionIndex(prev => (prev + 1) % allEntities.length); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setMentionIndex(prev => (prev - 1 + allEntities.length) % allEntities.length); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (allEntities[mentionIndex]) { + insertMention(allEntities[mentionIndex]); + } + } else if (e.key === 'Escape') { + e.preventDefault(); + setShowMentionMenu(false); + } }; return ( @@ -150,47 +187,66 @@ const Editor: React.FC = () => {- Generate portable versions of your knowledge base. All exports are processed entirely in your browser. -
- -+ Your local SQLite/OPFS storage is the canonical source of truth. + Use exports to share knowledge or create portable artifacts. +
Processing your knowledge base...
+Showing {filteredData.entities.length} entities and {filteredData.links.length} relationships.
+Every note is an entity.
', - onUpdate: ({ editor }) => { - let claimsCount = 0; - let mentionsCount = 0; - editor.state.doc.descendants((node) => { - if (node.marks.some(m => m.type.name === 'claim') && node.isText) { - claimsCount++; - } - if (node.marks.some(m => m.type.name === 'mention')) { - mentionsCount++; - } - return true; - }); - setStats({ claims: claimsCount, mentions: mentionsCount }); - } }); const handleSave = async () => { if (!title.trim() || !editor) return; - setSaveStatus('saving'); try { const content = editor.getHTML(); const entity = await repository.createEntity({ @@ -72,13 +54,17 @@ const Editor: React.FC = () => { }); const { doc } = editor.state; - const claims: string[] = []; + const claims: { statement: string; source: string; status: string }[] = []; const mentions: { id: string, name: string }[] = []; doc.descendants((node) => { const claimMark = node.marks.find(mark => mark.type.name === 'claim'); if (claimMark && node.isText && node.text) { - claims.push(node.text); + claims.push({ + statement: node.text, + source: claimMark.attrs.source || 'Manual entry', + status: claimMark.attrs.verification_status || 'unverified' + }); } const mentionMark = node.marks.find(mark => mark.type.name === 'mention'); @@ -99,13 +85,14 @@ const Editor: React.FC = () => { }); // Persist Claims - for (const statement of claims) { + for (const claim of claims) { await repository.createClaim({ entity_id: entity.id!, - statement: statement, + statement: claim.statement, confidence: 1.0, evidence: 'Extracted from editor', - source: 'Manual entry' + source: claim.source, + verification_status: claim.status as Claim['verification_status'] }); } @@ -125,14 +112,11 @@ const Editor: React.FC = () => { jobCoordinator.enqueue('reindex-document', entity.id, { entityId: entity.id }); setStatus({ type: 'success', message: `Saved successfully! (${claims.length} claims, ${mentions.length} links)` }); - setSaveStatus('saved'); setTitle(''); editor.commands.setContent(''); - setTimeout(() => setSaveStatus('idle'), 3000); } catch (err) { logger.error('Failed to save entity', err); - setSaveStatus('error'); - setStatus({ type: 'error', message: 'Save failed. Content has been preserved for recovery.' }); + setStatus({ type: 'error', message: 'Save failed. See console for details.' }); } }; @@ -140,27 +124,6 @@ const Editor: React.FC = () => { if (!editor || !target.id) return; editor.chain().focus().setMention({ entityId: target.id, entityName: target.name }).run(); setShowMentionMenu(false); - setMentionIndex(0); - }; - - const handleMentionKeyDown = (e: React.KeyboardEvent) => { - if (!showMentionMenu) return; - - if (e.key === 'ArrowDown') { - e.preventDefault(); - setMentionIndex(prev => (prev + 1) % allEntities.length); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - setMentionIndex(prev => (prev - 1 + allEntities.length) % allEntities.length); - } else if (e.key === 'Enter') { - e.preventDefault(); - if (allEntities[mentionIndex]) { - insertMention(allEntities[mentionIndex]); - } - } else if (e.key === 'Escape') { - e.preventDefault(); - setShowMentionMenu(false); - } }; return ( @@ -187,66 +150,47 @@ const Editor: React.FC = () => {- Your local SQLite/OPFS storage is the canonical source of truth. - Use exports to share knowledge or create portable artifacts. -
++ Generate portable versions of your knowledge base. All exports are processed entirely in your browser. +
+ +Processing your knowledge base...
Showing {filteredData.entities.length} entities and {filteredData.links.length} relationships.
-Every note is an entity.
', + onUpdate: ({ editor }) => { + let claimsCount = 0; + let mentionsCount = 0; + editor.state.doc.descendants((node) => { + if (node.marks.some(m => m.type.name === 'claim') && node.isText) { + claimsCount++; + } + if (node.marks.some(m => m.type.name === 'mention')) { + mentionsCount++; + } + return true; + }); + setStats({ claims: claimsCount, mentions: mentionsCount }); + } }); const handleSave = async () => { if (!title.trim() || !editor) return; + setSaveStatus('saving'); try { const content = editor.getHTML(); const entity = await repository.createEntity({ @@ -54,17 +72,13 @@ const Editor: React.FC = () => { }); const { doc } = editor.state; - const claims: { statement: string; source: string; status: string }[] = []; + const claims: string[] = []; const mentions: { id: string, name: string }[] = []; doc.descendants((node) => { const claimMark = node.marks.find(mark => mark.type.name === 'claim'); if (claimMark && node.isText && node.text) { - claims.push({ - statement: node.text, - source: claimMark.attrs.source || 'Manual entry', - status: claimMark.attrs.verification_status || 'unverified' - }); + claims.push(node.text); } const mentionMark = node.marks.find(mark => mark.type.name === 'mention'); @@ -85,14 +99,13 @@ const Editor: React.FC = () => { }); // Persist Claims - for (const claim of claims) { + for (const statement of claims) { await repository.createClaim({ entity_id: entity.id!, - statement: claim.statement, + statement: statement, confidence: 1.0, evidence: 'Extracted from editor', - source: claim.source, - verification_status: claim.status as Claim['verification_status'] + source: 'Manual entry' }); } @@ -112,11 +125,14 @@ const Editor: React.FC = () => { jobCoordinator.enqueue('reindex-document', entity.id, { entityId: entity.id }); setStatus({ type: 'success', message: `Saved successfully! (${claims.length} claims, ${mentions.length} links)` }); + setSaveStatus('saved'); setTitle(''); editor.commands.setContent(''); + setTimeout(() => setSaveStatus('idle'), 3000); } catch (err) { logger.error('Failed to save entity', err); - setStatus({ type: 'error', message: 'Save failed. See console for details.' }); + setSaveStatus('error'); + setStatus({ type: 'error', message: 'Save failed. Content has been preserved for recovery.' }); } }; @@ -124,6 +140,27 @@ const Editor: React.FC = () => { if (!editor || !target.id) return; editor.chain().focus().setMention({ entityId: target.id, entityName: target.name }).run(); setShowMentionMenu(false); + setMentionIndex(0); + }; + + const handleMentionKeyDown = (e: React.KeyboardEvent) => { + if (!showMentionMenu) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setMentionIndex(prev => (prev + 1) % allEntities.length); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setMentionIndex(prev => (prev - 1 + allEntities.length) % allEntities.length); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (allEntities[mentionIndex]) { + insertMention(allEntities[mentionIndex]); + } + } else if (e.key === 'Escape') { + e.preventDefault(); + setShowMentionMenu(false); + } }; return ( @@ -150,47 +187,66 @@ const Editor: React.FC = () => {- Generate portable versions of your knowledge base. All exports are processed entirely in your browser. -
- -+ Your local SQLite/OPFS storage is the canonical source of truth. + Use exports to share knowledge or create portable artifacts. +
Processing your knowledge base...
+Showing {filteredData.entities.length} entities and {filteredData.links.length} relationships.
+