# ✅ 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 `` 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 `` 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 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=` 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)