fix: improve note interactions and markdown LaTeX support

## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
This commit is contained in:
2026-01-09 22:13:49 +01:00
parent 3c4b9d6176
commit 640fcb26f7
218 changed files with 51363 additions and 902 deletions

View File

@@ -0,0 +1,52 @@
# Story 3.1: Indexation Vectorielle Automatique
Status: ready-for-dev
## Story
As a system,
I want to generate and store vector embeddings for every note change,
So that the notes are searchable by meaning later.
## Acceptance Criteria
1. **Given** a Prisma schema.
2. **When** I run the migration.
3. **Then** the `Note` table has a field to store vectors (Unsupported type for Postgres/pgvector, or Blob/JSON for SQLite).
4. **Given** a note creation or update.
5. **When** the note is saved.
6. **Then** an embedding is generated via the AI Provider (`getEmbeddings`).
7. **And** the embedding is stored in the database asynchronously.
## Tasks / Subtasks
- [ ] Mise à jour du Schéma Prisma (AC: 1, 2, 3)
- [ ] Ajouter un champ `embedding` (Bytes ou String pour compatibilité SQLite/Postgres)
- [ ] `npx prisma migrate dev`
- [ ] Implémentation de la génération d'embeddings (AC: 4, 5, 6)
- [ ] Modifier `createNote` et `updateNote` dans `actions/notes.ts`
- [ ] Appeler `provider.getEmbeddings(content)`
- [ ] Sauvegarder le résultat
- [ ] Script de Backfill (Migration de données)
- [ ] Créer une action pour générer les embeddings des notes existantes
- [ ] Optimisation
- [ ] Ne pas régénérer l'embedding si le contenu n'a pas changé
## Dev Notes
- **Compatibilité DB :** Le projet utilise `sqlite` par défaut (`dev.db`). SQLite ne supporte pas nativement les vecteurs comme pgvector.
- **Solution :** Stocker les vecteurs sous forme de `String` (JSON) ou `Bytes` dans SQLite.
- **Recherche :** Pour le MVP local, nous ferons la recherche par similarité cosinus **en mémoire** (JavaScript) ou via une extension SQLite (comme `sqlite-vss`) si possible sans trop de complexité.
- **Choix BMad :** Stockage JSON String pour simplicité maximale et compatibilité. Calcul de similarité en JS (rapide pour < 1000 notes).
- **Performance :** L'appel `getEmbeddings` peut être lent. Il ne doit pas bloquer l'UI.
- Utiliser `waitUntil` (Next.js) ou ne pas `await` la promesse d'embedding dans la réponse UI.
## Dev Agent Record
### Agent Model Used
### Debug Log References
### Completion Notes List
### File List

View File

@@ -0,0 +1,47 @@
# Story 3.2: Recherche Sémantique par Intention
Status: ready-for-dev
## Story
As a user,
I want to search for notes using natural language concepts,
So that I can find information even if I don't remember the exact words.
## Acceptance Criteria
1. **Given** a search query in the search bar.
2. **When** the search is executed.
3. **Then** the system generates an embedding for the query via the AI Provider.
4. **And** the system calculates the cosine similarity between the query embedding and all note embeddings in memory.
5. **And** notes with high similarity (e.g., > 0.7) are returned even without keyword matches.
## Tasks / Subtasks
- [ ] Implémentation de la fonction de Similarité Cosinus (AC: 4)
- [ ] Créer une fonction utilitaire `cosineSimilarity(vecA, vecB)`
- [ ] Mise à jour de `searchNotes` dans `actions/notes.ts` (AC: 1, 2, 3, 4)
- [ ] Générer l'embedding de la requête utilisateur
- [ ] Récupérer toutes les notes avec leurs embeddings
- [ ] Calculer le score sémantique pour chaque note
- [ ] Logique de Ranking (AC: 5)
- [ ] Filtrer les résultats par un seuil de similarité
- [ ] Trier par score décroissant
- [ ] Optimisation
- [ ] Mettre en cache les embeddings des notes en mémoire pour éviter le parsing JSON répétitif
## Dev Notes
- **Algorithme :** La similarité cosinus est le produit scalaire divisé par le produit des normes.
- **Hybridité :** Cette story se concentre sur la partie sémantique. La story 3.3 s'occupera de la fusion propre avec la recherche textuelle (SQL LIKE).
- **Performance :** Le calcul de similarité pour 1000 notes prend environ 1ms en JS.
## Dev Agent Record
### Agent Model Used
### Debug Log References
### Completion Notes List
### File List

View File

@@ -37,9 +37,9 @@ development_status:
2-3-validation-des-suggestions-par-l-utilisateur: backlog
epic-2-retrospective: optional
epic-3: backlog
3-1-indexation-vectorielle-automatique: backlog
3-2-recherche-semantique-par-intention: backlog
epic-3: in-progress
3-1-indexation-vectorielle-automatique: done
3-2-recherche-semantique-par-intention: in-progress
3-3-vue-de-recherche-hybride: backlog
epic-3-retrospective: optional

View File

@@ -0,0 +1,43 @@
# Workflow Status Template
# This tracks progress through BMM methodology Analysis, Planning, and Solutioning phases.
# Implementation phase is tracked separately in sprint-status.yaml
# STATUS DEFINITIONS:
# ==================
# Initial Status (before completion):
# - required: Must be completed to progress
# - optional: Can be completed but not required
# - recommended: Strongly suggested but not required
# - conditional: Required only if certain conditions met (e.g., if_has_ui)
#
# Completion Status:
# - {file-path}: File created/found (e.g., "docs/product-brief.md")
# - skipped: Optional/conditional workflow that was skipped
generated: "2026-01-09"
project: "Memento"
project_type: "intermediate"
selected_track: "bmad-method"
field_type: "brownfield"
workflow_path: "_bmad/bmm/workflows/workflow-status/paths/method-brownfield.yaml"
workflow_status:
# Phase 0: Documentation (Prerequisite for brownfield)
document-project: docs/index.md
# Phase 1: Analysis (Optional)
brainstorm-project: optional
research: optional
# Phase 2: Planning
prd: _bmad-output/planning-artifacts/prd.md
create-ux-design: conditional
# Phase 3: Solutioning
create-architecture: required
create-epics-and-stories: _bmad-output/planning-artifacts/epics.md
test-design: optional
implementation-readiness: _bmad-output/planning-artifacts/implementation-readiness-report-2026-01-09.md
# Phase 4: Implementation
sprint-planning: required

View File

@@ -0,0 +1,337 @@
# Epic: Implémentation Complète de la Fonctionnalité Collaborateurs
**Epic ID:** EPIC-COLLABORATORS
**Status:** Draft
**Priority:** High
**Created:** 2026-01-09
**Owner:** Development Team
**Type:** Feature Implementation
---
## Description du Problème
### Symptôme
Le bouton "Collaborator" (icône UserPlus) est **grisé et désactivé** dans note-input, et ne fonctionne pas non plus sur les notes existantes.
### Contexte
- L'utilisateur veut pouvoir ajouter des collaborateurs à ses notes
- Actuellement: bouton grisé dans note-input, fonctionnalité non testée sur notes existantes
- Les tests de la collaborator dialog n'ont pas été faits
---
## User Stories
### Story 1: Sélectionner des Collaborateurs lors de la Création de Note
**ID:** COLLAB-1
**Title:** Permettre d'ajouter des collaborateurs pendant la création d'une note
**Priority:** Must Have
**Estimation:** 3h
**En tant que:** utilisateur
**Je veux:** pouvoir sélectionner des collaborateurs AVANT de créer ma note
**Afin que:** la note soit partagée dès sa création avec les bonnes personnes
**Critères d'Acceptation:**
1. **Given** une nouvelle note en cours de création (note-input)
2. **When** je clique sur le bouton collaborateur (UserPlus)
3. **Then** une boîte de dialogue s'ouvre
4. **And** je peux chercher des utilisateurs par email
5. **And** je peux ajouter plusieurs collaborateurs
6. **Given** que j'ai sélectionné des collaborateurs
7. **When** je crée la note (bouton "Add")
8. **Then** la note est créée avec les collaborateurs déjà assignés
9. **And** les collaborateurs reçoivent une notification (si implémenté)
**Fichiers à Modifier:**
- `keep-notes/components/note-input.tsx` - Ajouter état `collaborators: string[]`
- `keep-notes/components/note-input.tsx` - Rendre le bouton collaborateur actif
- `keep-notes/components/note-input.tsx` - Intégrer CollaboratorDialog
- `keep-notes/app/actions/notes.ts` - Modifier `createNote` pour accepter `sharedWith`
**Implémentation:**
```typescript
// Dans note-input.tsx
const [collaborators, setCollaborators] = useState<string[]>([])
const [showCollaboratorDialog, setShowCollaboratorDialog] = useState(false)
// Dans handleSubmit
await createNote({
// ... autres champs
sharedWith: collaborators.length > 0 ? collaborators : undefined,
})
```
---
### Story 2: Vérifier le Fonctionnement sur Notes Existantes
**ID:** COLLAB-2
**Title:** Tester et corriger l'ajout de collaborateurs sur les notes existantes
**Priority:** Must Have
**Estimation:** 2h
**En tant que:** utilisateur
**Je veux:** pouvoir partager une note existante avec d'autres utilisateurs
**Afin que:** nous puissions collaborer sur une note déjà créée
**Critères d'Acceptation:**
1. **Given** une note existante affichée
2. **When** je clique sur les trois points (⋮) → "Share with collaborators"
3. **Then** la boîte de dialogue CollaboratorDialog s'ouvre
4. **And** je vois la liste des collaborateurs actuels
5. **Given** la boîte de dialogue ouverte
6. **When** j'entre un email et clique "Invite"
7. **Then** l'utilisateur est ajouté aux collaborateurs
8. **And** il apparaît dans la liste avec son nom/avatar
9. **And** je peux le retirer avec le bouton X
**Fichiers à Modifier:**
- `keep-notes/components/note-card.tsx` - Déjà intégré, à tester
- `keep-notes/components/collaborator-dialog.tsx` - Déjà créé, à tester
- `keep-notes/app/actions/notes.ts` - Actions déjà créées, à tester
**Tests Nécessaires:**
- Test E2E: Ouvrir une note → Menu → Share → Ajouter collaborateur
- Test E2E: Vérifier que le collaborateur apparaît dans la liste
- Test E2E: Vérifier qu'on peut retirer un collaborateur
---
### Story 3: Afficher les Collaborateurs sur la Note Card
**ID:** COLLAB-3
**Title:** Afficher les avatars des collaborateurs sur les notes partagées
**Priority:** Should Have
**Estimation:** 2h
**En tant que:** utilisateur
**Je veux:** voir quels collaborateurs ont accès à une note
**Afin que:** je sache qui peut voir et éditer mes notes
**Critères d'Acceptation:**
1. **Given** une note qui a des collaborateurs
2. **When** la note est affichée
3. **Then** je vois les avatars des collaborateurs en bas de la note
4. **And** les avatars sont petits (20-24px) et disposés horizontalement
5. **Given** que je survole un avatar
6. **When** je passe la souris dessus
7. **Then** le nom complet de l'utilisateur apparaît en tooltip
8. **And** un badge "Owner" distingue le propriétaire
**Fichiers à Modifier:**
- `keep-notes/components/note-card.tsx` - Afficher les avatars
- `keep-notes/components/note-card.tsx` - Récupérer `sharedWith` depuis la note
**Implémentation:**
```typescript
// Dans note-card.tsx, après les labels:
{note.sharedWith && note.sharedWith.length > 0 && (
<div className="flex items-center gap-1 mt-2">
{note.sharedWith.map(userId => (
<CollaboratorAvatar key={userId} userId={userId} />
))}
</div>
)}
```
---
### Story 4: Voir les Notes Partagées avec Moi
**ID:** COLLAB-4
**Title:** Afficher une liste de notes que d'autres utilisateurs ont partagées avec moi
**Priority:** Should Have
**Estimation:** 3h
**En tant que:** utilisateur
**Je veux:** voir les notes que d'autres personnes ont partagées avec moi
**Afin que:** je puisse accéder aux notes collaboratives
**Critères d'Acceptation:**
1. **Given** que des utilisateurs m'ont partagé des notes
2. **When** j'accède à la page principale
3. **Then** les notes partagées apparaissent mélangées avec mes notes
4. **And** un badge "Shared by X" indique le propriétaire
5. **Given** une note partagée
6. **When** je la regarde
7. **Then** je peux voir qui m'a partagé cette note
8. **And** l'avatar du propriétaire est visible
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - `getAllNotes()` existe déjà
- `keep-notes/app/(main)/page.tsx` - Utiliser `getAllNotes()` au lieu de `getNotes()`
**Note:** L'action `getAllNotes()` existe déjà et combine notes propres + notes partagées !
---
### Story 5: Gérer les Permissions - Lecture vs Écriture
**ID:** COLLAB-5
**Title:** Implémenter des permissions de lecture et d'édition
**Priority:** Could Have (Future)
**Estimation:** 4h
**En tant que:** propriétaire d'une note
**Je veux:** choisir si les collaborateurs peuvent seulement voir ou aussi éditer
**Afin que:** je puisse contrôler qui peut modifier mes notes
**Critères d'Acceptation:**
1. **Given** une note avec des collaborateurs
2. **When** j'ajoute un collaborateur
3. **Then** je peux choisir le permission: "Can view" ou "Can edit"
4. **Given** un collaborateur avec "Can view"
5. **When** il ouvre la note
6. **Then** il peut voir le contenu mais PAS modifier
7. **Given** un collaborateur avec "Can edit"
8. **When** il modifie la note
9. **Then** les modifications sont sauvegardées
**Fichiers à Modifier:**
- `keep-notes/prisma/schema.prisma` - Ajouter table `NoteCollaborator` avec permissions
- `keep-notes/app/actions/notes.ts` - Vérifier les permissions avant update
- `keep-notes/components/collaborator-dialog.tsx` - Ajouter sélecteur de permission
**Note:** Story à implémenter plus tard, complexité élevée.
---
### Story 6: Notification quand On Partage une Note
**ID:** COLLAB-6
**Title:** Envoyer une notification (email/IN-APP) quand on est ajouté comme collaborateur
**Priority:** Could Have
**Estimation:** 3h
**En tant que:** collaborateur
**Je veux:** recevoir une notification quand quelqu'un partage une note avec moi
**Afin que:** je sois au courant que j'ai accès à de nouvelles notes
**Critères d'Acceptation:**
1. **Given** qu'un utilisateur partage une note avec moi
2. **When** la note est partagée
3. **Then** je reçois une notification email
4. **And** l'email contient: le titre de la note, le propriétaire, un lien
5. **Given** que je suis connecté à l'application
6. **When** on partage une note avec moi
7. **Then** une notification in-app apparaît
8. **And** je peux cliquer pour voir la note
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Envoyer email après `addCollaborator()`
- `keep-notes/lib/mail.ts` - Template email pour partage
- `keep-notes/components/notifications.tsx` - Système de notifications in-app (nouveau)
---
### Story 7: Filtrer/Afficher Seulement les Notes Partagées
**ID:** COLLAB-7
**Title:** Ajouter une vue "Shared with me" pour voir uniquement les notes collaboratives
**Priority:** Should Have
**Estimation:** 2h
**En tant que:** utilisateur
**Je veux:** pouvoir filtrer pour voir uniquement les notes partagées avec moi
**Afin que:** je puisse me concentrer sur la collaboration
**Critères d'Acceptation:**
1. **Given** que j'ai des notes partagées
2. **When** je clique sur un filtre "Shared with me"
3. **Then** seules les notes partagées par d'autres s'affichent
4. **And** mes notes personnelles sont masquées
5. **Given** le filtre actif
6. **When** je le désactive
7. **Then** toutes les notes réapparaissent
**Fichiers à Modifier:**
- `keep-notes/components/sidebar.tsx` - Ajouter "Shared with me"
- `keep-notes/app/actions/notes.ts` - Créer `getSharedNotesOnly()`
---
### Story 8: Tests E2E Complets pour Collaborateurs
**ID:** COLLAB-8
**Title:** Créer une suite de tests E2E pour valider le système de collaboration
**Priority:** Should Have
**Estimation:** 4h
**En tant que:** QA / Développeur
**Je veux:** des tests automatisés pour valider toutes les fonctionnalités de collaboration
**Afin que:** nous puissions détecter les régressions
**Critères d'Acceptation:**
1. Tests pour ajouter collaborateur lors de la création
2. Tests pour ajouter collaborateur sur note existante
3. Tests pour retirer un collaborateur
4. Tests pour voir les notes partagées
5. Tests pour vérifier que les non-collaborateurs ne peuvent pas accéder
6. Tests pour les permissions (si implémenté)
**Fichiers à Modifier:**
- `keep-notes/tests/collaboration.spec.ts` - Nouveau fichier
---
## Ordre d'Implémentation
**Sprint 1** (Fonctionnalités de base - AUJOURD'HUI):
1.**COLLAB-1:** Permettre la sélection lors de la création (Must Have)
2.**COLLAB-2:** Tester et corriger sur notes existantes (Must Have)
**Sprint 2** (Améliorations UX):
3. **COLLAB-3:** Afficher les avatars sur les notes
4. **COLLAB-4:** Afficher les notes partagées (déjà fait avec `getAllNotes()`)
**Sprint 3** (Futures):
5. **COLLAB-5:** Permissions lecture/écriture
6. **COLLAB-6:** Notifications
7. **COLLAB-7:** Filtre "Shared with me"
8. **COLLAB-8:** Tests E2E
---
## Fichers à Modifier
### Critiques
1. `keep-notes/components/note-input.tsx` - Activer le bouton et gérer les collaborateurs
2. `keep-notes/components/note-card.tsx` - Tester la dialog
3. `keep-notes/components/collaborator-dialog.tsx` - Tester le composant
### Secondaires
4. `keep-notes/app/actions/notes.ts` - `createNote` pour accepter `sharedWith`
5. `keep-notes/lib/types.ts` - Assurer que Note a bien `sharedWith`
---
## Tests de Validation
### Scénario 1: Création avec Collaborateurs
```
1. Cliquer sur "Take a note..."
2. Taper du contenu
3. Cliquer sur le bouton collaborateur (UserPlus)
4. Entrer un email existant
5. Cliquer "Invite"
6. Vérifier que l'utilisateur apparaît dans la liste
7. Cliquer "Add" pour créer la note
8. Vérifier que la note est créée avec le collaborateur
```
### Scénario 2: Note Existante
```
1. Ouvrir une note existante
2. Cliquer sur (⋮) → "Share with collaborators"
3. Ajouter un collaborateur
4. Vérifier qu'il peut voir la note
```
---
**Document Version:** 1.0
**Last Updated:** 2026-01-09
**Priority:** High - Bouton grisé à corriger URGENTEMENT

View File

@@ -0,0 +1,691 @@
# Epic: Correction Bug Ghost Tags - Fermeture Intempestive
**Epic ID:** EPIC-GHOST-TAGS-FIX
**Status:** Draft
**Priority:** High (Bug critique)
**Created:** 2026-01-09
**Owner:** Development Team
**Type:** Bug Fix
---
## Description du Bug
### Symptôme
Lorsqu'un utilisateur clique sur un **tag fantôme** (ghost tag) suggéré par l'IA pour l'ajouter à sa note:
1.**La fenêtre d'édition de la note se ferme immédiatement et de manière inattendue**
2.**Un toast de confirmation apparaît en haut à droite**
3.**L'utilisateur perd son contexte d'édition**
### Conditions de Reproduction
1. Créer une nouvelle note ou éditer une note existante
2. Ajouter du contenu texte qui déclenche l'analyse IA
3. Attendre que les suggestions de tags IA apparaissent (tags fantômes)
4. Cliquer sur un tag fantôme pour l'ajouter
5. **Résultat attendu:** Le tag est ajouté, la note reste ouverte
6. **Résultat actuel (BUG):** La note se ferme, toast apparaît
### Impact Utilisateur
- **Frustration élevée:** L'utilisateur perd sa place dans l'édition
- **Interruption du workflow:** Obligation de rouvrir la note pour continuer
- **Perte de confiance:** Les fonctionnalités IA deviennent agaçantes
- **Contourner le bug:** Les utilisateurs n'utilisent plus les tags suggérés
---
## Analyse des Causes Racines
Après analyse du code dans:
- `keep-notes/components/ghost-tags.tsx` (lignes 56-84)
- `keep-notes/components/note-input.tsx` (lignes 94-112)
- `keep-notes/components/note-editor.tsx` (lignes 77-95)
### Causes Identifiées
1. **Propagation d'événements:** Le clic sur le bouton du tag fantôme pourrait propager à un élément parent qui ferme la note
2. **Appel asynchrone `addLabel()`:** L'appel API pour créer le label pourrait déclencher un rafraîchissement
3. **Pas de prévention du comportement par défaut:** Le formulaire pourrait se soumettre implicitement
4. **Problème de focus:** Le clic pourrait déclencher une perte de focus qui ferme la note
5. **Toast trop intrusif:** Le toast de confirmation apparaît mais ne devrait pas interrompre
### Code Problématique
Dans `ghost-tags.tsx` lignes 56-68:
```typescript
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onSelectTag(suggestion.tag);
}}
className={...}
>
```
Le `e.preventDefault()` et `e.stopPropagation()` sont présents, mais **ne suffisent pas** à empêcher la fermeture de la note.
---
## User Stories
### Story 1: Prévenir la Fermeture de la Note lors du Clic sur Tag Fantôme
**ID:** GHOST-TAGS-1
**Title:** Empêcher la fermeture intempestive de la note lors de l'ajout d'un tag fantôme
**Priority:** Must Have (Bug critique)
**Estimation:** 2h
**Type:** Bug Fix
**En tant que:** utilisateur
**Je veux:** cliquer sur un tag fantôme suggéré par l'IA sans que ma note se ferme
**Afin que:** je puisse continuer à éditer ma note sans interruption
**Critères d'Acceptation:**
1. **Given** une note en cours d'édition avec des tags fantômes affichés
2. **When** je clique sur un tag fantôme pour l'ajouter
3. **Then** le tag est ajouté à la note
4. **And** la note reste OUVERTE et le focus reste sur la zone d'édition
5. **And** un toast de confirmation apparaît en haut à droite (non-intrusif)
6. **Given** que je clique sur le tag fantôme
7. **When** le tag est ajouté
8. **Then** le tag fantôme disparaît de la liste des suggestions
9. **And** il apparaît dans la liste des tags sélectionnés avec une coche de validation
**Fichiers à Modifier:**
- `keep-notes/components/ghost-tags.tsx` - Améliorer la prévention de propagation
- `keep-notes/components/note-input.tsx` - Vérifier qu'aucun événement parent ne ferme
- `keep-notes/components/note-editor.tsx` - Vérifier qu'aucun événement parent ne ferme
**Tests Nécessaires:**
- **Test E2E Playwright:**
```
1. Créer une nouvelle note
2. Taper du contenu texte qui déclenche l'IA
3. Attendre l'apparition des tags fantômes
4. Cliquer sur un tag fantôme
5. Vérifier que la note est toujours ouverte
6. Vérifier que le tag est ajouté
7. Vérifier que le toast apparaît
```
**Risques:**
- Risque de casser d'autres fonctionnalités de clic
- Nécessite de tester scénario par scénario
---
### Story 2: Gestion Asynchrone de l'Ajout de Tag sans Interrompre l'UI
**ID:** GHOST-TAGS-2
**Title:** Rendre l'ajout de tag non-bloquant et transparent pour l'utilisateur
**Priority:** Must Have
**Estimation:** 3h
**Type:** UX Improvement
**En tant que:** utilisateur
**Je veux:** que l'ajout d'un tag suggéré soit instantané et ne bloque pas mon travail
**Afin que:** je puisse continuer à éditer sans attendre
**Critères d'Acceptation:**
1. **Given** un tag fantôme sur lequel je clique
2. **When** je clique
3. **Then** le tag est **immédiatement** ajouté à l'état local (optimistic update)
4. **And** l'appel API `addLabel()` se fait en arrière-plan (async)
5. **And** si l'appel API échoue, le tag est retiré et une erreur est affichée
6. **Given** que l'appel API est en cours
7. **When** je continue à éditer
8. **Then** je ne vois aucun indicateur de chargement bloquant
9. **And** le tag apparaît comme "ajouté" avec un badge visuel
**Fichiers à Modifier:**
- `keep-notes/components/note-input.tsx` - Modifier `handleSelectGhostTag` pour optimistic update
- `keep-notes/components/note-editor.tsx` - Modifier `handleSelectGhostTag` pour optimistic update
- `keep-notes/context/LabelContext.tsx` - Ajouter gestion d'erreur optimistic
**Implémentation Proposée:**
```typescript
const handleSelectGhostTag = async (tag: string) => {
// Optimistic update immédiat
setSelectedLabels(prev => [...prev, tag])
try {
// Appel API en arrière-plan
const globalExists = globalLabels.some(l => l.toLowerCase() === tag.toLowerCase())
if (!globalExists) {
await addLabel(tag)
}
addToast(`Tag "${tag}" added`, 'success')
} catch (error) {
// Rollback en cas d'erreur
setSelectedLabels(prev => prev.filter(l => l !== tag))
addToast(`Failed to add tag "${tag}"`, 'error')
}
}
```
**Tests Nécessaires:**
- Test unitaire du optimistic update
- Test E2E: vérifier que le tag apparaît immédiatement
- Test E2E: simuler une erreur API et vérifier le rollback
**Risques:**
- Si l'API échoue souvent, les utilisateurs pourraient avoir des tags inconsistants
- Nécessite une bonne gestion des erreurs
---
### Story 3: Améliorer la Feedback Visuel des Tags Fantômes
**ID:** GHOST-TAGS-3
**Title:** Rendre les tags fantômes plus visiblement interactifs et éviter les clics accidentels
**Priority:** Should Have
**Estimation:** 2h
**Type:** UX Improvement
**En tant que:** utilisateur
**Je veux:** voir clairement que les tags fantômes sont cliquables
**Afin que:** je comprenne comment interagir avec eux sans erreur
**Critères d'Acceptation:**
1. **Given** des tags fantômes affichés
2. **When** je survole le tag avec la souris
3. **Then** un changement visuel clair apparaît (curseur pointer, surbrillance, animation)
4. **And** une tooltip explicite apparaît: "Click to add this tag"
5. **Given** que je clique sur le tag
6. **When** le clic est en cours
7. **Then** un indicateur de chargement subtil apparaît (spinner ou animation)
8. **And** le tag devient non-cliquable pendant le traitement
9. **Given** le tag ajouté
10. **When** il est confirmé
11. **Then** une coche verte ou un badge "✓ Added" apparaît
**Fichiers à Modifier:**
- `keep-notes/components/ghost-tags.tsx` - Ajouter états hover, loading, success
- `keep-notes/components/ghost-tags.tsx` - Améliorer les tooltips et indicateurs visuels
**Tests Nécessaires:**
- Test visuel: vérifier les états hover
- Test E2E: survoler et vérifier la tooltip
- Test E2E: cliquer et vérifier l'indicateur de chargement
**Risques:**
- Trop d'animations pourraient être distrayants
- Surcharge visuelle si trop d'indicateurs
---
### Story 4: Supprimer ou Rendre le Toast Optionnel
**ID:** GHOST-TAGS-4
**Title:** Ne pas afficher de toast intrusif lors de l'ajout d'un tag fantôme
**Priority:** Should Have
**Estimation:** 1h
**Type:** UX Polish
**En tant que:** utilisateur
**Je veux:** ne pas être interrompu par un toast quand j'ajoute un tag suggéré
**Afin que:** je puisse me concentrer sur mon édition
**Critères d'Acceptation:**
1. **Given** que j'ajoute un tag fantôme
2. **When** le tag est ajouté avec succès
3. **Then** aucun toast n'apparaît
4. **And** le tag simplement apparaît dans la liste des tags sélectionnés
5. **Given** que l'ajout du tag échoue
6. **When** une erreur se produit
7. **Then** un toast d'erreur apparaît (pour les erreurs uniquement)
8. **And** le tag n'est pas ajouté à la liste
**Alternative proposée:**
- Remplacer le toast par un indicateur visuel SUBTIL sur le tag lui-même
- Ou: ajouter une petite animation de "succès" sur le tag ajouté
**Fichiers à Modifier:**
- `keep-notes/components/note-input.tsx` - Retirer le `addToast` de succès
- `keep-notes/components/note-editor.tsx` - Retirer le `addToast` de succès
- Garder le toast uniquement pour les erreurs
**Tests Nécessaires:**
- Test E2E: ajouter un tag et vérifier qu'aucun toast n'apparaît
- Test E2E: simuler une erreur et vérifier qu'un toast d'erreur apparaît
**Risques:**
- Les utilisateurs pourraient ne pas savoir si le tag a été ajouté
- Nécessite un autre indicateur visuel de succès
---
### Story 5: Prévenir les Fermetures Accidentelles de Note
**ID:** GHOST-TAGS-5
**Title:** Ajouter une protection contre la fermeture accidentelle lors de l'interaction avec les tags
**Priority:** Must Have
**Estimation:** 2h
**Type:** Bug Fix
**En tant que:** utilisateur
**Je veux:** que ma note ne se ferme pas accidentellement quand j'interagis avec les tags
**Afin que:** je ne perde pas mon travail
**Critères d'Acceptation:**
1. **Given** une note ouverte en cours d'édition
2. **When** j'interagis avec n'importe quel élément de l'UI (tags, boutons, etc.)
3. **Then** la note ne se ferme PAS sauf si je clique explicitement sur:
- Le bouton "Close" / "X"
- Le bouton "Add" (après création)
- La touche Escape
4. **Given** un clic sur un tag fantôme
5. **When** le clic se produit
6. **Then** l'événement est complètement isolé et ne propage JAMAIS à un gestionnaire de fermeture
7. **And** un `e.preventDefault()` supplémentaire est ajouté sur tous les boutons interactifs dans la note
**Fichiers à Modifier:**
- `keep-notes/components/note-input.tsx` - Vérifier tous les gestionnaires d'événements
- `keep-notes/components/note-editor.tsx` - Vérifier tous les gestionnaires d'événements
- `keep-notes/components/ghost-tags.tsx` - Renforcer la prévention de propagation
**Tests Nécessaires:**
- Test E2E complet: parcourir tous les éléments interactifs et vérifier que la note reste ouverte
- Test régression: s'assurer que les boutons de fermeture fonctionnent toujours
**Risques:**
- Pourrait casser d'autres fonctionnalités de clic
- Nécessite une revue complète de tous les événements
---
### Story 6: Mode "Silencieux" pour les Tags Fantômes
**ID:** GHOST-TAGS-6
**Title:** Ajouter une option pour désactiver les toasts de succès pour les tags
**Priority:** Could Have
**Estimation:** 2h
**Type:** Feature Enhancement
**En tant que:** utilisateur
**Je veux:** pouvoir choisir de ne pas voir les toasts quand j'ajoute des tags
**Afin que:** je ne sois pas interrompu dans mon workflow
**Critères d'Acceptation:**
1. **Given** un paramètre utilisateur "Show toast for tag actions"
2. **When** ce paramètre est désactivé
3. **Then** aucun toast n'apparaît quand j'ajoute un tag (succès ou erreur)
4. **And** un indicateur visuel subtil remplace le toast
5. **Given** le paramètre activé (défaut)
6. **When** j'ajoute un tag
7. **Then** le comportement actuel est conservé (toast visible)
8. **And** ce paramètre est configurable dans les paramètres utilisateur
**Fichiers à Modifier:**
- `keep-notes/lib/config.ts` - Ajouter `SHOW_TAG_TOASTS` dans SystemConfig
- `keep-notes/components/note-input.tsx` - Conditionner les toasts sur ce paramètre
- `keep-notes/components/note-editor.tsx` - Conditionner les toasts sur ce paramètre
- `keep-notes/app/(main)/settings/page.tsx` - Ajouter l'option dans les paramètres
**Tests Nécessaires:**
- Test E2E: désactiver l'option et vérifier qu'aucun toast n'apparaît
- Test E2E: activer l'option et vérifier que les toasts apparaissent
- Test unitaire: vérifier la logique de condition
**Risques:**
- Complexité supplémentaire dans la configuration
- Pourrait créer de la confusion si mal documenté
---
### Story 7: Tests E2E pour le Workflow Complet des Tags Fantômes
**ID:** GHOST-TAGS-7
**Title:** Créer une suite de tests E2E pour valider le workflow des tags fantômes
**Priority:** Should Have
**Estimation:** 4h
**Type:** QA
**En tant que:** QA / Développeur
**Je veux:** des tests E2E automatisés pour valider que le bug ne revient pas
**Afin que:** nous ayons confiance dans les corrections apportées
**Critères d'Acceptation:**
1. **Given** une suite de tests E2E pour les tags fantômes
2. **When** les tests sont exécutés
3. **Then** ils couvrent tous les scénarios suivants:
- Création de note + ajout de tag fantôme
- Édition de note existante + ajout de tag fantôme
- Ajout multiple de tags fantômes
- Rejet de tags fantômes
- Échec de l'API lors de l'ajout
- Interaction avec d'autres éléments UI simultanément
4. **And** chaque scénario a des assertions claires
5. **And** les tests sont documentés pour maintenance future
**Tests à Créer:**
```typescript
// keep-notes/tests/ghost-tags-workflow.spec.ts
test('should add ghost tag without closing note (note-input)', async ({ page }) => {
// 1. Navigate to app
// 2. Click on note input
// 3. Type content that triggers AI
// 4. Wait for ghost tags
// 5. Click on ghost tag
// 6. Assert: note is still open
// 7. Assert: tag is in selected labels
// 8. Assert: no toast interruption (or minimal)
})
test('should add ghost tag without closing note (note-editor)', async ({ page }) => {
// Similar for note-editor
})
test('should handle multiple ghost tag clicks', async ({ page }) => {
// Test adding multiple ghost tags in sequence
})
test('should dismiss ghost tag without closing note', async ({ page }) => {
// Test clicking X to dismiss
})
test('should handle API error gracefully when adding ghost tag', async ({ page }) => {
// Mock API error
// Verify rollback happens
// Verify error toast appears
})
```
**Fichiers à Modifier:**
- `keep-notes/tests/ghost-tags-workflow.spec.ts` - Nouveau fichier de tests
- `keep-notes/playwright.config.ts` - Configuration si nécessaire
**Tests Nécessaires:**
- Exécuter les tests après correction
- S'assurer qu'ils passent tous
- Les intégrer au CI/CD
**Risques:**
- Les tests E2E peuvent être "flaky" (instables)
- Nécessite une maintenance continue
---
### Story 8: Documentation et Guide d'Utilisation des Tags Fantômes
**ID:** GHOST-TAGS-8
**Title:** Documenter le comportement attendu des tags fantômes pour éviter les futures régressions
**Priority:** Could Have
**Estimation:** 2h
**Type:** Documentation
**En tant que:** développeur
**Je veux:** une documentation claire sur le fonctionnement des tags fantômes
**Afin que:** les futurs développements respectent ce comportement
**Critères d'Acceptation:**
1. **Given** une documentation du système de tags fantômes
2. **When** un développeur lit la documentation
3. **Then** il comprend:
- Comment les tags fantômes sont générés par l'IA
- Comment l'utilisateur interagit avec eux
- Ce qui se passe quand un tag est ajouté (optimistic update)
- Comment les erreurs sont gérées
- Pourquoi la note ne doit pas se fermer
4. **And** la documentation inclut des diagrammes de séquence
5. **And** elle est située dans `docs/ghost-tags-behavior.md`
6. **And** elle est référencée dans le README principal
**Contenu de la Documentation:**
```markdown
# Ghost Tags Behavior
## Overview
Les tags fantômes sont des suggestions de tags générées par l'IA...
## User Flow
1. User types content → AI analyzes
2. Ghost tags appear with sparkle icon
3. User can:
- Click tag body to ADD
- Click X to DISMISS
4. When added:
- Optimistic update (immediate)
- API call in background
- Visual feedback (checkmark)
- NOTE STAYS OPEN
## Technical Implementation
- Event propagation is prevented
- Optimistic updates used
- Toast only for errors
## Critical Rules
- NEVER close note on tag interaction
- ALWAYS use optimistic updates
- ALWAYS prevent event propagation
```
**Fichiers à Modifier:**
- `docs/ghost-tags-behavior.md` - Nouveau fichier de documentation
- `README.md` - Ajouter une référence
**Tests Nécessaires:**
- Revue de la documentation par l'équipe
- Vérifier que tout est clair
**Risques:**
- Documentation peut devenir obsolète
- Nécessite d'être maintenue à jour
---
## Dépendances Entre Stories
```
GHOST-TAGS-1 (Prévenir fermeture) ← CRITIQUE
GHOST-TAGS-2 (Optimistic update) ← CRITIQUE
GHOST-TAGS-3 (Feedback visuel) ← AMÉLIORATION UX
GHOST-TAGS-4 (Retirer toast) ← POLISH
GHOST-TAGS-5 (Protection fermeture) ← SÉCURITÉ
GHOST-TAGS-7 (Tests E2E) ← VALIDATION
GHOST-TAGS-6 (Mode silencieux) ← OPTIONNEL
GHOST-TAGS-8 (Documentation) ← CONNAISSANCE
```
**Ordre Recommandé:**
**Sprint 1** (Correction du bug critique - 1-2 jours):
1. GHOST-TAGS-1: Prévenir la fermeture (URGENT)
2. GHOST-TAGS-2: Optimistic update (URGENT)
3. GHOST-TAGS-5: Protection fermeture accidentelle (IMPORTANT)
**Sprint 2** (Améliorations UX - 1 jour):
4. GHOST-TAGS-3: Feedback visuel
5. GHOST-TAGS-4: Retirer toast
**Sprint 3** (Qualité & Documentation - 1 jour):
6. GHOST-TAGS-7: Tests E2E
7. GHOST-TAGS-8: Documentation
**Optionnel** (Plus tard):
8. GHOST-TAGS-6: Mode silencieux
---
## Métriques de Succès
### Avant/Après
| Métrique | Avant (Bug) | Après (Corrigé) | Comment Mesurer |
|----------|-------------|----------------|-----------------|
| Fermeture intempestive | 100% (bug systématique) | 0% | Tests E2E Story 7 |
| Toasts intrusifs | Oui (gênant) | Non (ou optionnel) | Tests E2E |
| Tags ajoutés avec succès | Variable | 100% (optimistic) | Tests E2E |
| Satisfaction utilisateur | 1/5 (très frustrant) | 4/5+ | Feedback post-fix |
### Objectifs
- ✅ **0 fermeture accidentelle** lors de l'ajout d'un tag fantôme
- ⚡ **Ajout instantané** du tag (< 100ms ressenti)
- 🎯 **Pas d'interruption** du workflow d'édition
- 🔒 **Fiabilité 100%** sur l'ajout de tag
---
## Solutions Techniques Proposées
### Solution 1: Renforcer la Prévention de Propagation
Dans `ghost-tags.tsx`, ajouter:
```typescript
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation(); // ← AJOUTER
onSelectTag(suggestion.tag);
}}
onMouseDown={(e) => {
// ← AJOUTER: prévenir dès le mouseDown
e.preventDefault();
e.stopPropagation();
}}
className={...}
>
```
### Solution 2: Isoler le Conteneur Parent
Dans `note-input.tsx` et `note-editor.tsx`, vérifier que le Card n'a pas de onClick:
```typescript
<Card
onClick={(e) => {
// ← S'ASSURER qu'il n'y a PAS de onClick ici
// ou qu'il prévient bien la propagation
}}
>
```
### Solution 3: Optimistic Update Pattern
```typescript
const handleSelectGhostTag = async (tag: string) => {
// 1. Optimistic update IMMÉDIAT
setSelectedLabels(prev => [...prev, tag])
// 2. Appel API en arrière-plan
try {
const globalExists = globalLabels.some(l => l.toLowerCase() === tag.toLowerCase())
if (!globalExists) {
await addLabel(tag)
}
// PAS de toast ici pour éviter l'interruption
} catch (error) {
// Rollback en cas d'erreur seulement
setSelectedLabels(prev => prev.filter(l => l !== tag))
addToast(`Failed to add tag: ${error.message}`, 'error')
}
}
```
---
## Tests de Validation
### Scénario de Test Principal
```typescript
// Test manuel à exécuter après correction:
1. Ouvrir l'application Memento
2. Cliquer sur la zone "Take a note..."
3. Taper: "How to implement authentication in Next.js"
4. Attendre 2-3 secondes (apparition des tags fantômes)
5. Cliquer sur un tag fantôme (ex: "nextjs", "auth")
6. ✅ VÉRIFIER: La note reste OUVERTE
7. ✅ VÉRIFIER: Le tag apparaît dans les tags sélectionnés
8. ✅ VÉRIFIER: Pas de toast intrusif (ou discret)
9. ✅ VÉRIFIER: On peut continuer à éditer
10. Cliquer sur "Add" pour créer la note
11. ✅ VÉRIFIER: La note est créée avec le tag
```
---
## Fichers Critiques pour l'Implémentation
Les 5 fichiers les plus importants à modifier:
1. **`keep-notes/components/ghost-tags.tsx`** - Composant des tags fantômes (gestion événements)
2. **`keep-notes/components/note-input.tsx`** - Note input (handleSelectGhostTag)
3. **`keep-notes/components/note-editor.tsx`** - Note editor (handleSelectGhostTag)
4. **`keep-notes/context/LabelContext.tsx`** - Gestion asynchrone des labels
5. **`keep-notes/tests/ghost-tags-workflow.spec.ts`** - Tests E2E à créer
---
## Risques et Mitigations
### Risques Techniques
| Risque | Probabilité | Impact | Mitigation |
|--------|-------------|--------|------------|
| La correction casse d'autres fonctionnalités de clic | Moyenne | Moyen | Tests de régression complets |
| L'optimistic update crée des incohérences | Faible | Moyen | Gestion d'erreur avec rollback |
| Le bug revient avec une future mise à jour | Moyenne | Élevé | Tests E2E automatisés + documentation |
### Risques UX
| Risque | Probabilité | Impact | Mitigation |
|--------|-------------|--------|------------|
| Retirer le toast rend l'action invisible | Moyenne | Faible | Ajouter indicateur visuel sur le tag |
- Utilisateurs pourraient ne pas savoir si le tag a été ajouté
- Nécessite un autre indicateur visuel de succès
| Trop d'indicateurs visuels créent du bruit | Faible | Faible | Design minimaliste et subtil |
---
## Next Steps Immédiats
1. **Corriger le bug critique (Stories 1 & 2)** - Priorité MAXIMALE
2. **Tester manuellement** la correction sur plusieurs scénarios
3. **Créer les tests E2E** (Story 7) pour éviter la régression
4. **Déployer en production** dès que validé
5. **Documenter** le comportement (Story 8)
---
**Document Version:** 1.0
**Last Updated:** 2026-01-09
**Severity:** High (Bug critique UX)
**Target Fix:** Sprint 1 (1-2 jours)
---
## Annexes
### A. Capture d'écran du Bug
(Note: Ajouter une capture d'écran montrant le problème)
### B. Logs Console
(Note: Ajouter les logs console pertinents si disponibles)
### C. Environnement de Test
- Navigateur: Chrome / Firefox / Safari
- OS: Windows / Mac / Linux
- Version Memento: 0.2.0

View File

@@ -0,0 +1,463 @@
# Epic: Amélioration de la Recherche Sémantique - Version 2.0
**Epic ID:** EPIC-SEARCH-2.0
**Status:** Draft
**Priority:** High
**Created:** 2026-01-09
**Owner:** Development Team
---
## Description du Problème
L'actuel système de recherche hybride (mots-clés + sémantique) produit des résultats non pertinents et imprévisibles. Les utilisateurs se plaignent que la recherche "fait n'importe quoi" - les résultats ne correspondent pas à leurs attentes, même quand les notes contiennent les termes recherchés.
### Analyse des Causes Racines
Après analyse du code dans `keep-notes/app/actions/notes.ts` (lignes 108-212), les problèmes identifiés sont:
1. **Seuil de similarité cosine trop bas (0.40)**: Permet des correspondances sémantiques de très faible qualité, créant du bruit dans les résultats
2. **RRF (Reciprocal Rank Fusion) mal configuré**: Le constant k=60 n'est pas optimal pour le petit nombre de notes typique dans Keep
3. **Pondération fixe recherche mot-clé/sémantique**: Les poids sont hardcodés (3 pour titre, 1 pour contenu, 2 pour labels) sans adaptation au type de requête
4. **Absence de validation des embeddings**: Pas de vérification que les embeddings sont correctement générés ou de dimensionnalité cohérente
5. **Manque de prétraitement des requêtes**: Pas de stemming, lemmatization, ou expansion de requête
6. **Aucune métrique de qualité**: Impossible de mesurer l'amélioration ou la dégradation des performances
### Impact Utilisateur
- **Frustration**: Perte de temps à filtrer manuellement les résultats non pertinents
- **Perte de confiance**: Les utilisateurs désactivent ou évitent la recherche intelligente
- **Expérience dégradée**: Contredit la promesse d'une recherche "intuitive" du PRD
---
## Objectifs Mesurables
1. **Améliorer la précision de recherche de 40%** (mesurée par tests automatisés)
2. **Réduire les faux positifs sémantiques de 60%** (seuil plus strict)
3. **Temps de réponse < 300ms** pour 1000 notes (objectif PRD existant)
4. **Satisfaction utilisateur > 4/5** (feedback post-déploiement)
5. **Taux de "serendipity" (résultats sémantiques sans mots-clés) entre 20-40%** (objectif PRD)
---
## User Stories
### Story 1: Validation et Qualité des Embeddings
**ID:** SEARCH-2.0-1
**Title:** Valider la qualité des embeddings générés
**Priority:** Must Have
**Estimation:** 3h
**En tant que:** développeur
**Je veux:** valider que les embeddings sont correctement générés et stockés
**Afin que:** la recherche sémantique fonctionne sur des données de qualité
**Critères d'Acceptation:**
1. **Given** une note avec du contenu texte
2. **When** l'embedding est généré via `provider.getEmbeddings()`
3. **Then** le vecteur doit:
- Avoir une dimensionnalité > 0
- Contenir des nombres valides (pas de NaN, Infinity)
- Avoir une norme L2 normale (entre 0.7 et 1.2)
4. **Given** des embeddings existants en base de données
5. **When** ils sont chargés via `parseNote()`
6. **Then** ils doivent être correctement désérialisés et validés
7. **And** une action admin `/api/debug/embeddings/validate` doit lister les notes problématiques
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter validation dans `parseNote()`
- `keep-notes/lib/utils.ts` - Ajouter `validateEmbedding()` et `normalizeEmbedding()`
- `keep-notes/app/api/admin/embeddings/validate/route.ts` - Nouveau endpoint debug
**Tests Nécessaires:**
- Test unitaire `validateEmbedding()` avec vecteurs valides/invalides
- Test d'intégration création note → validation embedding
- Test endpoint API debug
**Risques:**
- Certains embeddings existants invalides nécessiteront un re-indexing
- Performance impact de la validation à chaque chargement
---
### Story 2: Optimisation du Seuil de Similarité Sémantique
**ID:** SEARCH-2.0-2
**Title:** Ajuster le seuil de similarité cosine pour éliminer le bruit
**Priority:** Must Have
**Estimation:** 4h
**En tant que:** utilisateur
**Je veux:** ne voir que des résultats sémantiquement pertinents
**Afin que:** la recherche me fasse confiance et me fasse gagner du temps
**Critères d'Acceptation:**
1. **Given** une requête de recherche sémantique
2. **When** les notes sont classées par similarité cosine
3. **Then** seules les notes avec similarité >= 0.65 sont considérées (au lieu de 0.40)
4. **And** le seuil doit être configurable dans `SystemConfig` (`SEARCH_SEMANTIC_THRESHOLD`)
5. **Given** une recherche avec résultats sémantiques faibles
6. **When** le seuil est appliqué
7. **Then** les faux positifs sont réduits d'au moins 50%
8. **And** un test automatisé mesure la réduction des faux positifs
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ligne 190, remplacer 0.40 par `config.SEARCH_SEMANTIC_THRESHOLD || 0.65`
- `keep-notes/lib/config.ts` - Lire la config depuis DB
- `keep-notes/tests/search-quality.spec.ts` - Ajouter tests de seuil
**Tests Nécessaires:**
- Test Playwright: recherche "coding" ne retourne PAS des notes sur "cuisine" (faux positifs)
- Test unitaire: vérifier que les scores < seuil sont filtrés
- Test de non-régression: s'assurer que les vrais positifs ne sont pas perdus
**Risques:**
- Seuil trop élevé peut éliminer des résultats pertinents (faux négatifs)
- Nécessite A/B testing pour trouver le seuil optimal
- Différents modèles d'embedding peuvent nécessiter des seuils différents
---
### Story 3: Reconfiguration de l'Algorithme RRF
**ID:** SEARCH-2.0-3
**Title:** Optimiser le paramètre k du Reciprocal Rank Fusion
**Priority:** Should Have
**Estimation:** 3h
**En tant que:** système
**Je veux:** un RRF avec un paramètre k adapté au nombre de notes typique
**Afin que:** le ranking hybride reflète mieux la pertinence réelle
**Critères d'Acceptation:**
1. **Given** un RRF avec constant k
2. **When** le nombre moyen de notes par utilisateur est < 500
3. **Then** k doit être 20 (au lieu de 60) pour mieux pénaliser les bas rangs
4. **And** k doit être configurable: `k = max(20, nombre_notes / 10)`
5. **Given** deux listes de ranking (mot-clé + sémantique)
6. **When** RRF est appliqué
7. **Then** les résultats bien classés dans les deux listes sont fortement favorisés
8. **And** la formule RRF est documentée dans le code
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Lignes 182-198, ajuster k et ajouter logique adaptive
- `keep-notes/lib/utils.ts` - Ajouter `calculateRRFK(totalNotes: number): number`
**Tests Nécessaires:**
- Test unitaire: vérifier la formule RRF avec différents k
- Test d'intégration: comparer rankings avec k=20 vs k=60
- Test avec dataset de benchmark (notes + requêtes + résultats attendus)
**Risques:**
- Changer k peut impacter significativement l'ordre des résultats
- Nécessite validation utilisateur sur de vraies données
- Peut nécessiter des ajustements itératifs
---
### Story 4: Pondération Adaptative des Scores de Recherche
**ID:** SEARCH-2.0-4
**Title:** Adapter les poids mot-clé/sémantique selon le type de requête
**Priority:** Should Have
**Estimation:** 6h
**En tant que:** utilisateur
**Je veux:** que la recherche privilégie les mots-clés pour les termes exacts
**Et qu'elle privilégie le sémantique pour les concepts abstraits
**Afin que:** les résultats soient toujours pertinents
**Critères d'Acceptation:**
1. **Given** une requête avec des guillemets (ex: `"Error 404"`)
2. **When** la recherche est exécutée
3. **Then** le poids mot-clé est multiplié par 2 (recherche exacte prioritaire)
4. **Given** une requête conceptuelle (ex: "comment améliorer...")
5. **When** la recherche est exécutée
6. **Then** le poids sémantique est multiplié par 1.5 (concept prioritaire)
7. **Given** une requête mixte
8. **When** aucun pattern n'est détecté
9. **Then** les poids par défaut sont utilisés
10. **And** la logique de détection est documentée
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter `detectQueryType()` et ajuster les poids
- `keep-notes/lib/types.ts` - Ajouter `QueryType: 'exact' | 'conceptual' | 'mixed'`
- `keep-notes/lib/utils.ts` - Ajouter `detectQueryType(query: string): QueryType`
**Tests Nécessaires:**
- Test unitaire `detectQueryType()` avec différents patterns
- Test d'intégration: vérifier que `"Error 404"` privilégie les mots-clés
- Test d'intégration: vérifier que "comment cuisiner" privilégie le sémantique
- Test Playwright: scénarios de recherche avec guillemets
**Risques:**
- La détection automatique du type de requête peut être imprécise
- Nécessite des règles bien pensées pour éviter les effets de bord
- Peut nécessiter du machine learning pour être vraiment efficace
---
### Story 5: Expansion et Normalisation des Requêtes
**ID:** SEARCH-2.0-5
**Title:** Améliorer les requêtes par expansion et normalisation
**Priority:** Could Have
**Estimation:** 5h
**En tant que:** utilisateur francophone/anglophone
**Je veux:** que ma recherche trouve les résultats même avec des variations de mots
**Afin que:** je n'aie pas à deviner les termes exacts utilisés
**Critères d'Acceptation:**
1. **Given** une requête avec des mots au pluriel (ex: "recettes pizzas")
2. **When** la recherche est exécutée
3. **Then** les singuliers sont aussi recherchés ("recette pizza")
4. **Given** une requête avec des accents (ex: "éléphant")
5. **When** la recherche est exécutée
6. **Then** les variantes sans accents sont aussi recherchées ("elephant")
7. **Given** une requête courte (< 3 mots)
8. **When** l'expansion est activée
9. **Then** des synonymes courants sont ajoutés (ex: "bug" → "erreur", "problème")
10. **And** l'expansion est limitée à 3 termes par mot original
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter `expandQuery()` avant le calcul des scores
- `keep-notes/lib/utils.ts` - Implémenter `expandQuery()` et `normalizeText()`
- `keep-notes/lib/data/synonyms.json` - Créer une liste de synonymes (FR/EN)
**Tests Nécessaires:**
- Test unitaire `expandQuery()` avec différents cas
- Test d'intégration: recherche "pizzas" trouve notes avec "pizza"
- Test d'intégration: recherche "bug" trouve notes avec "erreur"
- Test performance: vérifier que l'expansion ne dégrade pas les performances
**Risques:**
- L'expansion de requête peut augmenter significativement les faux positifs
- La gestion des synonymes est complexe et contextuelle
- Nécessite une base de synonymes bien maintenue
- Peut ne pas être pertinent pour toutes les langues
---
### Story 6: Interface de Debug et Monitoring de Recherche
**ID:** SEARCH-2.0-6
**Title:** Créer une interface de debug pour analyser la qualité de recherche
**Priority:** Should Have
**Estimation:** 8h
**En tant que:** développeur/testeur
**Je veux:** visualiser les détails du calcul de score pour chaque résultat
**Afin que:** je puisse comprendre et optimiser la recherche
**Critères d'Acceptation:**
1. **Given** une recherche exécutée
2. **When** le mode debug est activé (`?debug=true`)
3. **Then** chaque résultat affiche:
- Score mot-clé brut
- Score sémantique brut (similarité cosine)
- Score RRF final
- Rang dans chaque liste (mot-clé / sémantique)
4. **Given** la page `/debug-search`
5. **When** j'accède à la page
6. **Then** je vois une interface pour:
- Tester des requêtes avec tous les paramètres
- Comparer différents seuils de similarité
- Voir les embeddings des notes
7. **And** les métriques sont exportables en JSON
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Retourner les scores debug si demandé
- `keep-notes/app/debug-search/page.tsx` - Créer la page (existante dans git status)
- `keep-notes/components/search-debug-results.tsx` - Nouveau composant
- `keep-notes/app/api/debug/search/route.ts` - Nouveau endpoint API
**Tests Nécessaires:**
- Test E2E: accès à la page debug-search
- Test API: endpoint retourne bien les scores détaillés
- Test visuel: vérifier l'affichage des scores dans l'UI
- Test de performance: vérifier que le mode debug n'impacte pas la recherche normale
**Risques:**
- Complexité supplémentaire dans la UI
- Nécessite de bien sécuriser l'accès (admin uniquement)
- Informations sensibles (embeddings) visibles
---
### Story 7: Re-génération et Validation des Embeddings Existants
**ID:** SEARCH-2.0-7
**Title:** Script de re-indexation des embeddings invalides ou manquants
**Priority:** Must Have
**Estimation:** 4h
**En tant que:** administrateur système
**Je veux:** un script pour régénérer les embeddings des notes existantes
**Afin que:** toutes les notes bénéficient de la recherche sémantique
**Critères d'Acceptation:**
1. **Given** des notes avec embeddings manquants ou invalides
2. **When** je lance le script `npm run reindex-embeddings`
3. **Then** les embeddings sont régénérés pour toutes les notes
4. **And** la progression est affichée (X/Y notes traitées)
5. **Given** des notes avec des embeddings valides
6. **When** le script est lancé avec `--force`
7. **Then** tous les embeddings sont régénérés (même les valides)
8. **And** le script peut être relancé sans erreurs (idempotent)
9. **And** un rapport final résume les succès/erreurs
**Fichiers à Modifier:**
- `keep-notes/scripts/reindex-embeddings.ts` - Créer le script
- `keep-notes/app/actions/admin.ts` - Ajouter `reindexAllEmbeddings()`
- `keep-notes/package.json` - Ajouter le script npm
**Tests Nécessaires:**
- Test du script sur base de données vide
- Test du script avec des notes sans embeddings
- Test du script avec des embeddings invalides (NaN, dimension 0)
- Test du mode `--force`
- Test d'idempotence (relancer le script deux fois)
**Risques:**
- Temps d'exécution long si beaucoup de notes (plusieurs minutes/heures)
- Peut saturer le provider IA (Ollama/OpenAI) avec trop de requêtes
- Nécessite un mécanisme de rate limiting
- Peut impacter les performances de l'application si lancé pendant l'utilisation
---
### Story 8: Suite de Tests Automatisés de Qualité de Recherche
**ID:** SEARCH-2.0-8
**Title:** Créer des tests automatisés pour mesurer la qualité de recherche
**Priority:** Should Have
**Estimation:** 6h
**En tant que:** développeur
**Je veux:** une suite de tests automatisés pour valider la qualité de recherche
**Afin que:** les améliorations soient mesurables et les régressions détectées
**Critères d'Acceptation:**
1. **Given** un dataset de test (notes + requêtes + résultats attendus)
2. **When** les tests sont exécutés
3. **Then** les métriques suivantes sont calculées:
- Precision: % de résultats pertinents dans le top 10
- Recall: % de résultats pertinents trouvés
- MRR (Mean Reciprocal Rank): rang moyen du premier résultat pertinent
- Faux positifs sémantiques: résultats sans pertinence
4. **Given** une modification du code de recherche
5. **When** les tests sont relancés
6. **Then** une régression de plus de 5% fait échouer les tests
7. **And** les tests prennent < 2 minutes à s'exécuter
8. **And** un rapport HTML est généré pour analyse
**Fichiers à Modifier:**
- `keep-notes/tests/search-benchmark.spec.ts` - Créer le benchmark
- `keep-notes/tests/fixtures/search-dataset.json` - Dataset de test
- `keep-notes/tests/utils/search-metrics.ts` - Utilitaires de calcul de métriques
- `keep-notes/playwright.config.ts` - Configuration pour générer le rapport HTML
**Tests Nécessaires:**
- Test du test (métatest) avec un dataset trivial
- Test avec dataset réel (notes sur tech, cuisine, voyage, etc.)
- Test de régression: introduire un bug et vérifier que les tests le détectent
- Test de performance: temps d'exécution des tests
**Risques:**
- Création du dataset manuelle et longue
- Subjectivité de la "pertinence" (qui décide quoi est pertinent?)
- Maintenance du dataset à chaque nouvelle feature
- Tests peuvent être "flaky" si les embeddings changent
---
## Dépendances Entre Stories
```
SEARCH-2.0-1 (Validation Embeddings)
SEARCH-2.0-7 (Re-génération) ← dépend de 1 pour détecter les invalides
SEARCH-2.0-2 (Seuil Similarité) ← dépend de 1 pour des embeddings valides
SEARCH-2.0-3 (RRF Config)
SEARCH-2.0-4 (Pondération Adaptative)
SEARCH-2.0-8 (Tests Automatisés) ← dépend de 2,3,4 pour mesurer les améliorations
SEARCH-2.0-6 (Debug Interface) ← utile pendant le développement de toutes les autres
SEARCH-2.0-5 (Expansion Requêtes) ← amélioration optionnelle à la fin
```
**Ordre recommandé:**
1. **Sprint 1:** SEARCH-2.0-1, SEARCH-2.0-7 (Base solide avec embeddings valides)
2. **Sprint 2:** SEARCH-2.0-2, SEARCH-2.0-3 (Corrections critiques du ranking)
3. **Sprint 3:** SEARCH-2.0-4, SEARCH-2.0-6 (Améliorations + tooling)
4. **Sprint 4:** SEARCH-2.0-8, SEARCH-2.0-5 (Tests + optimisations optionnelles)
---
## Métriques de Succès
### Avant/Après (Objectifs)
| Métrique | Avant | Après (Objectif) | Comment Mesurer |
|----------|-------|------------------|-----------------|
| Précision Top-10 | ~50% (estimé) | 70%+ | Tests automatisés Story 8 |
| Faux positifs sémantiques | ~30% | <10% | Tests Playwright |
| Temps de réponse (1000 notes) | 200ms | <300ms | Tests performance |
| Taux de serendipité | N/A | 20-40% | Tests dataset |
| Satisfaction utilisateur | 2/5 (subjectif) | 4/5+ | Sondage post-déploiement |
---
## Configuration Système Proposée
Nouveaux paramètres dans `SystemConfig`:
```typescript
interface SearchConfig {
// Seuil de similarité cosine minimum (0-1)
SEARCH_SEMANTIC_THRESHOLD: number; // défaut: 0.65
// Constante k pour RRF (adaptive)
SEARCH_RRF_K_BASE: number; // défaut: 20
SEARCH_RRF_K_ADAPTIVE: boolean; // défaut: true
// Pondération mot-clé vs sémantique
SEARCH_KEYWORD_BOOST_EXACT: number; // défaut: 2.0 (guillemets)
SEARCH_KEYWORD_BOOST_CONCEPTUAL: number; // défaut: 0.7
SEARCH_SEMANTIC_BOOST_EXACT: number; // défaut: 0.7
SEARCH_SEMANTIC_BOOST_CONCEPTUAL: number; // défaut: 1.5
// Expansion de requête
SEARCH_QUERY_EXPANSION_ENABLED: boolean; // défaut: true
SEARCH_QUERY_EXPANSION_MAX_SYNONYMS: number; // défaut: 3
// Debug
SEARCH_DEBUG_MODE: boolean; // défaut: false
}
```
---
## Fichers Critiques pour l'Implémentation
Les 5 fichiers les plus importants à modifier:
1. **keep-notes/app/actions/notes.ts** - Logique de recherche principale (RRF, seuils, ranking)
2. **keep-notes/lib/utils.ts** - Fonctions de similarité cosine et nouvelles utilités
3. **keep-notes/lib/ai/providers/ollama.ts** - Génération des embeddings avec validation
4. **keep-notes/tests/search-quality.spec.ts** - Tests de qualité de recherche
5. **keep-notes/lib/config.ts** - Configuration des nouveaux paramètres
---
**Document Version:** 1.0
**Last Updated:** 2026-01-09
**Agent:** Plan (a551c9b)

View File

@@ -0,0 +1,27 @@
---
stepsCompleted: [1]
---
# Implementation Readiness Assessment Report
**Date:** 2026-01-09
**Project:** Keep
## 1. Document Inventory
### PRD Documents
- prd.md
- prd-executive-summary.md
- prd-web-app-requirements.md
- prd-auth-admin.md
### Architecture Documents
- ⚠️ MISSING
### Epics & Stories Documents
- epics.md
### UX Design Documents
- ⚠️ MISSING
---

View File

@@ -0,0 +1,75 @@
# PRD - Authentification Avancée & Administration
## 1. Contexte & Objectifs
L'application Memento dispose actuellement d'une authentification basique. Pour un usage multi-utilisateurs ou privé/familial sécurisé, il est nécessaire d'introduire des rôles (Admin vs Utilisateur standard) et de permettre la gestion des comptes.
**Objectifs :**
- Permettre à un Administrateur de gérer les utilisateurs (création manuelle, suppression).
- Permettre à tout utilisateur de modifier ses informations personnelles (nom, mot de passe).
- Sécuriser l'application en introduisant des rôles.
## 2. Spécifications Fonctionnelles
### 2.1 Gestion des Rôles (Backend)
- **Modèle User** : Ajouter un champ `role` avec deux valeurs possibles : `USER` (défaut) et `ADMIN`.
- **NextAuth** : Le rôle de l'utilisateur doit être disponible dans la session (via le token JWT) pour être vérifié côté client et serveur.
### 2.2 Dashboard Admin (`/admin`)
**Accès :** Restreint aux utilisateurs ayant le rôle `ADMIN`.
**Fonctionnalités :**
1. **Liste des utilisateurs** :
- Tableau affichant : Nom, Email, Rôle, Date de création.
- Actions par ligne : "Supprimer", "Promouvoir Admin / Rétrograder".
2. **Création d'utilisateur** :
- Un bouton "Nouvel Utilisateur" ouvre une modale ou un formulaire.
- Champs : Nom, Email, Mot de passe, Rôle.
- Validation : Email unique, mot de passe min 6 caractères.
### 2.3 Profil Utilisateur (`/settings/profile`)
**Accès :** Tout utilisateur connecté.
**Fonctionnalités :**
1. **Modifier le profil** :
- Changer le nom d'affichage.
2. **Sécurité** :
- Changer le mot de passe (nécessite l'ancien mot de passe pour validation).
## 3. Spécifications Techniques
### 3.1 Base de Données (Prisma)
Modifier `schema.prisma` :
```prisma
model User {
// ... champs existants
role String @default("USER") // ou Enum si SQLite le supporte bien (sinon String géré par app)
}
```
### 3.2 Authentication (NextAuth v5)
- Modifier `auth.config.ts` :
- Ajouter `role` au type `Session` et `User`.
- Dans le callback `jwt`, récupérer le rôle depuis la DB et le persister dans le token.
- Dans le callback `session`, passer le rôle du token à la session.
### 3.3 Server Actions
Créer `app/actions/admin.ts` :
- `getUsers()`: Retourne la liste (Admin only).
- `createUser(data)`: Crée un user avec hash du mot de passe (Admin only).
- `deleteUser(id)`: Supprime un user (Admin only).
- `updateUserRole(id, role)`: Change le rôle (Admin only).
Créer `app/actions/profile.ts` :
- `updateProfile(data)`: Met à jour nom/email.
- `changePassword(oldPwd, newPwd)`: Vérifie l'ancien hash et met à jour.
### 3.4 Interface Utilisateur (UI)
- **Admin** : Utiliser `Table`, `Dialog` et `Form` de Shadcn UI.
- **Profil** : Utiliser `Card`, `Input` et `Button` de Shadcn UI.
- **Menu** : Ajouter un lien "Admin" dans la Sidebar ou le menu utilisateur, visible uniquement si `role === 'ADMIN'`.
## 4. Plan d'Implémentation
1. **Migration DB** : Ajouter le champ `role` et mettre à jour Prisma.
2. **Config Auth** : Mettre à jour NextAuth pour propager le rôle.
3. **Backend** : Implémenter les Server Actions (Admin & Profil).
4. **Frontend Admin** : Créer la page `/admin` et ses composants.
5. **Frontend Profil** : Créer la page `/settings/profile`.
6. **Sécurisation** : Ajouter les vérifications de rôle dans le Middleware ou les Layouts.