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