diff --git a/_bmad-output/implementation-artifacts/spec-improve-note-graph-relationships.md b/_bmad-output/implementation-artifacts/spec-improve-note-graph-relationships.md index 44d9649..c6fe5f2 100644 --- a/_bmad-output/implementation-artifacts/spec-improve-note-graph-relationships.md +++ b/_bmad-output/implementation-artifacts/spec-improve-note-graph-relationships.md @@ -55,6 +55,8 @@ Cela donne à la vue en graphe un aspect incomplet et décousu, limitant grandem **Execution:** - [x] `memento-note/app/api/graph/route.ts` -- Modifier la route pour interroger en parallèle `prisma.noteLink` et `prisma.memoryEchoInsight`, filtrer les éléments invalides/corrompus/supprimés, et les injecter avec les types `explicit_link` et `semantic_echo`. - [x] `memento-note/components/note-graph-view.tsx` -- Enrichir la logique de mappage des arêtes du graphe pour attribuer des styles visuels premium à chaque type de lien (WikiLinks en vert émeraude épais et plein, Échos sémantiques IA en violet pointillés, etc.). +- [x] `memento-note/components/note-graph-view.tsx` -- Rendre la légende des carnets (clusters) interactive : ajouter un état `selectedNotebookId` pour filtrer à la volée les nœuds et arêtes du graphe. Styliser les boutons actifs/inactifs de manière premium avec des micro-animations. +- [x] `memento-note/components/note-graph-view.tsx` -- Afficher le nom du carnet dans le panneau latéral de détail de la note sous forme de tag interactif cliquable pour isoler instantanément le carnet sélectionné. **Acceptance Criteria:** - **Given** une note A contenant un WikiLink vers une note B (`NoteLink` enregistré) et une note C sémantiquement proche de la note A (`MemoryEchoInsight` enregistré) @@ -106,3 +108,11 @@ Pour les pointillés (dash) dans `react-force-graph-2d`, nous adapterons l'objet - Rendu du panneau de légende des liaisons dans le coin inférieur gauche pour une expérience utilisateur premium. [`note-graph-view.tsx:340`](../../memento-note/components/note-graph-view.tsx#L340) + +**Interactive Filtering & Notebook Navigation** + +- Boutons de légende interactifs pour isoler ou réinitialiser le filtre par carnet à la volée. + [`note-graph-view.tsx:342`](../../memento-note/components/note-graph-view.tsx#L342) + +- Tag cliquable dans le panneau latéral de détails pour isoler instantanément le carnet associé. + [`note-graph-view.tsx:421`](../../memento-note/components/note-graph-view.tsx#L421) diff --git a/memento-note/components/note-graph-view.tsx b/memento-note/components/note-graph-view.tsx index 0e307cc..ed844b8 100644 --- a/memento-note/components/note-graph-view.tsx +++ b/memento-note/components/note-graph-view.tsx @@ -27,6 +27,7 @@ export function NoteGraphView() { const [selectedNode, setSelectedNode] = useState(null) const [notePreview, setNotePreview] = useState(null) const [previewLoading, setPreviewLoading] = useState(false) + const [selectedNotebookId, setSelectedNotebookId] = useState(null) // ─── Resize ─────────────────────────────────────────────────────────────── useEffect(() => { @@ -89,9 +90,17 @@ export function NoteGraphView() { // ─── Graph data ─────────────────────────────────────────────────────────── const graphData = useMemo(() => { if (!rawData) return { nodes: [], links: [] } - const filtered = searchFilter.trim() - ? rawData.nodes.filter(n => n.title.toLowerCase().includes(searchFilter.toLowerCase())) + + // Filter by notebook + let filtered = selectedNotebookId + ? rawData.nodes.filter(n => n.notebookId === selectedNotebookId) : rawData.nodes + + // Filter by text search + filtered = searchFilter.trim() + ? filtered.filter(n => n.title.toLowerCase().includes(searchFilter.toLowerCase())) + : filtered + const filteredIds = new Set(filtered.map(n => n.id)) return { nodes: filtered.map(n => ({ @@ -134,7 +143,12 @@ export function NoteGraphView() { } }), } - }, [rawData, searchFilter, colorMap]) + }, [rawData, searchFilter, colorMap, selectedNotebookId]) + + const selectedNotebookName = useMemo(() => { + if (!selectedNode || !rawData) return null + return rawData.clusters.find(c => c.id === selectedNode.notebookId)?.name ?? null + }, [selectedNode, rawData]) // ─── Handlers (double-click via timer) ────────────────────────────────── const lastClickRef = useRef<{ id: string; time: number } | null>(null) @@ -325,15 +339,43 @@ export function NoteGraphView() { )} - {/* Cluster legend */} + {/* Cluster legend (Interactive Notebook Filter) */} {rawData && rawData.clusters && rawData.clusters.length > 0 && ( -
- {rawData.clusters.map(c => ( -
- - {c.name} -
- ))} +
+ Carnets + {selectedNotebookId && ( + + )} +
+ {rawData.clusters.map(c => { + const isSelected = selectedNotebookId === c.id + const isAnySelected = selectedNotebookId !== null + const color = colorMap.get(c.id) ?? '#94a3b8' + return ( + + ) + })} +
)} @@ -376,6 +418,15 @@ export function NoteGraphView() { {new Date(selectedNode.createdAt).toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' })} {' · '}{selectedNode.degree} connexion{selectedNode.degree !== 1 ? 's' : ''}

+ {selectedNotebookName && ( + + )}