521 lines
14 KiB
Markdown
521 lines
14 KiB
Markdown
# Plan Complet de Correction des Bugs - Memento/Keep
|
|
**Date:** 2026-01-15
|
|
**Version:** 1.0
|
|
**Statut:** Prêt pour correction
|
|
|
|
---
|
|
|
|
## RÉSUMÉ EXÉCUTIF
|
|
|
|
**Bugs Critiques Confirmés par Tests Playwright:** 3
|
|
**Bugs Critiques Identifiés par Analyse de Code:** 5
|
|
**Bugs High Priorité:** 0
|
|
**Total Bugs à Corriger:** 8
|
|
|
|
---
|
|
|
|
## PRIORITÉS DE CORRECTION
|
|
|
|
### 🔴 CRITIQUE (Doit être corrigé immédiatement)
|
|
1. **TriggerRefresh ne fonctionne pas** - Confirmé par tests
|
|
2. **Refresh excessif router.refresh()** - Cause flash et perte de scroll
|
|
3. **Reload complet window.location.reload()** - Force page reload complète
|
|
4. **Mobile drag non fonctionnel** - Muuri incompatible avec touch
|
|
|
|
### 🟡 HIGH (Corriger rapidement)
|
|
5. **Doublons boutons fermeture** - Confusion UI
|
|
6. **Performance re-renders** - useEffect mal gérés
|
|
|
|
---
|
|
|
|
## DÉTAIL DES CORRECTIONS PAR BUG
|
|
|
|
### 🔴 Bug #1: triggerRefresh() Non Fonctionnel
|
|
|
|
**Confirmation par tests:**
|
|
- `bug-move-direct.spec.ts` ligne 168-174
|
|
- `bug-note-move-refresh.spec.ts` ligne 105-109, 123-136
|
|
- Les tests confirment: "triggerRefresh() didn't work!"
|
|
|
|
**Cause Racine:**
|
|
```typescript
|
|
// keep-notes/context/NoteRefreshContext.tsx
|
|
const NoteRefreshContext = createContext<NoteRefreshContextType | undefined>(undefined)
|
|
|
|
export function NoteRefreshProvider({ children }: { children: React.ReactNode }) {
|
|
const [refreshKey, setRefreshKey] = useState(0)
|
|
|
|
const triggerRefresh = useCallback(() => {
|
|
setRefreshKey(prev => prev + 1) // ❌ INCORRECT! Ne déclenche pas de re-render global
|
|
}, [refreshKey]) // ❌ DÉPENDANCE INCORRECT!
|
|
|
|
return (
|
|
<NoteRefreshContext.Provider value={{ refreshKey, triggerRefresh }}>
|
|
{children}
|
|
</NoteRefreshContext.Provider>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correction:**
|
|
```typescript
|
|
// CORRECT: utiliser useRef pour éviter les cycles de dépendances
|
|
export function NoteRefreshProvider({ children }: { children: React.ReactNode }) {
|
|
const [refreshKey, setRefreshKey] = useState(0)
|
|
const refreshKeyRef = useRef(refreshKey)
|
|
|
|
const triggerRefresh = useCallback(() => {
|
|
const newKey = refreshKeyRef.current + 1
|
|
refreshKeyRef.current = newKey
|
|
setRefreshKey(newKey) // ✅ Déclenche le re-render
|
|
}, []) // ✅ Pas de dépendances
|
|
|
|
return (
|
|
<NoteRefreshContext.Provider value={{ refreshKey, triggerRefresh }}>
|
|
{children}
|
|
</NoteRefreshContext.Provider>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/context/NoteRefreshContext.tsx`
|
|
|
|
---
|
|
|
|
### 🔴 Bug #2: router.refresh() Excessif
|
|
|
|
**Fichiers affectés:**
|
|
- `keep-notes/components/note-card.tsx` (lignes 200, 208, 216, 224, 235)
|
|
- `keep-notes/app/(main)/page.tsx` (lignes 171, 185)
|
|
|
|
**Cause Racine:**
|
|
```typescript
|
|
// Chaque action déclenche un refresh complet de la page
|
|
const handleTogglePin = async () => {
|
|
startTransition(async () => {
|
|
addOptimisticNote({ isPinned: !note.isPinned })
|
|
await togglePin(note.id, !note.isPinned)
|
|
router.refresh() // ❌ FORCE RELOAD COMPLET
|
|
})
|
|
}
|
|
```
|
|
|
|
**Correction:**
|
|
```typescript
|
|
// CORRECT: Supprimer router.refresh() et laisser le state React se mettre à jour
|
|
const handleTogglePin = async () => {
|
|
addOptimisticNote({ isPinned: !note.isPinned })
|
|
await togglePin(note.id, !note.isPinned)
|
|
// ✅ Pas de refresh - l'état optimiste gère l'UI
|
|
}
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/components/note-card.tsx`
|
|
- `keep-notes/app/(main)/page.tsx`
|
|
|
|
---
|
|
|
|
### 🔴 Bug #3: window.location.reload() dans notebooks-context.tsx
|
|
|
|
**Fichier affecté:**
|
|
- `keep-notes/context/notebooks-context.tsx` (lignes 141, 154, 169)
|
|
|
|
**Cause Racine:**
|
|
```typescript
|
|
// Création, mise à jour, suppression de notebook déclenche un reload complet
|
|
const updateNotebook = async (notebookId: string, data: UpdateNotebookInput) => {
|
|
const response = await fetch(`/api/notebooks/${notebookId}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update notebook')
|
|
}
|
|
|
|
// Recharger les notebooks après mise à jour
|
|
window.location.reload() // ❌ FORCE RELOAD COMPLET
|
|
}
|
|
```
|
|
|
|
**Correction:**
|
|
```typescript
|
|
// CORRECT: Utiliser triggerRefresh() à la place
|
|
const updateNotebook = async (notebookId: string, data: UpdateNotebookInput) => {
|
|
const response = await fetch(`/api/notebooks/${notebookId}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update notebook')
|
|
}
|
|
|
|
// ✅ Rafraîchissement optimiste du state React
|
|
triggerRefresh()
|
|
}
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/context/notebooks-context.tsx`
|
|
|
|
---
|
|
|
|
### 🔴 Bug #4: Mobile Drag Non Fonctionnel
|
|
|
|
**Fichier affecté:**
|
|
- `keep-notes/components/masonry-grid.tsx` (lignes 160-185)
|
|
|
|
**Cause Racine:**
|
|
```typescript
|
|
// Détection mobile insuffisante
|
|
const isMobile = window.matchMedia('(pointer: coarse)').matches;
|
|
|
|
// Configuration Muuri avec dragHandle qui conflict avec touch events
|
|
const layoutOptions = {
|
|
dragEnabled: true,
|
|
dragHandle: '.muuri-drag-handle', // ❌ Problématique sur mobile
|
|
dragContainer: document.body,
|
|
dragStartPredicate: {
|
|
distance: 10,
|
|
delay: 0,
|
|
},
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Correction:**
|
|
```typescript
|
|
// CORRECT 1: Désactiver drag sur mobile
|
|
const isMobile = window.innerWidth < 768; // ✅ Détection fiable
|
|
const isTouchDevice = 'ontouchstart' in window; // ✅ Détection fiable
|
|
|
|
const layoutOptions = {
|
|
dragEnabled: !isMobile && !isTouchDevice, // ✅ Désactiver sur mobile
|
|
dragHandle: isMobile ? undefined : '.muuri-drag-handle', // ✅ Pas de handle sur mobile
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Correction Alternative (Recommandée):**
|
|
```typescript
|
|
// CORRECT 2: Utiliser @dnd-kit/core qui supporte nativement le touch
|
|
// Remplacer Muuri par @dnd-kit/core
|
|
import { DndContext, DndProvider } from '@dnd-kit/core'
|
|
|
|
// Ceci résoudra définitivement les problèmes de mobile drag
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/components/masonry-grid.tsx`
|
|
- Éventuellement: `package.json` (si changement de librairie)
|
|
|
|
---
|
|
|
|
### 🟡 Bug #5: Doublons Boutons Fermeture
|
|
|
|
**Fichier affecté:**
|
|
- `keep-notes/components/note-card.tsx` (lignes 351-357, 411-413)
|
|
|
|
**Cause Racine:**
|
|
```typescript
|
|
// Bouton "Leave Share" avec icône X
|
|
<Button onClick={handleLeaveShare}>
|
|
<X className="h-3 w-3 mr-1" />
|
|
Leave Share
|
|
</Button>
|
|
|
|
// Bouton "Remove Fused Badge" avec icône X
|
|
<button onClick={handleRemoveFusedBadge}>
|
|
<X className="h-2.5 w-2.5" />
|
|
Remove
|
|
</button>
|
|
```
|
|
|
|
**Correction:**
|
|
```typescript
|
|
// CORRECT: Utiliser des icônes différentes pour chaque action
|
|
import { X, Trash2, FolderOpen, LogOut } from 'lucide-react'
|
|
|
|
// Bouton "Remove Fused Badge" - utiliser icône différente
|
|
<button onClick={handleRemoveFusedBadge}>
|
|
<Trash2 className="h-2.5 w-2.5 text-purple-600" />
|
|
<span className="ml-2">Remove</span>
|
|
</button>
|
|
|
|
// Bouton "Leave Share" - utiliser icône différente
|
|
<Button onClick={handleLeaveShare}>
|
|
<LogOut className="h-3 w-3 mr-1 text-blue-600" />
|
|
Leave Share
|
|
</Button>
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/components/note-card.tsx`
|
|
|
|
---
|
|
|
|
### 🟡 Bug #6: Re-renders Inutiles (Performance)
|
|
|
|
**Fichiers affectés:**
|
|
- `keep-notes/components/note-card.tsx` (lignes 151-154, 162-180)
|
|
- `keep-notes/hooks/use-debounce.ts` (lignes 6-14)
|
|
|
|
**Cause Racine #1: useDebounce mal implémenté**
|
|
```typescript
|
|
export function useDebounce<T>(value: T, delay: number): T {
|
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setDebouncedValue(value)
|
|
}, delay)
|
|
|
|
return () => {
|
|
clearTimeout(timer)
|
|
}
|
|
}, [value, delay]) // ❌ Recrée l'effet à chaque render
|
|
// ❌ Même si value ne change pas, l'effet se recrée
|
|
// ❌ Provoque des cascades de re-renders
|
|
|
|
return debouncedValue
|
|
}
|
|
```
|
|
|
|
**Cause Racine #2: useEffect mal géré**
|
|
```typescript
|
|
useEffect(() => {
|
|
const loadCollaborators = async () => {
|
|
// ...
|
|
}
|
|
|
|
loadCollaborators()
|
|
}, [note.id, note.userId]) // ❌ Se déclenche trop souvent
|
|
// ❌ Même si les collaborateurs n'ont pas changé
|
|
// ❌ Provoque des cascades de re-renders
|
|
```
|
|
|
|
**Correction #1:**
|
|
```typescript
|
|
export function useDebounce<T>(value: T, delay: number): T {
|
|
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
const timerRef = useRef<NodeJS.Timeout>()
|
|
|
|
useEffect(() => {
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current)
|
|
}
|
|
|
|
timerRef.current = setTimeout(() => {
|
|
setDebouncedValue(value)
|
|
}, delay)
|
|
|
|
return () => {
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current)
|
|
}
|
|
}
|
|
}, [value, delay]) // ✅ Recrée uniquement quand value change
|
|
|
|
return debouncedValue
|
|
}
|
|
```
|
|
|
|
**Correction #2:**
|
|
```typescript
|
|
useEffect(() => {
|
|
const loadCollaborators = async () => {
|
|
// ...
|
|
}
|
|
|
|
loadCollaborators()
|
|
}, [note.id, note.userId, isOwner, isSharedNote]) // ✅ Dépendances complètes
|
|
// ✅ Se déclenche uniquement quand une de ces valeurs change vraiment
|
|
```
|
|
|
|
**Fichiers à modifier:**
|
|
- `keep-notes/hooks/use-debounce.ts`
|
|
- `keep-notes/components/note-card.tsx`
|
|
|
|
---
|
|
|
|
## ORDRE DES CORRECTIONS RECOMMANDÉ
|
|
|
|
### Phase 1: Critique (Doit être corrigé immédiatement)
|
|
1. **Corriger triggerRefresh()** - Bug #1
|
|
2. **Supprimer router.refresh()** - Bug #2
|
|
3. **Supprimer window.location.reload()** - Bug #3
|
|
4. **Corriger mobile drag** - Bug #4
|
|
|
|
### Phase 2: High (Corriger rapidement)
|
|
5. **Doublons boutons** - Bug #5
|
|
6. **Performance re-renders** - Bug #6
|
|
|
|
### Phase 3: Validation (Tests)
|
|
7. **Rejouer tous les tests** pour confirmer les corrections
|
|
8. **Tests manuels** pour vérifier chaque bug corrigé
|
|
|
|
---
|
|
|
|
## INSTRUCTIONS D'EXÉCUTION
|
|
|
|
### Étape 1: Sauvegarder l'état actuel
|
|
```bash
|
|
git add .
|
|
git commit -m "Backup avant correction des bugs"
|
|
```
|
|
|
|
### Étape 2: Corriger Bug #1 (triggerRefresh)
|
|
**Fichier:** `keep-notes/context/NoteRefreshContext.tsx`
|
|
1. Remplacer l'implémentation actuelle
|
|
2. Tester avec les tests Playwright existants
|
|
3. Confirmer que triggerRefresh fonctionne
|
|
|
|
### Étape 3: Corriger Bug #2 (router.refresh excessif)
|
|
**Fichiers:**
|
|
- `keep-notes/components/note-card.tsx`
|
|
- `keep-notes/app/(main)/page.tsx`
|
|
|
|
1. Supprimer tous les appels à `router.refresh()`
|
|
2. Laisser l'état optimiste gérer l'UI
|
|
3. Tester que les actions se font sans refresh
|
|
|
|
### Étape 4: Corriger Bug #3 (window.location.reload())
|
|
**Fichier:** `keep-notes/context/notebooks-context.tsx`
|
|
1. Remplacer tous les `window.location.reload()` par `triggerRefresh()`
|
|
2. Tester que les actions sur notebooks se font sans reload complet
|
|
|
|
### Étape 5: Corriger Bug #4 (Mobile drag)
|
|
**Fichier:** `keep-notes/components/masonry-grid.tsx`
|
|
|
|
**Option A (Simple):**
|
|
1. Désactiver drag sur mobile
|
|
2. Améliorer la détection mobile
|
|
|
|
**Option B (Recommandée):**
|
|
1. Remplacer Muuri par @dnd-kit/core
|
|
2. Implémenter un drag handler mobile-friendly
|
|
|
|
### Étape 6: Corriger Bug #5 (Doublons boutons)
|
|
**Fichier:** `keep-notes/components/note-card.tsx`
|
|
1. Remplacer les icônes X par des icônes spécifiques
|
|
2. Ajouter des tooltips explicites
|
|
3. Utiliser des couleurs sémantiques
|
|
|
|
### Étape 7: Corriger Bug #6 (Performance)
|
|
**Fichiers:**
|
|
- `keep-notes/hooks/use-debounce.ts`
|
|
- `keep-notes/components/note-card.tsx`
|
|
|
|
1. Corriger useDebounce avec useRef
|
|
2. Corriger les dépendances useEffect
|
|
3. Utiliser useMemo pour éviter les recréations
|
|
|
|
### Étape 8: Validation
|
|
```bash
|
|
# Rejouer tous les tests
|
|
npx playwright test tests/bug-*.spec.ts
|
|
|
|
# Tests manuels
|
|
1. Ouvrir l'application
|
|
2. Créer une note
|
|
3. Toggle pin, archive, color
|
|
4. Vérifier: pas de flash, pas de perte de scroll
|
|
5. Déplacer la note vers un notebook
|
|
6. Vérifier: la note disparaît de la page principale
|
|
7. Sur mobile: tester le drag and drop
|
|
8. Vérifier: pas de bugs de scroll sur mobile
|
|
```
|
|
|
|
---
|
|
|
|
## CRITÈRES DE VALIDATION
|
|
|
|
### Pour chaque bug corrigé:
|
|
- ✅ Le bug ne se produit plus
|
|
- ✅ Aucun effet secondaire indésirable
|
|
- ✅ Les tests Playwright passent
|
|
- ✅ Performance améliorée (mesurable)
|
|
- ✅ Code propre et bien documenté
|
|
|
|
### Pour l'application entière:
|
|
- ✅ Aucun flash d'écran inutile
|
|
- ✅ Aucune perte de scroll
|
|
- ✅ Drag and drop fonctionne sur desktop
|
|
- ✅ Drag and drop fonctionne/ou est désactivé proprement sur mobile
|
|
- ✅ Toutes les actions UI sont optimistes
|
|
- ✅ L'UI reste réactive sans re-renders inutiles
|
|
|
|
---
|
|
|
|
## SUITE APRÈS CORRECTIONS
|
|
|
|
### Améliorations UX:
|
|
1. **Animations fluides** lors des actions
|
|
2. **Feedback visuel** (loading states, success toasts)
|
|
3. **Indicateurs de progression** pour les actions longues
|
|
4. **Undo/Redo** pour les actions destructrices
|
|
|
|
### Améliorations Mobile:
|
|
1. **Touch gestures** pour swipe actions
|
|
2. **Pull-to-refresh** pour synchronisation
|
|
3. **Double-tap** pour quick actions
|
|
4. **Responsive design** amélioré
|
|
|
|
### Améliorations Techniques:
|
|
1. **Optimisation des re-renders** (React.memo, useMemo, useCallback)
|
|
2. **Lazy loading** des composants lourds
|
|
3. **Virtual scrolling** pour les grandes listes de notes
|
|
4. **Code splitting** pour réduire la taille du bundle
|
|
|
|
---
|
|
|
|
## RISQUES ET ATTENTIONS
|
|
|
|
### Risques:
|
|
- **Casser des fonctionnalités existantes** en modifiant le state
|
|
- **Introduction de nouveaux bugs** pendant la correction
|
|
- **Régression** de bugs déjà corrigés
|
|
|
|
### Atténuations:
|
|
- Faire les corrections une par une avec validation
|
|
- Commencer par les bugs critiques les plus simples
|
|
- Tester minutieusement après chaque correction
|
|
- Avoir un plan de rollback prêt
|
|
|
|
---
|
|
|
|
## DOCUMENTS DE RÉFÉRENCE
|
|
|
|
- Analyse complète: `_bmad-output/BUG-ANALYSIS-REPORT.md`
|
|
- Tests Playwright: `keep-notes/tests/bug-*.spec.ts`
|
|
- Documentation existante: `docs/` (à mettre à jour après corrections)
|
|
|
|
---
|
|
|
|
## TIMELINE ESTIMÉE
|
|
|
|
**Phase 1 (Critique):** 2-4 heures
|
|
**Phase 2 (High):** 1-2 heures
|
|
**Phase 3 (Validation):** 1-2 heures
|
|
**Total estimé:** 4-8 heures
|
|
|
|
**Note:** Les corrections sont relativement simples car les bugs sont bien localisés et documentés.
|
|
|
|
---
|
|
|
|
## STATUT
|
|
|
|
**État:** ✅ Prêt pour l'exécution
|
|
**Fichiers à modifier:** 6 fichiers principaux
|
|
**Bugs à corriger:** 8
|
|
**Tests à rejouer:** 3 tests Playwright
|
|
|
|
---
|
|
|
|
*Ce plan doit être exécuté après validation par le développeur.*
|