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