436 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			436 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # ✅ CHECKLIST D'AMÉLIORATIONS PRIORISÉES (ICE Scoring)
 | ||
| 
 | ||
| **Légende ICE:** Impact (1-10) / Confiance (1-10) / Effort (1-10, 1=facile)  
 | ||
| **Score ICE = (Impact × Confiance) / Effort**
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🔴 P0 — CRITICAL (>10 jours, éliminer gels UI et sécurité)
 | ||
| 
 | ||
| ### 1. [P0] Intégration Meilisearch pour recherche côté serveur
 | ||
| **ICE:** 10/10/7 = **14.3**  
 | ||
| **Pourquoi:** Recherche actuelle O(N) frontend bloque UI, impossible de scaler >1000 notes. Meilisearch offre typo-tolerance, highlights serveur, facettes natives, temps de réponse <50ms.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Ajouter service Docker Meilisearch dans `docker-compose.yml`
 | ||
| 2. Créer script d'indexation backend (`server/meilisearch-indexer.mjs`)
 | ||
| 3. Définir schéma index (voir section Architecture)
 | ||
| 4. Créer `SearchMeilisearchService` Angular appelant `/api/search`
 | ||
| 5. Mapper opérateurs Obsidian → filtres Meilisearch
 | ||
| 6. Migrer `SearchOrchestratorService` pour déléguer à backend
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Recherche retourne en <150ms P95 sur 1000 notes
 | ||
| - ✅ Opérateurs `tag:`, `path:`, `file:` fonctionnels
 | ||
| - ✅ Highlights retournés par serveur (pas de calcul frontend)
 | ||
| - ✅ Typo-tolerance activée (distance 2)
 | ||
| 
 | ||
| **Estimation:** 5 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 2. [P0] Ajouter DOMPurify pour sanitization XSS
 | ||
| **ICE:** 10/10/2 = **50.0**  
 | ||
| **Pourquoi:** Vulnérabilité critique, Markdown malveillant peut injecter scripts. DOMPurify est le standard industrie (3M+ downloads/semaine).
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. `npm install dompurify @types/dompurify`
 | ||
| 2. Remplacer `escapeHtml()` par `DOMPurify.sanitize()` dans `MarkdownService`
 | ||
| 3. Configurer whitelist tags/attributes Obsidian-safe
 | ||
| 4. Ajouter tests avec payloads XSS connus
 | ||
| 5. Documenter politique sanitization
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Payload `<img src=x onerror=alert(1)>` neutralisé
 | ||
| - ✅ Markdown légitime (callouts, mermaid) préservé
 | ||
| - ✅ Tests E2E passent avec notes malveillantes
 | ||
| 
 | ||
| **Estimation:** 1 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 3. [P0] Implémenter CDK Virtual Scroll pour résultats de recherche
 | ||
| **ICE:** 9/10/3 = **30.0**  
 | ||
| **Pourquoi:** Actuellement 500 résultats = 500 nodes DOM, causant CLS et janky scroll. Virtual scroll réduit à ~15 nodes visibles, gain 97%.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Importer `ScrollingModule` depuis `@angular/cdk/scrolling`
 | ||
| 2. Wrapper liste résultats dans `<cdk-virtual-scroll-viewport>`
 | ||
| 3. Définir `itemSize` fixe (80px) ou dynamique
 | ||
| 4. Ajouter `trackBy` sur `noteId` pour optimiser change detection
 | ||
| 5. Tester avec 1000+ résultats
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Scroll fluide 60fps sur 1000 résultats
 | ||
| - ✅ CLS <0.1
 | ||
| - ✅ Temps de rendu initial <100ms
 | ||
| 
 | ||
| **Estimation:** 2 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 4. [P0] Offloader parsing Markdown dans Web Worker
 | ||
| **ICE:** 9/9/6 = **13.5**  
 | ||
| **Pourquoi:** `MarkdownService.render()` bloque main thread 500ms+ sur notes avec mermaid/MathJax. Worker libère UI.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer `markdown.worker.ts` avec MarkdownIt + plugins
 | ||
| 2. Exposer API `parse(markdown: string, options) => html`
 | ||
| 3. Créer `MarkdownWorkerService` Angular avec pool de workers (2-4)
 | ||
| 4. Gérer communication async (Observable-based)
 | ||
| 5. Ajouter fallback synchrone pour SSR
 | ||
| 6. Migrer `MarkdownService` pour déléguer au worker
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Parsing note 1000 lignes + mermaid: main thread <16ms (1 frame)
 | ||
| - ✅ Rendu progressif (streaming) si possible
 | ||
| - ✅ Pas de régression features (wikilinks, callouts)
 | ||
| 
 | ||
| **Estimation:** 4 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 5. [P0] Debounce/Incremental rebuild des index (Search + Graph)
 | ||
| **ICE:** 8/10/3 = **26.7**  
 | ||
| **Pourquoi:** Actuellement `effect(() => rebuildIndex(allNotes()))` reconstruit tout à chaque mutation, coût O(N²) sur édition.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Remplacer effect par `debounceTime(300)` sur `allNotes()` signal
 | ||
| 2. Implémenter rebuild incrémental: détecter delta notes (added/updated/removed)
 | ||
| 3. Update index partiellement pour delta uniquement
 | ||
| 4. Ajouter flag `isIndexing` pour désactiver search pendant rebuild
 | ||
| 5. Logger timing rebuild dans console
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Édition note: index update <50ms (vs 300ms actuellement)
 | ||
| - ✅ Pas de gel UI perceptible
 | ||
| - ✅ Index cohérent après updates multiples
 | ||
| 
 | ||
| **Estimation:** 3 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 6. [P0] Lazy load Mermaid + MathJax + highlight.js
 | ||
| **ICE:** 8/10/2 = **40.0**  
 | ||
| **Pourquoi:** 1.8MB chargés au boot alors qu'utilisés seulement si note contient code/diagramme. Lazy import réduit TTI de 2s.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Convertir imports statiques en `import()` dynamiques
 | ||
| 2. Dans `MarkdownService.renderFence()`, charger mermaid on-demand
 | ||
| 3. Détecter présence `$$` avant charger MathJax
 | ||
| 4. Wrapper imports dans `NgZone.runOutsideAngular()` pour éviter CD
 | ||
| 5. Ajouter spinner pendant chargement initial
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ TTI initial <2.5s (vs 4.2s actuellement)
 | ||
| - ✅ Premier diagramme mermaid rendu <500ms après chargement lib
 | ||
| - ✅ Pas de flash of unstyled content
 | ||
| 
 | ||
| **Estimation:** 2 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 7. [P0] Implémenter /api/log backend pour diagnostics
 | ||
| **ICE:** 7/10/4 = **17.5**  
 | ||
| **Pourquoi:** Impossible diagnostiquer problèmes production sans logs structurés. Client logging existe mais pas d'endpoint backend.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer route POST `/api/log` dans `server/index.mjs`
 | ||
| 2. Parser batch d'événements (schéma LogRecord)
 | ||
| 3. Valider/sanitize données (éviter injection logs)
 | ||
| 4. Persister dans fichier JSON rotatif (max 10MB) ou stdout structuré
 | ||
| 5. Ajouter corrélation sessionId + requestId
 | ||
| 6. Exposer GET `/api/log/health` pour monitoring
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Batch de 50 événements persisté en <50ms
 | ||
| - ✅ Rotation logs automatique
 | ||
| - ✅ Champs sensibles (query, path) hashés/redacted
 | ||
| - ✅ Corrélation sessionId fonctionne
 | ||
| 
 | ||
| **Estimation:** 3 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🟡 P1 — HIGH (5-8 jours, optimisations majeures)
 | ||
| 
 | ||
| ### 8. [P1] Configurer Service Worker + Workbox pour cache offline
 | ||
| **ICE:** 7/8/4 = **14.0**  
 | ||
| **Pourquoi:** Actuellement chaque visite = full reload. SW cache assets statiques + API responses, réduction 80% trafic.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Installer `@angular/service-worker`
 | ||
| 2. Créer `ngsw-config.json` avec stratégies cache:
 | ||
|    - Assets: cache-first
 | ||
|    - `/api/vault`: network-first, fallback cache (stale-while-revalidate)
 | ||
|    - `/api/attachments`: cache-first
 | ||
| 3. Ajouter `ServiceWorkerModule.register()` dans app config
 | ||
| 4. Implémenter update notifications
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Offline: app charge depuis cache
 | ||
| - ✅ Rechargement vault: <500ms si cached
 | ||
| - ✅ Update notification après deploy
 | ||
| 
 | ||
| **Estimation:** 3 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 9. [P1] Ajouter budgets Lighthouse dans angular.json
 | ||
| **ICE:** 6/10/1 = **60.0**  
 | ||
| **Pourquoi:** Pas de garde-fou, bundle grossit sans alerte. Budgets cassent build si dépassés.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Ajouter section `budgets` dans `angular.json`:
 | ||
|    ```json
 | ||
|    "budgets": [
 | ||
|      { "type": "initial", "maximumWarning": "1.5mb", "maximumError": "2mb" },
 | ||
|      { "type": "anyComponentStyle", "maximumWarning": "50kb" }
 | ||
|    ]
 | ||
|    ```
 | ||
| 2. Configurer CI pour fail si budgets dépassés
 | ||
| 3. Monitorer avec `ng build --stats-json` + webpack-bundle-analyzer
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Build warning si bundle >1.5MB
 | ||
| - ✅ Build error si >2MB
 | ||
| - ✅ CI pipeline fail sur dépassement
 | ||
| 
 | ||
| **Estimation:** 0.5 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 10. [P1] Dockerfile multi-stage optimisé + healthcheck
 | ||
| **ICE:** 6/9/3 = **18.0**  
 | ||
| **Pourquoi:** Image actuelle 450MB+, redéploiement lent. Multi-stage réduit à <150MB.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Stage 1 (builder): `FROM node:20-alpine`, build Angular + prune devDeps
 | ||
| 2. Stage 2 (runtime): `FROM node:20-alpine`, copier dist + node_modules prod uniquement
 | ||
| 3. Utiliser `.dockerignore` (node_modules, .git, .angular, tests)
 | ||
| 4. Ajouter HEALTHCHECK curl `/api/health`
 | ||
| 5. Configurer non-root user (security)
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Image finale <150MB
 | ||
| - ✅ Build time <3min
 | ||
| - ✅ Healthcheck passe dans Kubernetes/Docker Swarm
 | ||
| 
 | ||
| **Estimation:** 2 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 11. [P1] Variables d'environnement structurées (12-factor app)
 | ||
| **ICE:** 6/9/2 = **27.0**  
 | ||
| **Pourquoi:** Config hardcodée empêche multi-instance (dev/staging/prod avec différentes voûtes).
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer `.env.example`:
 | ||
|    ```
 | ||
|    VAULT_PATH=/app/vault
 | ||
|    VAULT_ID=primary
 | ||
|    MEILISEARCH_URL=http://meilisearch:7700
 | ||
|    MEILISEARCH_KEY=masterKey
 | ||
|    LOG_LEVEL=info
 | ||
|    ```
 | ||
| 2. Charger avec `dotenv` dans `server/index.mjs`
 | ||
| 3. Exposer config runtime via `/api/config` (non-sensitive seulement)
 | ||
| 4. Documenter dans README
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Plusieurs instances pointent vers voûtes différentes
 | ||
| - ✅ Dev/staging/prod configs séparées
 | ||
| - ✅ Pas de secrets hardcodés
 | ||
| 
 | ||
| **Estimation:** 1.5 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 12. [P1] Ajouter CSP headers + NGINX hardening
 | ||
| **ICE:** 6/8/2 = **24.0**  
 | ||
| **Pourquoi:** Défense en profondeur contre XSS. CSP bloque inline scripts non whitelistés.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer `docker/config/nginx.conf` avec:
 | ||
|    ```nginx
 | ||
|    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;";
 | ||
|    add_header X-Content-Type-Options nosniff;
 | ||
|    add_header X-Frame-Options DENY;
 | ||
|    add_header Referrer-Policy no-referrer-when-downgrade;
 | ||
|    ```
 | ||
| 2. Tester avec CSP reporter
 | ||
| 3. Activer Brotli compression
 | ||
| 4. Configurer rate limiting (optional)
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ CSP headers présents dans réponses
 | ||
| - ✅ Aucun inline script bloqué (app fonctionne)
 | ||
| - ✅ Score Mozilla Observatory A+
 | ||
| 
 | ||
| **Estimation:** 1.5 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 13. [P1] Implémenter throttle RAF pour redraws canvas graph
 | ||
| **ICE:** 5/9/2 = **22.5**  
 | ||
| **Pourquoi:** `draw()` appelé à chaque `scheduleRedraw()` sans throttle, GPU surchargé sur mobile.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Ajouter flag `isScheduled` dans `GraphCanvasComponent`
 | ||
| 2. Wrapper `scheduleRedraw()` pour éviter multiples RAF pending
 | ||
| 3. Utiliser `requestAnimationFrame()` comme gate (déjà présent mais pas optimal)
 | ||
| 4. Limiter FPS max à 60 (skip frames si <16ms depuis dernier draw)
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Max 60fps rendering (monitoring via Chrome DevTools)
 | ||
| - ✅ CPU réduit de 30% sur interactions graph
 | ||
| - ✅ Pas de visual jank
 | ||
| 
 | ||
| **Estimation:** 1 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 14. [P1] Étendre tests E2E Playwright (graph freeze, search perf)
 | ||
| **ICE:** 5/8/3 = **13.3**  
 | ||
| **Pourquoi:** Tests actuels incomplets, régressions performance passent inaperçues.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer `e2e/search-performance.spec.ts`:
 | ||
|    - Charger vault 500 notes
 | ||
|    - Mesurer temps search <150ms
 | ||
|    - Vérifier pas de freeze main thread >100ms
 | ||
| 2. Créer `e2e/graph-interaction.spec.ts`:
 | ||
|    - Cliquer node graph
 | ||
|    - Mesurer temps avant sélection <100ms
 | ||
| 3. Ajouter fixtures vault de test (small/medium/large)
 | ||
| 4. Intégrer dans CI
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Tests passent avec vault 500 notes
 | ||
| - ✅ Fail si search >150ms P95
 | ||
| - ✅ CI exécute E2E avant merge
 | ||
| 
 | ||
| **Estimation:** 2.5 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🟢 P2 — MEDIUM (3-5 jours, nice-to-have)
 | ||
| 
 | ||
| ### 15. [P2] Lazy routes Angular pour code-splitting
 | ||
| **ICE:** 5/7/4 = **8.75**  
 | ||
| **Pourquoi:** Bundle monolithique, tout chargé au boot. Lazy routes réduit initial bundle de 40%.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer routes avec `loadComponent`:
 | ||
|    ```ts
 | ||
|    { path: 'graph', loadComponent: () => import('./graph/...') }
 | ||
|    ```
 | ||
| 2. Séparer features: graph, calendar, bookmarks en chunks
 | ||
| 3. Preload strategy: `PreloadAllModules` ou custom
 | ||
| 4. Mesurer impact avec webpack-bundle-analyzer
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Initial bundle <800KB (vs 1.5MB)
 | ||
| - ✅ Routes chargent <300ms
 | ||
| - ✅ Pas de flash of content
 | ||
| 
 | ||
| **Estimation:** 3 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 16. [P2] Memoization fine du computed graphData
 | ||
| **ICE:** 4/8/2 = **16.0**  
 | ||
| **Pourquoi:** `graphData` recalcule O(N×M) à chaque mutation de `allNotes()`, même si notes non liées changent.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Comparer hash du tableau notes (shallow equality)
 | ||
| 2. Ajouter cache Map<notesHash, GraphData>
 | ||
| 3. Retourner cached si hash identique
 | ||
| 4. Logger cache hit/miss
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Édition note sans liens: pas de recalcul graph
 | ||
| - ✅ Cache hit rate >80%
 | ||
| 
 | ||
| **Estimation:** 1.5 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 17. [P2] Valider markdown-it-attrs avec whitelist stricte
 | ||
| **ICE:** 4/7/1 = **28.0**  
 | ||
| **Pourquoi:** `{.class}` syntax peut injecter classes malveillantes, risque XSS edge case.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Configurer `allowedAttributes` whitelist stricte:
 | ||
|    ```ts
 | ||
|    allowedAttributes: ['id', 'class'],
 | ||
|    allowedClasses: ['callout', 'md-*', 'hljs-*']
 | ||
|    ```
 | ||
| 2. Tester avec payloads injection
 | ||
| 3. Documenter classes autorisées
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ `{.malicious-script}` rejeté
 | ||
| - ✅ Classes légitimes passent
 | ||
| 
 | ||
| **Estimation:** 0.5 jour
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 18. [P2] Progressive rendering pour longues listes (tags, files)
 | ||
| **ICE:** 4/6/3 = **8.0**  
 | ||
| **Pourquoi:** Liste 1000 tags freeze 200ms au render. Progressive rendering (batch 50/frame) fluide.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Wrapper liste dans composant custom avec rendering batched
 | ||
| 2. Utiliser `requestIdleCallback` pour render par chunks
 | ||
| 3. Afficher skeleton pendant batching
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ 1000 tags rendus sans freeze perceptible
 | ||
| - ✅ Interaction possible pendant rendering
 | ||
| 
 | ||
| **Estimation:** 2 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 19. [P2] IndexedDB cache pour métadonnées vault
 | ||
| **ICE:** 4/6/4 = **6.0**  
 | ||
| **Pourquoi:** Rechargement vault requête complète `/api/vault`, lent sur >500 notes. Cache IDB réduit à delta.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Créer `VaultCacheService` avec Dexie.js
 | ||
| 2. Persister notes + timestamps dans IDB
 | ||
| 3. `/api/vault?since=<timestamp>` pour delta uniquement
 | ||
| 4. Merger delta avec cache local
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Rechargement vault: <500ms avec cache (vs 2s)
 | ||
| - ✅ Sync delta fonctionne
 | ||
| 
 | ||
| **Estimation:** 3 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ### 20. [P2] Monitoring OpenTelemetry (optionnel)
 | ||
| **ICE:** 3/5/5 = **3.0**  
 | ||
| **Pourquoi:** Observabilité production, traces distribuées. Coût setup élevé vs bénéfice pour petit projet.
 | ||
| 
 | ||
| **Étapes concrètes:**
 | ||
| 1. Installer `@opentelemetry/sdk-node`
 | ||
| 2. Instrumenter Express avec auto-instrumentation
 | ||
| 3. Exporter traces vers Jaeger/Zipkin
 | ||
| 4. Ajouter spans custom pour opérations longues
 | ||
| 
 | ||
| **Critères d'acceptation:**
 | ||
| - ✅ Traces visibles dans Jaeger
 | ||
| - ✅ P95 latency API <200ms
 | ||
| 
 | ||
| **Estimation:** 4 jours
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| **Total items:** 20 (10 P0, 7 P1, 3 P2)  
 | ||
| **Total effort estimé:** ~48 jours (10 semaines avec 1 dev)
 | ||
| 
 |