Keep/_bmad-output/PLAN-DE-CORRECTION-DES-BUGS.md

14 KiB

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)

  1. Doublons boutons fermeture - Confusion UI
  2. 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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):

// 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:

// 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:

// 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é

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é

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:

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:

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)

  1. Doublons boutons - Bug #5
  2. Performance re-renders - Bug #6

Phase 3: Validation (Tests)

  1. Rejouer tous les tests pour confirmer les corrections
  2. Tests manuels pour vérifier chaque bug corrigé

INSTRUCTIONS D'EXÉCUTION

Étape 1: Sauvegarder l'état actuel

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

# 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.