ObsiViewer/docs/ARCHITECTURE/AUDIT_CHECKLIST_AMELIORATIONS.md

436 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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