feat(perf): implement performance optimizations for session handling

- Introduced a new configuration file `config.yaml` for specifying project context and artifact rules.
- Added `.openspec.yaml` files for tracking changes related to performance improvements.
- Created design documents outlining the context, goals, decisions, and migration plans for optimizing session performance.
- Proposed changes include batching database queries, debouncing event refreshes, purging old events, and implementing loading states for better user experience.
- Added tasks and specifications to ensure proper implementation and validation of the new features.

These enhancements aim to improve the scalability and responsiveness of the application during collaborative sessions.
This commit is contained in:
2026-03-10 08:06:47 +01:00
parent 6baa9bfada
commit 2d266f89f9
19 changed files with 519 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
## Context
L'application charge les collaborateurs de session via `resolveCollaborator` appelé en séquence dans une boucle (N+1). Le hook `useLive` déclenche `router.refresh()` sur chaque événement SSE reçu, sans groupement, causant des re-renders en cascade si plusieurs événements arrivent simultanément. La fonction `cleanupOldEvents` existe dans `session-share-events.ts` mais n'est jamais appelée, laissant les événements s'accumuler indéfiniment. L'absence de `loading.tsx` sur les routes principales empêche le streaming App Router de s'activer. Les modals (`ShareModal`) sont inclus dans le bundle initial alors qu'ils sont rarement utilisés.
## Goals / Non-Goals
**Goals:**
- Éliminer le N+1 sur `resolveCollaborator` avec un fetch batché
- Grouper les refreshes SSE consécutifs avec un debounce
- Purger les événements SSE au fil de l'eau (après chaque `createEvent`)
- Activer le streaming de navigation avec `loading.tsx` sur les routes à chargement lent
- Réduire le bundle JS initial en lazy-loadant les modals
**Non-Goals:**
- Refactorer l'architecture SSE (sujet Phase 2)
- Changer la stratégie de cache/revalidation (sujet Phase 2)
- Optimiser les requêtes Prisma profondes (sujet Phase 3)
- Modifier le comportement fonctionnel existant
## Decisions
### 1. Batch resolveCollaborator par collect + single query
**Décision** : Dans `session-queries.ts`, collecter tous les `userId` des collaborateurs d'une liste de sessions, puis faire un seul `prisma.user.findMany({ where: { id: { in: [...ids] } } })`, et mapper les résultats en mémoire.
**Alternatives** : Garder le N+1 mais ajouter un cache mémoire par requête → rejeté car ne résout pas le problème structurellement.
### 2. Debounce via useRef + setTimeout natif
**Décision** : Dans `useLive.ts`, utiliser `useRef` pour stocker un timer et `setTimeout` / `clearTimeout` pour debounce à 300ms. Pas de dépendance externe.
**Alternatives** : Bibliothèque `lodash.debounce` → rejeté pour éviter une dépendance pour 5 lignes.
### 3. cleanupOldEvents inline dans createEvent
**Décision** : Appeler `cleanupOldEvents` à la fin de chaque `createEvent` (fire-and-forget, pas d'await bloquant). La purge garde les 50 derniers événements par session (seuil actuel).
**Alternatives** : Cron externe → trop complexe pour un quick win ; interval côté API SSE → couplage non souhaité.
### 4. loading.tsx avec skeleton minimaliste
**Décision** : Créer un `loading.tsx` par route principale (`/sessions`, `/weather`, `/users`) avec un skeleton générique (barres grises animées). Le composant est statique et ultra-léger.
### 5. next/dynamic avec ssr: false sur les modals
**Décision** : Wrapper `ShareModal` (et `CollaborationToolbar` si pertinent) avec `next/dynamic({ ssr: false })`. Le composant parent gère le loading state.
## Risks / Trade-offs
- **Debounce 300ms** → légère latence perçue sur les mises à jour collaboratives. Mitigation : valeur configurable via constante.
- **cleanupOldEvents fire-and-forget** → si la purge échoue, les erreurs sont silencieuses. Mitigation : logger l'erreur sans bloquer.
- **Batch resolveCollaborator** → si la liste de sessions est très grande (>500), la requête `IN` peut être lente. Mitigation : acceptable pour les volumes actuels ; paginer si nécessaire (Phase 3).
- **next/dynamic ssr: false** → les modals ne sont pas rendus côté serveur. Acceptable car ils sont interactifs uniquement.
## Migration Plan
Chaque optimisation est indépendante et déployable séparément. Pas de migration de données. Rollback : revert du commit concerné. L'ordre recommandé : (1) batch resolveCollaborator, (2) cleanupOldEvents, (3) debounce useLive, (4) loading.tsx, (5) next/dynamic.