# User Stories — Momento Next Phase
> Basé sur l'analyse du prototype `architectural-grid/` et du code production `memento-note/`.
> Dernière mise à jour : 2026-05-29 (Epic 6 Croissance & Activation ajouté — analyse stratégique Mary/BMad)
---
## Tableau de bord
| ID | Feature | Statut | Fichiers livrés |
|---|---|---|---|
| **US-SIDEBAR** | Sidebar deux colonnes (rail d'icônes + panneau) | ✅ **LIVRÉ** | `sidebar.tsx` restructuré |
| **US-SEARCH** | Recherche Globale Dual-Panel + Ctrl+K | ✅ **LIVRÉ** | `search-modal.tsx`, `search-modal-context.tsx`, bug `openNote` corrigé |
| **US-LIVING-BLOCKS** | Blocs Vivants (Transclusion Bidirectionnelle) | ✅ **LIVRÉ** | `tiptap-unique-id-extension.ts`, `tiptap-live-block-extension.tsx`, `block-picker.tsx`, `app/api/blocks/*`, migration `LiveBlockRef` |
| **US-MEMORY-ECHO** | Résonance Sémantique + Embed depuis Echo | ✅ **LIVRÉ** | `memory-echo-section.tsx`, `/api/notes/[id]/live-block-refs`, `/api/blocks/resolve` |
| **US-INFO-RÉSEAU** | Panneau Info + Réseau Local | ✅ **LIVRÉ** | `note-network-tab.tsx`, `sync-note-links.ts`, migration `NoteLink`, picker `[[` |
| **US-CLIPPER** | Web Clipper | ✅ **LIVRÉ** | `extension/`, `/api/clip/*`, migration `sourceUrl`, badge panneau Info |
| **US-GRAPH** | Graphe de Connaissance Global enrichi | ✅ **LIVRÉ** | `note-graph-view.tsx` — filtres liens, seuil sémantique, focus voisinage, couleurs carnets, double-clic ouverture |
| **US-INSIGHTS** | Clusters Sémantiques + Bridge Notes | ✅ **LIVRÉ** | `app/(main)/insights/page.tsx`, `network-graph.tsx`, `/api/clusters`, `/api/bridge-notes/*`, état dégradé si clusters périmés |
| **US-TEMPORAL** | Prédictions d'accès temporelles | ⏸️ **REPORTÉ** | Remplacé par rappels + révision SM-2 + Memory Echo ; heuristique faible, migration NoteAccessLog non prioritaire |
| **US-FLASHCARDS** | Révision IA — Répétition espacée SM-2 | ✅ **LIVRÉ** | `/revision`, `/api/flashcards/*`, SM-2, génération IA depuis l'éditeur |
| **US-STRUCTURED-VIEWS** | Vues Structurées (Tableau/Kanban/Galerie) | ✅ **LIVRÉ** | `/api/notebooks/[id]/schema`, `/api/notes/[id]/properties`, vues structurées + panneau propriétés éditeur |
| **US-NEXTGEN-EDITOR** | Éditeur Next-Gen : Drag Handle + Menu Bloc + **Vue Structurée Inline** (redesign US-4) + Smart Paste | ✅ **LIVRÉ** | Voir `docs/story-nextgen-editor.md` + `docs/story-nextgen-editor-us4-redesign.md` |
| **US-EDITOR-PERF** | Performance de frappe TipTap (quick wins) | ✅ **LIVRÉ** | `rich-text-editor.tsx` (useEditorState), `note-editor-context.tsx` (debounced setContent) |
| **US-EDITOR-UX** | Micro-interactions saisie (slash menu, sélection multi-blocs, paste étendu, placeholders) | ✅ **LIVRÉ** | Sélection globale, redesign Slash Menu (favoris/preview), placeholders contextuels, smart paste étendu, Turn Into & Undo/Redo |
| **US-EDITOR-MOBILE** | Expérience tactile & toolbar mobile adaptée | ✅ **LIVRÉ** | Toolbar fixe premium 44px, Bottom Sheet tactile (actions de bloc + IA), sélection facilitée de bloc |
| **US-EDITOR-MARKDOWN** | Rendu WYSIWYG Markdown fidèle (round-trip byte-for-byte) | ⏳ **À FAIRE** | Brief : `docs/brief-markdown-roundtrip.md` |
| **US-ONBOARDING** | Wizard Activation — Effet "Aha!" Recherche Sémantique | 🆕 **À FAIRE** | Story : `docs/story-onboarding-activation.md` |
| **US-BRAINSTORM-FINALIZE** | Brainstorm Canvas D3 — Finalisation (export PPTX, gaps UX) | 🆕 **À FAIRE** | ~75% code existant (`brainstorm-page.tsx`, 14 routes API) |
| **US-CHAT-PDF** | Chat with PDF — RAG documentaire | 🆕 **À FAIRE** | — |
---
## Ordre d'implémentation (dépendances)
```
Quick wins (en premier, ~2h) :
US-EDITOR-PERF <- depend de : rien (modifs config TipTap)
Editeur Next-Gen (bloc principal) :
US-NEXTGEN-EDITOR <- depend de : US-LIVING-BLOCKS, US-STRUCTURED-VIEWS
US-EDITOR-UX <- depend de : US-NEXTGEN-EDITOR (drag handle + menu bloc en place)
US-EDITOR-MOBILE <- depend de : US-NEXTGEN-EDITOR (drag handle existant)
Plus tard :
US-EDITOR-MARKDOWN <- depend de : rien (evaluation Milkdown, low priority)
Stories livrees :
US-LIVING-BLOCKS <- depend de : TipTap UniqueID (fondation)
US-MEMORY-ECHO <- depend de : pgvector existant
US-INFO-RESEAU <- depend de : wikilinks + backlinks
US-CLIPPER <- depend de : migration Note.sourceUrl
US-GRAPH <- depend de : /api/graph existant
US-INSIGHTS <- depend de : Memory Echo + clusters
US-FLASHCARDS <- depend de : migration FlashcardDeck + Flashcard
US-STRUCTURED-VIEWS <- depend de : migration NotebookSchema + NotebookProperty
Reportees :
US-TEMPORAL <- depend de : migration NoteAccessLog (REPORTE)
```
---
## Migrations Prisma requises (ordre d'application)
1. `Note.sourceUrl String?` → US-CLIPPER
2. `LiveBlockRef` → US-LIVING-BLOCKS
3. `NotebookSchema` + `NotebookProperty` + `NoteProperty` → US-STRUCTURED-VIEWS
4. `FlashcardDeck` + `Flashcard` + `FlashcardReview` → US-FLASHCARDS
5. `NoteAccessLog` → US-TEMPORAL
---
---
## ✅ US-SIDEBAR — Sidebar Deux Colonnes (LIVRÉ)
**Contexte :**
Le prototype `architectural-grid/Sidebar.tsx` introduit un layout deux colonnes inspiré de SiYuan : une rail d'icônes étroit (54px) à gauche + un panneau de contenu dynamique à droite. L'ancienne sidebar avait des tabs horizontaux qui consommaient de l'espace vertical et manquaient de hiérarchie visuelle.
**Livraison :**
- `memento-note/components/sidebar.tsx` — restructuré en `flex-row`
- Colonne gauche (54px) : logo "M" avec dropdown profil, boutons nav verticaux avec indicateur actif, boutons utilitaires bas (notifications, corbeille, partagé, recherche Ctrl+K, thème, paramètres, déconnexion)
- Colonne droite : panneau dynamique (carnets, agents, rappels, brainstorms, révisions)
- `NavigationView` étendu à `'notebooks' | 'agents' | 'reminders' | 'brainstorms' | 'revision'`
- Tooltips au survol qui apparaissent à droite du rail (UX SiYuan)
- Placeholder "Révisions" ajouté (en attente de US-FLASHCARDS)
**Décision design :** pas de toggle "ancien layout vs nouveau" dans les paramètres — design unique assumé.
---
## ✅ US-SEARCH — Recherche Globale Redesignée (LIVRÉ)
**Contexte :**
Le prototype `SearchModal.tsx` est une refonte complète avec dual-panel, regex, queries sauvegardées et preview contextuel. Il contenait **un bug** : le flag `caseSensitive` appliquait toujours `'gi'`. La navigation vers la note utilisait le mauvais paramètre URL.
**Livraison :**
- `memento-note/components/search-modal.tsx` — modal dual-panel (40% résultats + 60% préview)
- `memento-note/context/search-modal-context.tsx` — contexte React + raccourci global `Ctrl+K`
- `memento-note/components/providers-wrapper.tsx` — ajout du `SearchModalProvider`
- Bouton loupe dans le rail d'icônes du sidebar
**Bugs corrigés :**
```typescript
// Bug 1 - casse regex (prototype) :
// AVANT : const flags = caseSensitive ? 'gi' : 'gi'
// APRÈS : const flags = caseSensitive ? 'g' : 'gi'
// Bug 2 - navigation vers la note :
// AVANT : router.push(`/home?noteId=${noteId}`)
// APRÈS : router.push(`/home?openNote=${noteId}`)
```
---
## US-CLIPPER — Web Clipper Intégré
**Contexte :**
Le prototype contient `ClipperSimulator.tsx` (618 lignes) qui simule le clipping avec données mock. Il n'existe rien d'équivalent dans `memento-note`. La feature doit être réalisée en deux parties : une **extension Chrome/Firefox** et un **modal de réception** côté app.
**En tant qu'utilisateur**, je veux capturer n'importe quelle page web depuis mon navigateur et l'enregistrer dans Momento avec résumé IA, tags suggérés et choix du carnet — sans quitter le navigateur.
**Critères d'acceptation :**
### Extension navigateur (`memento-note/extension/` — nouveau répertoire)
- Icône dans la barre d'outils du navigateur, clic ouvre un popup 380×520px
- Popup affiche : titre de la page (éditable), domaine + favicon, mode de capture (`Article complet` / `Sélection` / `Lien seul`)
- Bouton "Analyser avec IA" → appel `POST /api/clip/analyze` (URL + HTML content) → retourne `{ title, summary, tags[], readingTime }`
- Champ de sélection du carnet (dropdown hiérarchique, dernier carnet mémorisé)
- Aperçu du contenu clipé (150px scrollable)
- Bouton "Sauvegarder dans Momento" → appel `POST /api/clip/save` avec `{ url, title, content, summary, tags, notebookId }`
- Feedback visuel : spinner → "Sauvegardé ✓" avec lien direct vers la note
### Route API `app/api/clip/analyze/route.ts` (nouvelle)
- Reçoit `{ url, html }`, nettoie le HTML via `@mozilla/readability` ou `DOMPurify`
- Appelle le LLM Router existant (`lib/ai/llm-router`) pour générer `{ title, summary (3 phrases max), tags[] (5 max), readingTime }`
- Retourne JSON structuré en < 3s
### Route API `app/api/clip/save/route.ts` (nouvelle)
- Auth via NextAuth session
- Crée une `Note` de type `richtext` avec `content` = HTML nettoyé + bloc source en bas (`
Extrait de [domaine] le [date]`)
- Sauvegarde l'URL source dans `Note.sourceUrl` (nouveau champ Prisma optionnel `String?`)
- Retourne `{ noteId, noteUrl }` pour que l'extension redirige
### Indicateurs visuels dans l'app
- Notification `memory-echo-notification.tsx` déclenché avec `type: 'clip'` : "Article clipé : [titre]" + bouton "Ouvrir"
- Badge `Source Web` visible dans `note-document-info-panel.tsx` onglet Infos (la logique `displayNoteType` doit inclure `'clip'`)
### Adaptation du prototype
- `ClipperSimulator.tsx` → remplacer les `MOCK_ARTICLES` et handlers locaux par les vrais appels API
- Conserver l'animation d'apparition (framer-motion), la structure de sélection de carnet et le layout du popup
- Supprimer `uuidv4` côté client (l'ID vient du serveur)
### Migration Prisma
```prisma
model Note {
sourceUrl String? // URL d'origine pour les clips web
}
```
---
## US-GRAPH — Graphe de Connaissance Global
**Contexte :**
`memento-note` a déjà `note-graph-view.tsx` (vue plein écran avec `react-force-graph-2d`) et `network-graph.tsx` (rendu D3 clusters). Le prototype `GraphKnowledgeMap.tsx` est plus avancé : filtres, recherche dans le graphe, liens sémantiques + wiki, navigation contextuelle. L'objectif est d'**enrichir `note-graph-view.tsx`** avec ces fonctionnalités supplémentaires.
**En tant qu'utilisateur**, je veux explorer visuellement toute ma base de connaissance sous forme de graphe interactif, avec la possibilité de filtrer par carnet, de chercher un nœud, d'afficher ou masquer les liens sémantiques, et de voir un aperçu d'une note en cliquant sur son nœud.
**Critères d'acceptation :**
### Panneau de contrôle latéral (nouveau dans `note-graph-view.tsx`)
- Toggle `Liens wiki` ON/OFF — masque/montre les arêtes `type: 'wikilink'`
- Toggle `Liens sémantiques` ON/OFF avec slider `Seuil de similarité` (0.30 → 0.90, pas 0.05)
- Multi-sélecteur de carnets (checkboxes colorées) — filtre les nœuds
- Input de recherche → met en surbrillance les nœuds correspondants et zoom vers eux
### Nœuds interactifs
- Taille du nœud proportionnelle au `degree` (nombre de connexions)
- Couleur du nœud = couleur du carnet (via `notebook.color` en BDD)
- Hover → tooltip `{titre} — {carnet} — {nb liens}`
- Clic → panneau latéral droit (slide-in 320px) avec : titre, extrait (200 chars), date, labels, bouton "Ouvrir la note"
- Double-clic → navigation directe vers la note (`router.push`)
### Performance
- API `GET /api/graph` existante retourne déjà `{ nodes, edges, clusters }` — l'utiliser directement
- Pour les graphes > 500 nœuds : mode `particle` (canvas optimisé)
- Cooldown de stabilisation = 3s, puis freeze physique automatique
### Navigation contextuelle
- Bouton "Explorer à partir de ce nœud" → re-centre avec voisins directs (1 hop) en avant, reste atténué
- Bouton "Réinitialiser la vue" → recentre sur tous les nœuds
### Adaptation du prototype
- Extraire la logique de filtrage de `GraphKnowledgeMap.tsx` → intégrer dans `note-graph-view.tsx`
- Les couleurs `CARNET_COLOR_PALETTE` hardcodées → remplacer par les couleurs réelles via `useNotebooks()`
- Les données mock → remplacer par l'API `GET /api/graph`
---
## US-LIVING-BLOCKS — Blocs Vivants (Transclusion Bidirectionnelle)
**Contexte :**
Le prototype `LivingBlock.tsx` utilise un `blockIndex` (numéro de ligne) pour référencer un bloc — **fragile** si du contenu est inséré avant. `BlockPicker.tsx` utilise la similarité Jaccard — **insuffisant**. `rich-text-editor.tsx` n'a pas l'extension `UniqueID` de TipTap.
**En tant qu'utilisateur**, je veux insérer dans ma note un "bloc vivant" issu d'une autre note, qui reste synchronisé en temps réel avec la source, peut être édité des deux côtés, et se détache proprement si la source est supprimée.
### 1. IDs stables pour chaque bloc (fondation technique)
*`rich-text-editor.tsx` — modification :*
- Ajouter `@tiptap/extension-unique-id` → chaque nœud de type `paragraph`, `heading`, `bulletList item`, `taskItem` reçoit un `data-id` UUID v4 stable
- Persister ces IDs dans `Note.content` (les attributs `data-id` sont conservés dans le HTML)
- Le backend `PUT /api/notes/[id]` doit sauvegarder le contenu tel quel (aucun strip des `data-id`)
### 2. Insertion d'un bloc vivant
*Commande slash dans l'éditeur :*
- Taper `/bloc` ou `/embed` → ouvre le `BlockPicker` (modal overlay)
- `BlockPicker` (adapter du prototype) :
- Onglet `Suggestions IA` : appel `GET /api/blocks/suggestions?noteId={id}` → 10 blocs les plus proches (pgvector cosine similarity)
- Onglet `Recherche` : input texte → `GET /api/blocks/search?q={query}`
- Onglet `Memory Echo` : affiche les `MemoryEchoInsight` déjà calculés avec bouton "Insérer ce bloc"
- Clic sur un bloc → insère un nœud TipTap custom `LiveBlock` dans l'éditeur
*Extension TipTap `LiveBlockExtension` (nouveau fichier `components/tiptap-live-block-extension.tsx`) :*
- Type de nœud : `liveBlock`, attributs : `{ sourceNoteId, blockId, snapshotContent }`
- Rendu : fond `blueprint/5`, bordure gauche `blueprint`, badge "LIVE" en coin supérieur droit
- Indicateurs : `wsConnected` (vert) / hors-ligne (ocre) / `isDeleted` (rose)
- Bouton "Détacher" → convertit en `paragraph` ordinaire avec le contenu actuel
- Bouton "Ouvrir la source" → `router.push` vers la note source
*Route API `POST /api/blocks/embed` (nouvelle) :*
- Body `{ sourceNoteId, blockId, targetNoteId }` → crée un enregistrement `LiveBlockRef` en BDD
### 3. Synchronisation temps réel (Redis Pub/Sub existant)
- Modification d'un bloc dans la note source → publier `block:update:{blockId}` avec le nouveau HTML
- La note cible écoute ce canal → met à jour le `snapshotContent` en temps réel
- Suppression de la source → publier `block:deleted:{blockId}` → état `isDeleted` côté cible
*Route API `GET /api/blocks/[blockId]/status` :*
- Retourne `{ exists: boolean, content: string, sourceNoteTitle: string }`
### Migration Prisma
```prisma
model LiveBlockRef {
id String @id @default(cuid())
sourceNoteId String
blockId String // data-id TipTap du bloc source
targetNoteId String
createdAt DateTime @default(now())
sourceNote Note @relation("SourceLiveBlocks", fields: [sourceNoteId], references: [id], onDelete: Cascade)
targetNote Note @relation("TargetLiveBlocks", fields: [targetNoteId], references: [id], onDelete: Cascade)
@@index([sourceNoteId, blockId])
@@index([targetNoteId])
}
```
### Bug corrigé vs prototype
`blockIndex` (numéro de ligne, fragile) → remplacé par `blockId` (UUID stable via TipTap UniqueID).
---
## US-STRUCTURED-VIEWS — Vues Structurées (Base de Données de Notes)
**Contexte :**
Aucun prototype n'existe pour cette feature. Inspiration : SiYuan Database Views et Notion. Permettre de transformer un carnet en "vue structurée" avec propriétés personnalisées et plusieurs modes d'affichage.
**En tant qu'utilisateur**, je veux visualiser un ensemble de notes sous forme de tableau, kanban ou galerie — avec des propriétés personnalisées filtrables et triables — comme une base de données légère intégrée dans mon carnet.
### 1. Activation et définition des propriétés
- Dans le header d'un carnet, icône `Table` → active le mode vue structurée
- Clic `+ Ajouter une propriété` → modal : nom, type (`Texte` / `Nombre` / `Date` / `Sélection` / `Sélection multiple` / `Case`)
- Les options de sélection sont définies à la création (ex. `Statut: À faire / En cours / Terminé`)
- Max 15 propriétés par carnet
- Stockées dans `NotebookSchema` / `NotebookProperty`
### 2. Saisie des valeurs par note
- Sidebar de l'éditeur, section "Propriétés"
- Saisie inline : date picker, input numérique, checkbox, dropdown
- Stockées dans `NoteProperty` (une ligne par note + propriété)
- Sauvegarde en debounce (500ms) via `PATCH /api/notes/[id]/properties`
### 3. Vue Tableau — `notes-structured-table.tsx` (nouveau)
- En-têtes = propriétés + `Titre` + `Date modif`
- Tri par colonne (clic en-tête) — ASC/DESC
- Filtre par colonne : `Contient`, `Est égal à`, `Est vide`
- Édition inline de la valeur directement dans la cellule
- Clic sur le titre → ouvre l'éditeur
### 4. Vue Kanban — `notes-kanban-view.tsx` (nouveau)
- Colonnes = valeurs d'une propriété `Sélection` (désignée "propriété de groupement")
- Drag & Drop entre colonnes (via `@dnd-kit/sortable`) → met à jour la propriété
- Bouton `+ Nouvelle note` dans chaque colonne → crée une note avec la valeur pré-remplie
### 5. Vue Galerie — `notes-gallery-view.tsx` (nouveau)
- Grille 3-4 colonnes responsive
- Carte : `illustrationSvg` (si dispo) ou couleur du carnet, titre, 2 propriétés
- Survol → aperçu des propriétés complètes
### 6. Navigation entre vues
- Toggle dans le header du carnet : `Liste` / `Tableau` / `Kanban` / `Galerie`
- Vue sélectionnée persistée dans `localStorage` (par carnet)
### Migration Prisma
```prisma
model NotebookSchema {
id String @id @default(cuid())
notebookId String @unique
notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade)
properties NotebookProperty[]
createdAt DateTime @default(now())
}
model NotebookProperty {
id String @id @default(cuid())
schemaId String
schema NotebookSchema @relation(fields: [schemaId], references: [id], onDelete: Cascade)
name String
type String // text | number | date | select | multiselect | checkbox
options String? // JSON array des options pour select/multiselect
position Int
noteValues NoteProperty[]
}
model NoteProperty {
id String @id @default(cuid())
noteId String
propertyId String
value String? // JSON selon le type
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
property NotebookProperty @relation(fields: [propertyId], references: [id], onDelete: Cascade)
@@unique([noteId, propertyId])
}
```
---
## US-FLASHCARDS — Révision IA par Répétition Espacée
**Contexte :**
Le prototype `RevisionView.tsx` contient une implémentation complète avec flip cards, évaluation, sessions et decks. Les types `Flashcard`, `FlashcardDeck`, `FlashcardEvaluation` y sont définis. Rien n'existe dans `memento-note`. Adapter avec un vrai algorithme SM-2 et une vraie persistance BDD.
**En tant qu'utilisateur**, je veux générer automatiquement des flashcards à partir de mes notes grâce à l'IA, puis les réviser en session de répétition espacée (algorithme SM-2) — et voir ma progression dans le temps.
### 1. Génération de flashcards par l'IA
*Menu action dans l'éditeur (icône `GraduationCap`) :*
- Bouton "Générer des flashcards" → `POST /api/flashcards/generate`
- Body : `{ noteId, count: 5-20 (slider), style: 'qa' | 'cloze' | 'concept' }`
- Types :
- `qa` : question/réponse directe
- `cloze` : phrase à trous (`"La capitale de la France est ___"`)
- `concept` : terme/définition
- Preview des flashcards générées avant confirmation (modal avec édition possible)
- Confirmation → sauvegarde en BDD dans le deck correspondant à la note
### 2. Organisation en decks
- Un `FlashcardDeck` créé automatiquement par carnet (`notebookId` → `deckId` 1:1)
- Deck manuel possible ("Créer un deck thématique")
- Vue `/decks` (ou onglet sidebar) : nom, nb cartes, nb à réviser aujourd'hui (badge rouge), dernière révision
### 3. Session de révision (adapter `RevisionView.tsx`)
- Écran d'accueil du deck : `Total: X / À réviser: Y / Maîtrisées: Z`
- Mode session : carte centrée avec flip 3D CSS
- 4 boutons d'évaluation : `Difficile (1)` / `Dur (2)` / `Bien (3)` / `Facile (4)`
- Fin de session : récapitulatif avec stats + graphe en barres (ChartExtension existante)
*Algorithme SM-2 (côté serveur) :*
```
easinessFactor = max(1.3, EF + 0.1 - (5 - grade) * (0.08 + (5 - grade) * 0.02))
nextInterval = previousInterval * easinessFactor
nextReview = now + nextInterval (en jours)
```
- Route `POST /api/flashcards/[id]/review` met à jour `nextReviewAt`, `interval`, `easinessFactor`
### 4. Dashboard de progression
- Heatmap de révision (vue calendrier) — composant `revision-heatmap.tsx`
- Courbe de rétention : % de cartes maîtrisées par semaine
- "Cartes difficiles" : les 5 cartes avec le `easinessFactor` le plus bas
### Migration Prisma
```prisma
model FlashcardDeck {
id String @id @default(cuid())
userId String
notebookId String?
name String
createdAt DateTime @default(now())
flashcards Flashcard[]
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: SetNull)
}
model Flashcard {
id String @id @default(cuid())
deckId String
noteId String?
front String
back String
type String @default("qa")
interval Int @default(1)
easinessFactor Float @default(2.5)
nextReviewAt DateTime @default(now())
createdAt DateTime @default(now())
deck FlashcardDeck @relation(fields: [deckId], references: [id], onDelete: Cascade)
note Note? @relation(fields: [noteId], references: [id], onDelete: SetNull)
reviews FlashcardReview[]
}
model FlashcardReview {
id String @id @default(cuid())
cardId String
grade Int // 1-4
reviewedAt DateTime @default(now())
card Flashcard @relation(fields: [cardId], references: [id], onDelete: Cascade)
}
```
---
## US-INFO-RÉSEAU — Panneau Info Document + Réseau Local
**Contexte :**
`note-document-info-panel.tsx` (407L) existe en production avec onglets `info` et `versions`. Le prototype `NotebookInfoSidebar.tsx` ajoute un onglet `Réseau` (graphe orbit SVG) avec des `explicitWikiLinks` **hardcodés** — bug critique. Il faut **ajouter l'onglet Réseau** au composant existant en production, en branchant les vraies données.
**En tant qu'utilisateur**, je veux voir depuis le panneau contextuel d'une note : ses métadonnées enrichies, ses versions avec diff, et son réseau local de connexions — le tout dans un espace cohérent.
### 1. Onglet Infos (enrichissement)
- Ajouter calcul `lineCount` = nombre de `\n` dans le contenu HTML nettoyé
- Ajouter calcul `equationCount` = regex `\$\$[\s\S]+?\$\$|\$[^$]+\$`
- Ajouter calcul `imageCount` = regex `
12 nœuds : afficher les 12 les plus connectés + badge `+N autres`
### Corrections critiques (vs prototype)
| Problème dans le prototype | Correction en production |
|---|---|
| `explicitWikiLinks` hardcodés | → API backlinks réelle + `NoteLink` BDD |
| `alert()` pour preview versions | → déjà corrigé en prod (`NoteHistoryModal`) |
| `window.confirm()` pour restauration | → déjà corrigé en prod (`AlertDialog`) |
| Dates relatives hardcodées | → déjà corrigé en prod (`date-fns`) |
---
## ~~US-SEARCH~~ — Voir section livrée ci-dessus
---
## US-MEMORY-ECHO — Panneau de Résonance Sémantique Intégré
**Contexte :**
`editor-connections-section.tsx` (255L) existe en production et appelle `GET /api/ai/echo/connections` (service pgvector complet). Mais il manque : le bouton **"Embedder ce passage"** (lien vers Living Blocks) et le panneau **Rétroliens** (quelles notes embarquent la note courante comme Living Block).
**En tant qu'utilisateur**, je veux voir en bas de chaque note le passage le plus sémantiquement proche de mes autres notes — avec la possibilité de l'insérer comme bloc vivant — et savoir quelles notes intègrent un extrait de ma note.
### 1. Bloc Memory Echo inline (enrichir `editor-connections-section.tsx`)
*Apparence (adapter prototype `NotebooksView.tsx` L1397–1436) :*
- Fond `gradient from-indigo-500/3`, bordure `indigo-500/10`
- Badge `XX% affinité sémantique` en mono (score = `Math.round(similarity * 100)%`)
- Icône `Sparkles` pulse, label `MEMORY ECHO` uppercase tracking-widest
- Citation en italic serif, bordure gauche `indigo-500/20`, 150 chars max
*Bouton "Voir la connexion" :*
- `router.push(/notes/[sourceNoteId])` — déjà présent, conserver
*Bouton "Embedder ce passage" (nouveau) :*
- Ouvre le `BlockPicker` (US-LIVING-BLOCKS) pré-rempli avec `{ sourceNoteId, blockId }`
- Fallback (si Living Blocks non encore implémenté) : copie le texte en bloc citation ordinaire
*Source des données :*
- `GET /api/ai/echo/connections?noteId={id}&limit=1` — **déjà en prod**
- Si aucune connexion → section masquée
### 2. Section Rétroliens Living Block (nouvelle)
*Logique :*
- `GET /api/notes/[id]/live-block-refs` (nouvelle route, utilise `LiveBlockRef` de US-LIVING-BLOCKS)
- Retourne les notes qui contiennent un `liveBlock` pointant sur la note courante
*Affichage :*
- Titre `RÉTROLIENS & INTÉGRATIONS SÉMANTIQUES` uppercase tracking-widest
- Badge animé bleu-accent (pulse)
- `Embeddé comme Living Block dans X note(s) :`
- Cartes cliquables : icône `Link2` + titre de la note hôte + carnet → `router.push`
- Si aucun rétrolien → section masquée
### 3. Connexions multiples
- Bloc Memory Echo affiche la **meilleure** connexion par défaut
- Bouton `Voir toutes les connexions (N)` → déplie la liste complète (réutilise `editor-connections-section.tsx`)
- Chaque connexion dans la liste a son propre bouton "Embedder ce passage"
### 4. Chargement
- Lazy loading : déclenché après 1.5s pour ne pas ralentir l'ouverture de la note
- Renommage : `editor-connections-section.tsx` → `memory-echo-section.tsx`
### Correction du prototype
- Prototype calcule `backlinks` en parsant le contenu texte de toutes les notes (fragile, lent)
- Production : utiliser `LiveBlockRef` en BDD via API (performant et fiable)
---
## US-INSIGHTS — Vue Clusters Sémantiques & Bridge Notes
**Contexte :**
`InsightsView.tsx` (482L) du prototype contient : clustering sémantique avec nommage IA, Bridge Notes, graphe réseau des clusters. En prod : `bridge-notes-dashboard.tsx` existe mais est séparé. À unifier en une page cohérente.
**En tant qu'utilisateur**, je veux une vue dédiée qui me montre comment mes notes se regroupent thématiquement, quelles notes font le pont entre clusters, et le tout visualisé dans un graphe interactif.
### Page `app/(main)/insights/page.tsx`
*Section Clusters Sémantiques :*
- Cartes colorées par cluster, nom généré par IA via `bridge-notes.service.ts` existant
- Liste des 5 notes les plus centrales par cluster
- Source : API `/api/clusters` existante
*Section Bridge Notes :*
- Intègre `bridge-notes-dashboard.tsx` existant + API `/api/bridge-notes`
- Suggestions de nouvelles Bridge Notes via `/api/bridge-notes/suggestions`
*Section Graphe Réseau :*
- Monte `note-graph-view.tsx` existant, filtré sur les clusters
- Toggle `vue graph` / `vue dashboard`
### Adaptation depuis `InsightsView.tsx`
- Remplacer `runClustering` / `geminiService` (mock) → API `/api/clusters`
- Remplacer `suggestBridgeIdeas` → API `/api/bridge-notes/suggestions`
- Conserver le layout dashboard et la logique de vue mobile/desktop
---
## US-TEMPORAL — Prédictions d'Accès Temporelles
> **Statut : ⏸️ REPORTÉ (2026-05-24)** — Chevauche rappels, révision SM-2 et Memory Echo ; faible signal sans volume d'ouvertures ; pas prioritaire pour éviter la surcharge produit. Spec conservée pour réévaluation ultérieure éventuelle.
**Contexte :**
`TemporalView.tsx` (169L) prédit quelles notes l'utilisateur voudra relire, basé sur des patterns d'accès. Rien n'existe en prod. Feature légère à fort impact perçu.
**En tant qu'utilisateur**, je veux voir dans la sidebar quelles notes je suis "sur le point" de vouloir relire — basé sur mes habitudes d'accès passées.
### Migration Prisma
```prisma
model NoteAccessLog {
id String @id @default(cuid())
noteId String
userId String
accessedAt DateTime @default(now())
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
@@index([noteId, accessedAt])
@@index([userId, accessedAt])
}
```
### Logging automatique
- `POST /api/notes/[id]/access` appelé à chaque ouverture de note (fire-and-forget)
### Route `GET /api/notes/temporal-predictions`
- Adapte la logique de `temporalService.ts` du prototype
- Retourne les `top 5` notes prédites : `{ noteId, title, predictedAt, confidence, cycleType }`
### Widget dans la sidebar
- Section "Notes à relire" avec icône `Clock`
- Liste des 3 meilleures prédictions
- Badge `cyclique` / `tendance` selon le type
- Clic → ouvre la note
---
## US-NEXTGEN-EDITOR — Éditeur Next-Gen : Drag Handle + Menu Bloc + DB Inline + Smart Paste
> **Status :** PLANIFIÉ
> **Depends on :** US-LIVING-BLOCKS (UniqueID et transclusion), US-STRUCTURED-VIEWS (NotebookSchema et propriétés)
> **Spec détaillée :** `docs/story-nextgen-editor.md` (4 sous-stories : US-1 Drag Handle, US-2 Menu Action Bloc, US-3 Smart Paste, US-4 Database Inline)
**Contexte :**
L'éditeur actuel est un document linéaire classique. Pour rivaliser avec Notion tout en étant plus performant, on implémente une approche hybride : un unique bouton de poignée en ProseMirror pur (pas de composant React lourd par paragraphe), un menu contextuel de bloc, une transclusion au collage, et un bloc database inline.
**4 sous-stories** (détail dans `docs/story-nextgen-editor.md`) :
### US-1 : Poignée de Glissement Gutter Unique (Hover Drag Handle)
- Bouton flottant unique dans la marge gauche, suit le curseur de bloc en bloc
- Un seul élément DOM repositionné (pas de duplication)
- Drag & drop de blocs avec indicateur de ligne d'insertion
- Masqué sur mobile/tactile
### US-2 : Menu Action Rapide de Bloc
- Clic sur la poignée -> dropdown glassmorphism
- Actions : Supprimer, Dupliquer, Transformer en (H1/H2/H3/liste/todo/citation/code/database), Copier la référence
- Utilise le `UniqueID` TipTap pour les références stables
### US-3 : Transclusion intelligente au Collage (Smart Paste)
- Collage d'un lien de bloc -> menu inline : "Bloc Connecté (Live)" ou "Texte simple"
- Insère un nœud `liveBlock` synchronisé via Redis Pub/Sub
### US-4 : Vue Structurée de Carnet Inline — *Redesign*
> ⚠️ Spec complète : [`docs/story-nextgen-editor-us4-redesign.md`](./story-nextgen-editor-us4-redesign.md)
- Slash `/vue` (+ keywords `database`, `db`, `tableau`) → insère un `structuredViewBlock`
- Affiche Table ou Galerie du carnet courant via l'API Structured Views existante
- Bloc stocke uniquement `notebookId` + `displayMode` (pas de données en attrs TipTap)
- Graceful fallback si carnet sans schéma ou note sans carnet
- **Supprime** le code legacy `tiptap-database-block-extension.tsx` (Verne/Liu Cixin)
- **Dépend de :** US-STRUCTURED-VIEWS ✅ (livré)
### Fichiers
- `[NEW]` `tiptap-drag-handle-plugin.ts` — Plugin ProseMirror pur (US-1)
- `[NEW]` `tiptap-structured-view-block-extension.tsx` — NodeView Vue Structurée (US-4 redesign)
- `[NEW]` `structured-view-block-embed.tsx` — Composant embed avec SWR + états dégradés
- `[DELETE]` `tiptap-database-block-extension.tsx` — Legacy Auteurs/Œuvres rejeté
- `[DELETE]` `database-block-editor.tsx` — UI legacy rejetée
- `[DELETE]` `lib/editor/database-block-types.ts` — Types legacy rejetés
- `[MODIFY]` `note-content-area.tsx` — Passer `notebookId` à l'éditeur
- `[MODIFY]` `rich-text-editor.tsx` — Swap extension DB → Vue Structurée, mise à jour slash
- `[MODIFY]` `block-action-menu.tsx` — Remplacer option "Database" par "Vue structurée"
- `[MODIFY]` `globals.css` — Gutter, poignée, glassmorphic dropdowns
---
## US-EDITOR-PERF — Performance de Frappe TipTap (Quick Wins)
> **Status :** PLANIFIÉ
> **Depends on :** rien (modifications config TipTap, ~2h)
> **Priorité :** HAUTE — impact immédiat sur le ressenti de saisie
> **Source recherche :** TipTap 2.5 (mai 2026), TipTap docs performance, PR #7828
**Contexte :**
Actuellement `rich-text-editor.tsx` utilise `immediatelyRender: false` mais pas `shouldRerenderOnTransaction` ni `useEditorState`. TipTap re-render le composant React à chaque transaction (frappe, déplacement curseur, sélection) — ce qui ajoute de la latence. Obsidian atteint <16ms de latence (local-first), Notion 50-150ms (cloud). Momento est local mais se comporte comme Notion à cause de ces re-renders inutiles.
**En tant qu'utilisateur**, je veux que la frappe dans l'éditeur soit instantanée, sans aucun décalage perceptible, même sur des notes longues avec de nombreux blocs.
### 1. shouldRerenderOnTransaction: false (1 ligne)
```typescript
// rich-text-editor.tsx — useEditor()
const editor = useEditor({
extensions: [...],
immediatelyRender: false,
shouldRerenderOnTransaction: false, // <-- AJOUTER
// ...
})
```
- Le composant React EditorContent ne se re-render plus sur chaque transaction
- Seul le DOM ProseMirror est mis à jour (ultra-rapide, natif)
- Gain mesurable : de ~50-100ms par frappe à <16ms
### 2. useEditorState pour la toolbar et les panels
```typescript
import { useEditorState } from '@tiptap/react'
// Au lieu de useEditor + editor.isActive() dans le render :
const { isBold, isItalic, isHeading } = useEditorState({
editor,
selector: (ctx) => ({
isBold: ctx.editor.isActive('bold'),
isItalic: ctx.editor.isActive('italic'),
isHeading: ctx.editor.isActive('heading'),
}),
})
```
- La toolbar et le panneau propriétés ne se re-rendent que quand leur slice d'état change
- Actuellement, tout le composant editor re-render à chaque frappe -> la toolbar aussi
### 3. Isoler l'éditeur dans un composant dédié
- Créer `NoteEditorCore` (composant wrapper) qui ne reçoit que les props strictement nécessaires
- Les re-renders du parent (note-panel, sidebar ouverture, etc.) ne doivent PAS propager dans l'éditeur
- React.memo sur le wrapper si besoin
### 4. NodeViews : trackNodeViewPosition: false par défaut
- Les NodeViews React (LiveBlock, Chart, DatabaseBlock) ne doivent pas se re-re-render quand seul leur position dans le document change
- TipTap PR #7828 (mai 2026) : shallow prop comparison + opt-in position tracking
- Vérifier que chaque NodeView utilise `stopEvent()`, `ignoreMutation()`, et ne déclenche pas de setState inutile
### 5. Vérification
- `console.count('editor render')` dans le composant éditeur pour mesurer le nombre de re-renders
- Objectif : 0 re-render React pendant la frappe pure (seul ProseMirror DOM bouge)
---
## US-EDITOR-UX — Micro-Interactions de Saisie
> **Status :** LIVRÉ
> **Depends on :** US-NEXTGEN-EDITOR (drag handle et menu bloc doivent être en place)
> **Source recherche :** Mintlify "22 UX improvements" (mai 2026), BlockNote v0.50, BlockNote v0.49
**Contexte :**
Après les quick wins performance (US-EDITOR-PERF) et le drag handle (US-NEXTGEN-EDITOR), il reste des micro-interactions qui font la différence entre un éditeur "correct" et un éditeur "agréable". Mintlify a listé 22 améliorations UX en mai 2026 — voici les plus pertinentes pour Momento.
**En tant qu'utilisateur**, je veux que chaque interaction courante (insérer un bloc, déplacer du contenu, transformer un format) soit fluide et intuitive, sans recourir à des raccourcis clavier obscurs.
### 1. Sélection globale de blocs (Haute priorité)
- Shift+clic pour sélectionner plusieurs blocs contigus
- Drag de sélection multi-blocs
- Actions groupées : supprimer, déplacer, transformer en masse
- Visuel : blocs sélectionnés avec fond accent/5, bordure accent/30
- **Inspiration :** Mintlify "Global Block Selection" — nettoyage de pages longues sans actions répétées
### 2. Slash menu redessiné (Haute priorité)
- Type-to-search avec catégories visuelles (Texte, Média, Données, Intégré, IA)
- Navigation clavier fluide (flèches + Entrée, Esc pour fermer)
- Épinglage des 3-5 commandes les plus utilisées en haut
- Description courte sous chaque item (ex. "Code" -> "Bloc de code avec coloration syntaxique")
- Preview visuel au survol pour les blocs complexes (tableau, database, chart)
- **Inspiration :** Mintlify slash menu redesign, BlockNote catégories
### 3. Placeholders contextuels par type de bloc (Moyenne priorité)
- Paragraphe vide : "Tapez / pour insérer un bloc..."
- Heading H1 vide : "Titre principal"
- Heading H2 vide : "Titre de section"
- TaskItem vide : "Ajouter une tâche"
- Bloc de code vide : "Code..."
- Bullet list vide : "Liste"
- **Inspiration :** BlockNote "Helpful placeholders"
### 4. Collage intelligent étendu (Moyenne priorité)
- Coller une URL HTTP(S) -> propose : lien hypertexte / intégration image / intégration vidéo
- Coller du code (détecté par caractères spéciaux {}[];) -> propose : bloc de code avec auto-détection langage
- Coller une image depuis le presse-papier -> upload direct (déjà en prod, vérifier la fluidité)
- Coller du HTML riche -> nettoyage intelligent (conserver structure, supprimer styles inline superflus)
- **Inspiration :** US-3 Smart Paste existe pour les blocs connectés — étendre au contenu générique
### 5. "Turn into" instant (Moyenne priorité)
- Raccourci clavier : sélectionner du texte + `Cmd+Shift+H` -> cycle H1 > H2 > H3 > paragraphe
- Via le menu bloc (US-2) : transformation instantanée sans flash ni re-render visible
- Conserver le contenu et les attributs (gras, liens, etc.) lors de la conversion
- **Inspiration :** Mintlify "Turn Blocks Into Anything"
### 6. Undo/redo visuel discret (Basse priorité)
- Toast subtil (2s) : "Action annulée" / "Action rétablie"
- Raccourci affiché dans le toast : "Cmd+Z pour annuler, Cmd+Shift+Z pour rétablir"
- Ne pas utiliser toast pour les actions normales — seulement undo/redo pour confirmer le feedback
---
## US-EDITOR-MOBILE — Expérience Tactile & Toolbar Mobile
> **Status :** LIVRÉ
> **Depends on :** US-NEXTGEN-EDITOR (drag handle existant)
> **Source recherche :** Notion mobile app, Obsidian mobile, benchmark 2026
**Contexte :**
L'éditeur fonctionne sur mobile mais l'expérience est dégradée : la bubble menu est trop petite pour les doigts, le drag handle est masqué (prévu) mais il n'y a pas d'alternative tactile, et les sélections longues sont douloureuses en contenteditable.
**En tant qu'utilisateur mobile**, je veux pouvoir éditer mes notes confortablement depuis mon téléphone ou tablette, avec des contrôles adaptés au tactile.
### 1. Toolbar mobile adaptée
- Remplacer la bubble menu desktop par une toolbar fixe en bas d'écran (viewport < 768px)
- Boutons 44x44px minimum (Apple HIG)
- 8 actions principales : Gras, Italique, Surligner, Lien, Liste, Titre, Code, Plus (menu étendu)
- Scroll horizontal si plus d'actions
- **Inspiration :** Notion mobile toolbar
### 2. Menu bloc tactile
- Pas de drag handle sur mobile (déjà prévu dans US-1)
- Alternative : swipe gauche sur un bloc -> reveal actions (supprimer, dupliquer, transformer)
- Ou : tap long sur un bloc -> menu contextuel mobile natif (action sheet iOS / bottom sheet Android)
- Bouton "Déplacer" dans le menu -> mode réorganisation avec poignées tactiles
### 3. Sélection de texte améliorée
- Les sélections longues en contenteditable sont frustrantes sur mobile
- Ajouter un bouton "Sélectionner tout le bloc" dans le menu bloc tactile
- Double-tap sélectionne le mot, triple-tap sélectionne le paragraphe (comportement natif iOS/Android, vérifier que TipTap ne l'écrase pas)
### 4. Performance mobile
- Les NodeViews React lourds (Chart, DatabaseBlock) doivent avoir un fallback léger sur mobile
- Désactiver les animations de la bubble menu sur mobile (prefers-reduced-motion)
- `immediatelyRender: false` déjà en place — bon pour le premier rendu mobile
---
## US-EDITOR-MARKDOWN — Rendu WYSIWYG Markdown Fidèle
> **Status :** À FAIRE (low priority)
> **Depends on :** rien (évaluation Milkdown)
> **Source recherche :** Milkdown v7.20, "Human Markdown" extension VSCode, round-trip byte-for-byte
**Contexte :**
Momento stocke les notes en HTML (TipTap). Mais les notes de type `markdown` existent aussi. Le problème classique : éditer en riche et voir le Markdown reformatté intégralement (indentations changées, lignes vides supprimées, `##` convertis en soulignements). Milkdown (11k+ stars, ProseMirror + remark) résout ce problème avec un round-trip byte-for-byte.
**En tant qu'utilisateur**, je veux que mes fichiers Markdown restent intacts quand je les édite en mode visuel — pas de diff parasite sur chaque modification.
### 1. Évaluation technique Milkdown
- Installer `@milkdown/core` + `@milkdown/preset-commonmark` + `@milkdown/preset-gfm`
- Benchmarker le round-trip : ouvrir un .md, éditer un paragraphe, sauvegarder, vérifier que seul ce paragraphe change dans le diff
- Comparer avec la solution actuelle (TipTap HTML storage) pour les notes markdown
- **Référence :** "Human Markdown" VSCode extension (Milkdown + byte-for-byte tests)
### 2. Mode d'édition dual (si Milkdown adopté)
- Toggle dans la toolbar : mode Visuel (WYSIWYG) / mode Brut (CodeMirror)
- Transition en <100ms, même position de scroll
- Le mode Visuel affiche le rendu live (titres, listes, tables, images, checkboxes cliquables)
- Le mode Brut affiche le Markdown source avec coloration syntaxique
- **Inspiration :** "Human Markdown" — Cmd+Shift+V pour basculer
### 3. Intégration avec les notes existantes
- Notes type `richtext` : inchangé (stockage HTML TipTap)
- Notes type `markdown` : mode dual avec Milkdown
- Détection automatique du type à l'ouverture
- Conversion possible : `richtext` -> `markdown` (export) et `markdown` -> `richtext` (import)
### 4. Support GFM complet
- Tables Markdown rendues comme de vrais tableaux éditables
- Task lists avec checkboxes cliquables
- Footnotes rendues inline
- Frontmatter YAML en carte collapsible en haut de document
- **Inspiration :** "Human Markdown" — GFM complet, Shiki pour le code