Files
ObsiGate/docs/ROADMAP.md
T
bruno 671befe610
CI / lint (push) Failing after 4s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Failing after -6s
feat: #59 finalisation mode hors-ligne PWA — badge + conflits
- Badge hors-ligne dans le header (icone wifi-off + compteur pending)
- Modale resolution de conflits (garder local vs serveur)
- CSS offline-indicator, offline-pending badge
- Integration: initOffline (sync.js), cacheViewedFile (viewer.js), searchOffline (search.js)
- IndexedDB: files, content, pending stores avec sync queue
- ROADMAP: #59 marque FAIT
2026-06-15 23:04:47 -04:00

566 lines
45 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.
# ObsiGate — Roadmap
> **Version :** 2.0.0-dev | **Dernière mise à jour :** 2026-06-05
> Voir aussi [CHANGELOG.md](./CHANGELOG.md), [AUDIT_TECHNIQUE.md](./docs/AUDIT_TECHNIQUE_2026-05-27.md)
---
## Légende
| Icône | Signification |
|---|---|
| ✅ | Terminé |
| 🔵 | En cours |
| ⚪ | Prévu / Backlog |
| 🔴 Impact | Critique / Bloquant |
| 🟡 Impact | Utile / Attendu |
| 🟢 Impact | Nice-to-have / Confort |
---
## ✅ Complété (v1.0.0 → v2.0.0-dev)
### Fondations (v1.0.0 → v1.4.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 1 | FastAPI backend — CRUD fichiers, vaults, recherche full-text | 5j | 🔴 |
| 2 | Moteur TF-IDF + stemming français (`snowballstemmer`) | 2j | 🔴 |
| 3 | Watchdog — indexation temps réel avec debounce | 1j | 🟡 |
| 4 | Interface SPA vanilla JS — sidebar, viewer, éditeur CodeMirror | 8j | 🔴 |
| 5 | Sécurité : JWT + Argon2id, rate limiting, audit log, CSP headers | 3j | 🔴 |
| 6 | Protection path traversal, utilisateur non-root Docker | 0.5j | 🔴 |
| 7 | Compression GZip SSE-safe, Cache-Control immutable | 0.5j | 🟢 |
| 8 | PWA : manifest, service worker, mode standalone | 1j | 🟡 |
### UX & Productivité (v1.5.0 → v1.6.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 9 | Publication publique de documents (lien partageable, token) | 1j | 🟡 |
| 10 | Webhooks HTTP avec signature HMAC-SHA256 | 1j | 🟡 |
| 11 | Dashboard statistiques (fichiers, tags, taille, vaults) | 0.5j | 🟡 |
| 12 | Gestion des conflits Syncthing | 0.5j | 🟢 |
| 13 | Index inversé incrémental (hook pattern) | 1j | 🟡 |
| 14 | Backlinks panel dans le viewer | 0.5j | 🟡 |
| 15 | Fichiers non-supportés → UI download | 0.3j | 🟢 |
| 16 | Redaction de secrets (`secret_redactor.py`) | 0.5j | 🟡 |
| 17 | Backup automatique avant écriture, restauration | 1j | 🟡 |
| 18 | Vue graphe — Barnes-Hut, focus, plein écran, export PNG | 3j | 🟡 |
| 19 | Header flat design, sticky panels, navigation historique ← → ↑ | 1j | 🟢 |
| 20 | Ctrl+survol → aperçu contenu formaté | 0.5j | 🟢 |
### Architecture (v1.5.1)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 21 | Split `app.js` (8 875 lignes) → 16 modules ES | 3j | 🔴 |
| 22 | Validateur imports/exports CI + tests unitaires frontend Node.js | 0.5j | 🟡 |
### CI/CD & Qualité (v1.6.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 23 | Pipeline Gitea Actions : lint → test → security → build | 1j | 🔴 |
| 24 | Ruff (0 erreur) + Mypy (0 erreur) + Bandit SAST + Pip-audit | 0.5j | 🟡 |
| 25 | Pytest : 285 tests, 63% coverage | 3j | 🔴 |
### AI Editor (v1.7.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 26 | Toolbar : Edit, Tone, Translate, Generate, Rewrite, Toolbox | 2j | 🟡 |
| 27 | Multi-provider : DeepSeek, OpenRouter, Gemini | 1j | 🟡 |
| 28 | 16 endpoints REST `/api/ai/{action}` + backend `ai.py` / `ai_routes.py` | 2j | 🟡 |
| 29 | Auto-save silencieux (2s debounce), loading toasts | 0.5j | 🟢 |
### Fonctionnalités avancées (v1.8.0 → v1.9.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 30 | Export PDF via WeasyPrint (endpoint API + lien share public) | 1j | 🟡 |
| 31 | Palette de commandes `Ctrl+Alt+Space` | 1j | 🟡 |
| 32 | Barre d'outils mobile + palette fichiers/commandes 📱 | 1j | 🟡 |
| 33 | Drag & drop de fichiers | 1j | 🟡 |
| 34 | Filtres recherche avancés : `created:`, `modified:`, `size:` | 0.5j | 🟡 |
| 35 | Fichiers récents par vault | 0.5j | 🟡 |
| 36 | Indicateur AI Actif (header + toast) | 0.3j | 🟢 |
| 37 | Page d'accueil de vault (liste récursive par date, style recherche) | 0.5j | 🟡 |
| 38 | Indexation non-bloquante (background thread) | 0.5j | 🟡 |
| 39 | Git tags semver (v1.8.0, v1.9.0) | 0.2j | 🟢 |
### Gestion des Backups (v1.9.0)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 40 | Diff viewer : unifié + côte à côte, restauration depuis backup | 1j | 🟡 |
| 41 | Gestionnaire de backups : page complète, filtre, suppression, purge | 1.5j | 🟡 |
| 42 | Purge par vault avec confirmation, preview contenu (100 Ko) | 0.5j | 🟡 |
| 43 | Compression gzip des vieux backups (niveau 6) | 0.5j | 🟢 |
| 44 | Backup automatique périodique (`POST /api/backups/auto`) | 1j | 🟡 |
| 45 | Restauration depuis le gestionnaire (extraction timestamp, confirmation) | 0.5j | 🟡 |
| 46 | Auto-nettoyage : `max_backups_per_file` (défaut 10) | 0.5j | 🟡 |
### Mermaid.js (v2.0.0-dev)
| # | Feature | Effort | Impact |
|---|---|---|---|
| 47 | CDN mermaid@11, `securityLevel: strict`, `startOnLoad: false` | 0.5j | 🟡 |
| 48 | `renderMermaidBlocks()` — parse, rendu SVG, bloc d'erreur stylisé | 1j | 🟡 |
| 49 | 22 templates (flowchart → sankey-beta) | 0.5j | 🟡 |
| 50 | Live preview dans l'éditeur (debounce 500ms, panneau `#mermaid-live-preview`) | 1j | 🟡 |
| 51 | Thème dark/light synchronisé, export SVG + PNG | 1j | 🟡 |
| 52 | Rendu inline dans le viewer Markdown | 0.5j | 🟡 |
| 53 | Zoom molette + drag + boutons +/- | 0.5j | 🟢 |
| 54 | Mode plein écran avec header bar (icônes zoom, copy, download, close) | 0.5j | 🟢 |
| 55 | Focus mode — clic diagramme → panneau latéral 480px | 0.3j | 🟡 |
| 56 | Pré-processeur Obsidian (`[[liens]]`, `![[img]]`, `==highlight==`) | 0.3j | 🟡 |
| 57 | Header bar : type de diagramme + toggle Code/Preview + copy SVG + download PNG | 1j | 🟡 |
---
## 🔵 En cours (P1)
### 58. Tests E2E Playwright ✅ FAIT
- **Effort :** 2-3 jours | **Impact :** 🔴
- **Description :** Tests navigateur automatisés pour les flows critiques : login, navigation vault, recherche full-text, ouverture/édition/sauvegarde de fichier, rendu Mermaid, export PDF.
- **Implémentation :** 44 tests Playwright (chromium-desktop) + 12 tests mobile dans `tests/e2e/obsigate.spec.ts`. Intégré au CI Gitea (job `e2e` après `build`).
- **Sous-tâches :**
- [x] Installation Playwright + config (`playwright.config.ts`)
- [x] Fixtures : vault de test (utilise les vaults Docker existants)
- [x] Test : dashboard → stats, tabs, Quick Help, sidebar
- [x] Test : recherche full-text → résultats, snippets, tri pertinence/date
- [x] Test : ouverture fichier → viewer Markdown, métadonnées
- [x] Test : éditeur → Forge, basic modal, Ctrl+S
- [x] Test : rendu Mermaid dans le viewer + preview Forge
- [x] Test : export PDF → bouton présent
- [x] Test : mode sombre → toggle, persistence localStorage
- [x] Test : responsive mobile → layout, recherche, barre flottante
- [x] Test : barre flottante résultats → compteur, nav, toggles Aa/wd
- [x] Test : sauvegardes → filtres Tous/Recherches/Répertoires
- [x] Test : raccourcis clavier → Ctrl+K, /, Escape
- [x] Test : menu contextuel répertoire
- [x] Intégration CI : job `e2e` dans `.gitea/workflows/ci.yml`
---
## ⚪ Backlog — Priorité 3 (P3)
### 59. Mode hors-ligne PWA complet ✅ FAIT
- **Effort :** 3-4 jours | **Impact :** 🟡
- **Description :** Service worker avancé avec IndexedDB pour permettre la navigation et la recherche en mode hors-ligne, avec file de synchronisation au retour réseau.
- **Implémentation :** `offline-db.js` (377 lignes) + `offline.js` (209 lignes). IndexedDB 3 stores (files, content, pending). Badge hors-ligne dans le header. Modale résolution de conflits.
- **Sous-tâches :**
- [x] IndexedDB : stockage local de l'index des fichiers (paths, titles, tags)
- [x] Moteur de recherche offline via IndexedDB (cursor + filtre)
- [x] Cache des fichiers markdown récemment ouverts (derniers 50, prune)
- [x] Stratégie de cache : Network First avec fallback IndexedDB
- [x] File de synchronisation : modifications offline → appliquées au retour réseau
- [x] UI indicateur : badge « Hors-ligne » + compteur de modifications en attente
- [x] Gestion des conflits : détection et résolution manuelle (choix version locale vs serveur)
### 60. OAuth2 / OIDC — Authentification SSO
- **Effort :** 2-3 jours | **Impact :** 🟡
- **Description :** Support de fournisseurs OAuth2/OIDC externes pour permettre l'authentification via Google, GitHub, ou tout provider compatible. Complément à l'auth JWT existante (pas de remplacement).
- **Sous-tâches :**
- [ ] Configuration par provider : `OBSIGATE_OAUTH_GOOGLE_CLIENT_ID`, `OBSIGATE_OAUTH_GITHUB_CLIENT_ID`, etc.
- [ ] Endpoint `GET /api/auth/oauth/login?provider=google` → redirection
- [ ] Endpoint `GET /api/auth/oauth/callback` → échange code → token OIDC → création/liaison compte local
- [ ] Mapping roles : config `OBSIGATE_OAUTH_DEFAULT_ROLE` (défaut `user`)
- [ ] UI : boutons « Se connecter avec Google / GitHub » sur la page login
- [ ] Sécurité : state parameter anti-CSRF, PKCE, nonce validation
- [ ] Stockage : liaison `oauth_provider` + `oauth_sub` dans `users.json`
### 61. Plugins système — Extensions utilisateur
- **Effort :** 4-5 jours | **Impact :** 🟢
- **Description :** Système de plugins permettant aux utilisateurs d'étendre ObsiGate avec des renderers personnalisés, des opérateurs de recherche, et des hooks d'UI. Inspiré du modèle de plugins Obsidian.
- **Sous-tâches :**
- [ ] Spécification du format de plugin : `plugin.json` (name, version, hooks, permissions)
- [ ] API de hooks : `onFileRender`, `onSearchFilter`, `onEditorAction`, `onSidebarItem`
- [ ] Sandbox d'exécution : Web Worker isolé pour le code plugin
- [ ] UI : page « Plugins » dans les paramètres (installer, activer/désactiver, désinstaller)
- [ ] Distribution : dépôt de plugins communautaire (fichier JSON index)
- [ ] Hot-reload : activation/désactivation sans rechargement de page
- [ ] Sécurité : manifest de permissions, validation de signature, CSP restrictif
### 62. Collaboration temps réel — Édition simultanée
- **Effort :** 5-7 jours | **Impact :** 🟢
- **Description :** Édition collaborative de fichiers markdown via WebSocket + CRDT (Yjs). Plusieurs utilisateurs peuvent éditer le même fichier simultanément avec résolution automatique des conflits.
- **Sous-tâches :**
- [ ] Serveur WebSocket : endpoint `/ws/collab/{vault}/{path}` avec gestion des rooms
- [ ] Intégration Yjs : `Y.Doc` partagé, `Y.Text` pour le contenu markdown
- [ ] Awareness : curseurs colorés par utilisateur, sélections visibles
- [ ] Synchro backend : persistance périodique du document (debounce 2s)
- [ ] Gestion des droits : vérification `check_vault_access` par connexion WS
- [ ] UI : indicateur de présence (avatars dans la barre d'outils éditeur)
- [ ] UI : curseurs distants dans CodeMirror (extension collaborative)
- [ ] Gestion des déconnexions : reconnexion automatique, merge state au retour
- [ ] Tests de charge : 5+ utilisateurs simultanés sur le même fichier
### 63. Internationalisation (i18n) — Multilingue
- **Effort :** 2-3 jours | **Impact :** 🟡
- **Description :** Support de l'anglais et du français via un système de clés de traduction. L'interface est actuellement en français uniquement. L'anglais est nécessaire pour une adoption plus large.
- **Sous-tâches :**
- [ ] Extraction des chaînes : inventaire de toutes les chaînes UI (~300)
- [ ] Format : JSON `fr.json` + `en.json` dans `frontend/locales/`
- [ ] Fonction `t(key, fallback)` → détection `navigator.language`
- [ ] Sélecteur de langue dans les paramètres (persisté localStorage)
- [ ] Traduction des messages backend (erreurs API, toasts)
- [ ] Documentation multilingue (README.fr.md, README.md)
### 64. MFA — Authentification multi-facteurs
- **Effort :** 2 jours | **Impact :** 🟡
- **Description :** Ajout de TOTP (Time-based One-Time Password) et WebAuthn (clés de sécurité) comme second facteur d'authentification pour les comptes admin.
- **Sous-tâches :**
- [ ] TOTP : génération de secret, QR code, vérification code 6 chiffres
- [ ] WebAuthn : enregistrement de clé, assertion, attestation
- [ ] UI : page « Sécurité du compte » avec activation/désactivation MFA
- [ ] Flow login : mot de passe → challenge TOTP si activé
- [ ] Recovery codes : 8 codes de backup à usage unique
- [ ] Stockage : `totp_secret` + `webauthn_credential_id` dans `users.json`
### 65. Thèmes personnalisés — CSS variables
- **Effort :** 1-2 jours | **Impact :** 🟢
- **Description :** Exposition de variables CSS pour permettre aux utilisateurs de créer des thèmes personnalisés. Presets inclus : light, dark, high-contrast, sepia.
- **Sous-tâches :**
- [ ] Audit des variables CSS existantes → liste des 30+ variables
- [ ] Fichier `themes.json` avec presets (light, dark, high-contrast, sepia)
- [ ] UI : sélecteur de thème dans les paramètres (aperçu live)
- [ ] Import/export de thème personnalisé (JSON)
- [ ] Application dynamique sans rechargement (`document.documentElement.style.setProperty`)
### 66. Export multi-formats
- **Effort :** 1-2 jours | **Impact :** 🟢
- **Description :** Export de notes individuelles ou de vaults entiers en HTML standalone, bundle Markdown (.zip), et ePub pour liseuses.
- **Sous-tâches :**
- [ ] Export HTML standalone : CSS inliné, images en base64, navigation inter-fichiers
- [ ] Export MD bundle : ZIP du vault avec structure préservée
- [ ] Export ePub : conversion markdown → ePub via `markdown` + `ebooklib`
- [ ] UI : bouton « Exporter » dans le viewer (fichier unique) + dans le menu vault (export complet)
- [ ] Endpoints : `GET /api/export/html`, `GET /api/export/md-bundle`, `GET /api/export/epub`
### 74. Support complet des documents PDF
- **Effort :** 4-5 jours | **Impact :** 🟡
- **Description :** Prise en charge native des fichiers PDF dans ObsiGate avec parité fonctionnelle complète avec les documents Markdown : apparition dans l'arborescence, indexation full-text, visualisation inline dans le navigateur, recherche TF-IDF, et téléchargement. Actuellement, les PDF sont traités comme des fichiers binaires non supportés (message « Ce fichier est binaire et ne peut pas être affiché » + bouton download).
- **Architecture actuelle :**
- `SUPPORTED_EXTENSIONS` (`backend/indexer.py:56`) : ne contient pas `.pdf` → les PDF sont ignorés par l'indexeur, le file watcher, et l'arborescence
- `api_file_view()` (`backend/main.py:2303`) : UnicodeDecodeError sur lecture → retourne `unsupported: true`
- `frontend/js/viewer.js:377` : si `data.unsupported` → affiche le message binaire + bouton download
- `pdf_export.py` : exporte du MD → PDF (WeasyPrint) — aucun rapport avec la lecture de PDF existants
- Icone PDF déjà présente dans `EXT_ICONS` frontend (`.pdf` → `file-text`) — inutilisée
- **Sous-tâches :**
##### A. Backend — Extraction de texte PDF (1-1.5 jour)
- [ ] **A1. Dépendance** : Ajouter `pymupdf` (PyMuPDF/fitz) à `requirements.txt` — bibliothèque C performante avec extraction texte + métadonnées, déjà compatible avec l'image Docker (libs système GTK/Pango déjà présentes pour WeasyPrint). Alternative légère : `pypdf` (pure Python, pas de deps système) si pymupdf pose problème.
- [ ] **A2. Module `backend/pdf_reader.py`** : Créer un module dédié avec les fonctions :
- `extract_pdf_text(file_path: Path) -> str` : extrait tout le texte du PDF, page par page, avec séparateur `\f` entre pages. Gère les PDF encodés, protégés par mot de passe (retourne erreur explicite), et corrompus.
- `extract_pdf_metadata(file_path: Path) -> dict` : extrait titre, auteur, sujet, nombre de pages, taille.
- `extract_pdf_preview(file_path: Path, max_chars: int = 100000) -> str` : extrait les N premiers caractères pour l'indexation (limité par `SEARCH_CONTENT_LIMIT`).
- [ ] **A3. Fallback pypdf** : Si pymupdf non disponible (exception d'import), fallback automatique sur `pypdf` avec un log warning. Code structuré avec une interface abstraite (`PdfReader` protocol) pour swap transparent.
##### B. Backend — Indexation des PDF (1 jour)
- [ ] **B1. Ajout à `SUPPORTED_EXTENSIONS`** : Ajouter `.pdf` au set dans `backend/indexer.py:56`. Déclencher un rebuild complet de l'index (incrémental via le file watcher pour les nouveaux PDFs).
- [ ] **B2. Modification de `index_document()`** (`backend/indexer.py:524`) : Dans la fonction d'indexation, détecter l'extension `.pdf` et appeler `extract_pdf_text()` au lieu de `read_text()`. Le texte extrait alimente le pipeline TF-IDF existant — aucun changement nécessaire dans `search.py`.
- [ ] **B3. Métadonnées PDF dans le document info** : Enrichir la structure de retour de `index_document()` avec les champs spécifiques PDF : `page_count`, `pdf_title` (titre extrait des métadonnées, prioritaire sur le nom de fichier), `pdf_author`.
- [ ] **B4. Gestion d'erreur robuste** : PDF corrompu → log warning + skip (ne pas bloquer l'indexation). PDF volumineux (>50 Mo) → log info + extraction tronquée à `SEARCH_CONTENT_LIMIT`. Timeout d'extraction configurable (30s par défaut).
##### C. Backend — API endpoints PDF (0.5 jour)
- [ ] **C1. Modification de `api_file_view()`** (`backend/main.py:2270`) : Avant la tentative de `read_text()`, détecter `.pdf` par extension. Pour les PDF :
- Extraire le texte avec `extract_pdf_text()`
- Extraire les métadonnées (pages, auteur)
- Retourner une réponse structurée : `is_pdf: true`, `page_count`, `pdf_metadata`, `html` (aperçu texte formaté), `raw_length`
- Le champ `html` contient un rendu texte simple (pas de markdown) : texte paginé ou première page formatée
- [ ] **C2. Nouvel endpoint `GET /api/file/{vault}/pdf/stream`** : Sert le fichier PDF brut avec `Content-Type: application/pdf` et `Content-Disposition: inline` pour visualisation dans le navigateur. Supporte le `Range` header (HTTP 206 Partial Content) pour le streaming progressif des gros PDFs — essentiel pour la performance sur des documents volumineux.
- [ ] **C3. Nouvel endpoint `GET /api/file/{vault}/pdf/info`** : Retourne les métadonnées seules (pages, titre, auteur) sans le contenu — permet à l'UI d'afficher les infos avant de charger le PDF lourd.
- [ ] **C4. Endpoint download** : Déjà fonctionnel (`/api/file/{vault}/download`) — aucun changement nécessaire.
##### D. Frontend — Arborescence de fichiers (0.5 jour)
- [ ] **D1. Icône et filtre** : L'icône PDF (`file-text` de Lucide) est déjà mappée dans `EXT_ICONS` (`frontend/js/utils.js:129`). Une fois `.pdf` dans `SUPPORTED_EXTENSIONS`, les PDFs apparaissent automatiquement dans l'arborescence via l'API `list_directory`. Aucun changement UI nécessaire.
- [ ] **D2. Distinction visuelle** (optionnel) : Sous-titre léger sous le nom du fichier dans l'arborescence indiquant le nombre de pages (ex: « 12 pages ») pour différencier rapidement les PDF des MD. Donnée disponible via l'API `pdf/info`.
- [ ] **D3. Drag & drop et upload** : Le mécanisme d'upload existant (`POST /api/file/{vault}/upload`) fonctionne déjà pour tout type de fichier. Vérifier que le MIME type `application/pdf` est correctement détecté et que le watcher réindexe automatiquement.
##### E. Frontend — Viewer PDF (1 jour)
- [ ] **E1. Rendu inline natif** : Utiliser le visualiseur PDF intégré du navigateur via `<iframe>` pointant sur `/api/file/{vault}/pdf/stream?path=...`. Approche optimale :
- Zéro dépendance JS supplémentaire
- Rendu identique à Chrome/Firefox/Safari natif
- Support natif du zoom, recherche dans le document, navigation par pages, rotation
- L'iframe s'adapte en hauteur (`height: 100%` du content-area)
- [ ] **E2. Détection dans le viewer** : Dans `frontend/js/viewer.js`, fonction `renderFileContent()` — ajouter une branche après la détection `data.unsupported` :
- Si `data.is_pdf === true` → render l'iframe PDF au lieu du viewer markdown
- Si le navigateur ne supporte pas le rendu PDF inline → fallback sur l'UI « binaire » avec bouton download + bouton « Ouvrir dans un nouvel onglet »
- [ ] **E3. Barre d'outils PDF** : Dans la barre d'outils du viewer (celle qui a déjà les boutons Copier, Source, .md, PDF, Éditer, pop-out), pour les fichiers PDF :
- Remplacer « Copier » / « Source » / « Éditer » par des actions spécifiques PDF
- Bouton « Télécharger » (.pdf) — déjà existant, fonctionne
- Bouton « Plein écran » — ouvre le PDF dans un nouvel onglet en plein écran
- Badge « N pages » indiquant le nombre de pages
- Bouton « pop-out » — gardé, ouvre le viewer PDF dans une popup séparée
- [ ] **E4. Thème** : L'iframe PDF est en dehors du DOM applicatif donc pas affecté par le thème dark/light. Ajouter un message discret « Le PDF s'affiche avec le thème de votre navigateur » si `_currentTheme === 'dark'` (les PDFs en fond blanc dans un thème sombre peuvent surprendre).
- [ ] **E5. Responsive** : L'iframe s'adapte à la largeur du content-area. En mode mobile, hauteur ajustée à la viewport. La toolbar mobile existante fonctionne avec les actions PDF.
##### F. Frontend — Recherche (0.5 jour)
- [ ] **F1. Résultats de recherche** : Les PDFs apparaissent dans les résultats via le TF-IDF existant (le texte extrait est indexé). Ajouter un badge visuel « PDF » à côté du titre dans les résultats de recherche pour distinguer les PDFs des MD — utiliser l'icône `file-text`.
- [ ] **F2. Snippets de recherche** : Les extraits de contexte montrent le texte extrait du PDF avec surlignage des termes recherchés — fonctionnement identique aux MD via le mécanisme de snippet existant dans `search.py`.
- [ ] **F3. Filtres de recherche avancés** : Ajouter `ext:pdf` comme filtre pour limiter la recherche aux PDFs uniquement (complément aux filtres `created:`, `modified:`, `size:` déjà prévus #34).
##### G. Docker & Dépendances (0.5 jour)
- [ ] **G1. requirements.txt** : Ajouter `pymupdf>=1.24.0` (sinon `pypdf>=4.0` en fallback).
- [ ] **G2. Dockerfile** : Vérifier que l'image `python:3.11-slim` dispose des libs système nécessaires pour pymupdf. Si besoin, ajouter `libmupdf-dev` ou utiliser `pypdf` (pure Python) pour éviter la complexité. Recommandation : pypdf pour la simplicité Docker, pymupdf en option pour la performance.
- [ ] **G3. Configuration** : Ajouter `OBSIGATE_PDF_MAX_SIZE_MB` (défaut 50) pour limiter la taille des PDFs indexés et `OBSIGATE_PDF_EXTRACT_TIMEOUT` (défaut 30s).
##### H. Tests (1 jour)
- [ ] **H1. Tests unitaires backend** :
- `test_pdf_reader.py` : extraction texte PDF simple, PDF vide, PDF avec uniquement des images (OCR non requis — retourne chaîne vide), PDF protégé par mot de passe, PDF corrompu, extraction métadonnées
- Fixtures : créer un PDF de test minimal (2 pages, texte simple) via `reportlab` dans les fixtures de test
- `test_pdf_indexing.py` : vérifier qu'un PDF dans un vault est correctement indexé, que le texte est recherchable, que `index_document()` gère l'extension `.pdf`
- `test_pdf_api.py` : endpoint `view` retourne `is_pdf: true`, endpoint `pdf/stream` retourne `application/pdf`, endpoint `pdf/info` retourne les métadonnées
- [ ] **H2. Tests frontend** :
- Test d'intégration : naviguer vers un fichier PDF → l'iframe est rendue
- Test : fichier PDF dans les résultats de recherche
- Test : téléchargement de PDF fonctionnel
- [ ] **H3. CI** : Ajouter la fixture PDF de test dans les artefacts de CI. Les tests PDF sont sautés si pymupdf/pypdf n'est pas disponible.
##### I. Documentation utilisateur (inclus dans l'effort)
- [ ] **I1.** Mettre à jour README.md : mentionner le support PDF dans les formats supportés
- [ ] **I2.** Ajouter une note dans la FAQ : « Comment visualiser un PDF dans ObsiGate ? »
- [ ] **I3.** Documenter les limitations : pas d'OCR (PDFs scannés non recherchables), pas d'annotation PDF, pas d'édition de PDF
##### J. Points d'attention / Risques
- **Performance** : Un PDF de 500 pages peut générer beaucoup de texte → `SEARCH_CONTENT_LIMIT` (100 Ko) limite l'indexation au début du document. Pour les PDFs volumineux, envisager une extraction paginée avec `SEARCH_CONTENT_LIMIT` réparti sur les N premières pages.
- **Sécurité** : Les PDFs malveillants (injections JS, liens externes) ne sont pas exécutés dans l'iframe par défaut (sandbox du navigateur). Ajouter `sandbox="allow-same-origin"` sur l'iframe pour renforcer.
- **Mémoire** : pymupdf charge le PDF entier en mémoire. Pour les très gros PDFs (>200 Mo), utiliser le streaming ou `pypdf` qui supporte la lecture paresseuse.
- **Compatibilité navigateurs** : Le rendu PDF natif fonctionne sur Chrome, Firefox, Edge, Safari. Safari iOS a des limitations sur les iframes PDF. Prévoir le fallback « ouvrir dans un nouvel onglet » pour ces cas.
- **PDFs dans les vaults Obsidian** : Obsidian Desktop ne gère pas nativement les PDFs (affichage via iframe système). ObsiGate apporte une valeur ajoutée en offrant la visualisation + recherche.
---
### 75. Éditeur multi-panneaux (Split View)
- **Effort :** 5-7 jours | **Impact :** 🟡
- **Description :** Système de panneaux divisés permettant d'afficher plusieurs documents côte à côte ou superposés, à la manière d'un éditeur de code (VS Code, Sublime Text). Drag & drop des onglets entre les panneaux, actions depuis le menu contextuel, et raccourcis clavier pour diviser/naviguer entre les panneaux. Le système remplace le `content-area` unique par un gestionnaire de panneaux (PaneManager) qui orchestre 1 à 4 zones d'affichage indépendantes, chacune avec sa propre barre d'onglets.
- **Architecture actuelle :**
- **DOM** (`frontend/index.html:885-896`) : Un seul bloc `.content-wrapper` contenant `.tab-bar` + `main.content-area#content-area`. Le `content-area` est un conteneur monolithique dont le contenu est remplacé par `renderFile()` à chaque changement d'onglet.
- **TabManager** (`frontend/app.js:7988`) : Gère déjà les onglets (ouvrir, fermer, preview, drag-reorder, menu contextuel). État stocké dans `_tabs[]`, `_tabCache{}`, `_activeTabId`. Mais il est couplé au `content-area` unique — il remplace `area.innerHTML` à chaque activation.
- **Renderer** : `renderFile()` (`frontend/js/viewer.js:377`) et `renderFileContent()` (`frontend/app.js:3182`) écrivent directement dans `content-area` via `document.getElementById("content-area")`.
- **Déjà présent** : Les onglets sont draggables dans la barre (réorganisation interne). Le drag & drop de fichiers depuis l'arborescence n'existe pas encore (#33).
- **Sous-tâches :**
##### A. PaneManager — Gestionnaire de panneaux (2-3 jours)
- [ ] **A1. Classe `PaneManager`** (`frontend/js/pane-manager.js`) : Nouveau module responsable de la grille de panneaux.
- **État** : `_panes: []` — tableau ordonné de `{id, element, tabBar, tabList, contentArea, tabs: [], activeTabId, tabCache: {}, dirtyTabs: Set}`
- **Pane actif** : `_activePaneId` — le dernier panneau ayant reçu le focus
- **Disposition** : `_layout: 'horizontal' | 'vertical' | 'grid'` — déterminé automatiquement selon le nombre et l'ordre de création
- **Maximum** : 4 panneaux (grille 2×2) pour éviter la surcharge cognitive et les problèmes de performance
- [ ] **A2. Création dynamique du DOM** : Quand un 2e panneau est créé, le `.content-wrapper` est transformé :
- Le `.tab-bar` et `#content-area` d'origine deviennent le **pane #1**
- Un conteneur `.pane-grid` (flex/grid) englobe tous les panneaux
- Chaque panneau a sa propre `.pane-container` > `.tab-bar.pane-tab-bar` + `.content-area.pane-content`
- Une **poignée de redimensionnement** (`.pane-resize-handle`) est insérée entre chaque paire de panneaux
- [ ] **A3. Redimensionnement (resize)** : Implémentation native sans bibliothèque.
- Mouse down sur `.pane-resize-handle` → capture du pointeur → calcul des ratios en pixels → mise à jour `flex-basis` en %
- Double-clic sur la poignée → réinitialise à 50/50
- Persistance des ratios dans `localStorage` par clé `pane-layout-{N}panes`
- Contrainte : largeur/hauteur minimum de 200px par panneau
- [ ] **A4. Indépendance des panneaux** : Chaque panneau a son propre :
- Jeu d'onglets (un fichier peut être ouvert dans plusieurs panneaux simultanément)
- État de scroll, toggle source view, position du curseur
- Cache de données (`tabCache`) — les données fetchées sont partagées via un cache global pour éviter les requêtes dupliquées
- Barre d'outils viewer (copier, source, éditer, pop-out) rattachée au panneau actif
##### B. Refactoring du TabManager (1 jour)
- [ ] **B1. Découplage du DOM global** : Actuellement `TabManager` référence `document.getElementById("content-area")` et `document.getElementById("tab-bar")` en dur. Refactorer pour que chaque instance de Pane TabManager reçoive ses propres éléments DOM en paramètre.
- [ ] **B2. Extraction de l'état** : Le `TabManager` actuel est un objet singleton avec état mutable partagé. Le scoper dans une factory `createTabManager(tabBar, tabList, contentArea)` qui retourne une instance indépendante.
- [ ] **B3. Compatibilité ascendante** : Le pane #1 (unique par défaut) réutilise les IDs DOM existants (`#tab-bar`, `#tab-list`, `#content-area`) pour ne rien casser. Les panneaux additionnels créent des IDs suffixés (`#tab-bar-2`, `#content-area-2`, etc.).
- [ ] **B4. Synchronisation avec le state global** : `currentVault`, `currentPath`, `syncActiveFileTreeItem()` sont mis à jour depuis le panneau actif. Quand on switch de panneau (clic dans un panneau), le state global reflète le document affiché dans ce panneau.
##### C. Actions de division (1 jour)
- [ ] **C1. Menu contextuel des onglets** : Ajouter 3 actions au menu existant (`_showTabContextMenu`) :
- « **Diviser à droite** » (`splitRight`) — crée un nouveau panneau à droite avec cet onglet, le ferme dans le panneau source
- « **Diviser en bas** » (`splitDown`) — crée un nouveau panneau en dessous
- « **Déplacer vers...** » (sous-menu) — liste les autres panneaux existants pour y déplacer l'onglet
- [ ] **C2. Raccourcis clavier** (non conflictuels avec le navigateur) :
- `Ctrl+Alt+\` → diviser le panneau actif à droite
- `Ctrl+Alt+Shift+\` → diviser le panneau actif en bas
- `Ctrl+Alt+←/→/↑/↓` → naviguer entre les panneaux (focus)
- `Ctrl+Alt+W` → fermer le panneau actif (tous ses onglets sont fermés)
- `Ctrl+Shift+Alt+W` → fermer tous les autres panneaux
- [ ] **C3. Boutons dans la barre d'onglets** : À droite de chaque `.tab-bar`, ajouter deux mini-boutons (visibles au survol) : « Split Right » (⊞→) et « Split Down » (⊞↓).
- [ ] **C4. Fermeture d'un panneau** : Si un panneau est fermé, ses onglets sont perdus (pas déplacés). Si c'est le dernier panneau → retour au mode panneau unique (le DOM redevient comme avant). Si le panneau actif est fermé → le focus passe au voisin le plus proche.
##### D. Drag & Drop (1 jour)
- [ ] **D1. Drag d'onglet entre panneaux** : Étendre le drag & drop existant des onglets.
- `dragstart` : mémoriser le `paneId` source + `tabId`
- `dragover` sur une `.tab-bar` d'un autre panneau : accepter le drop, afficher un indicateur
- `drop` : retirer l'onglet du panneau source, l'ajouter au panneau destination, l'activer. Si c'était le dernier onglet du panneau source et que le panneau a été créé par split → fermer le panneau source (sinon le laisser vide)
- `dragend` : nettoyer les indicateurs
- [ ] **D2. Drag de fichier depuis l'arborescence** (complément à #33) : Quand un fichier est dragué depuis le tree, au lieu de simplement l'ouvrir dans le panneau actif, permettre de le dropper :
- Sur une `.tab-bar` d'un panneau spécifique → ouvre dans ce panneau
- Sur une zone vide entre `.pane-resize-handle` → zone de drop fantôme qui crée un nouveau panneau
- Sur l'espace après le dernier onglet → ouvre dans ce panneau (comportement par défaut)
- [ ] **D3. Drag pour créer un panneau** : Si l'onglet est dragué vers le bord droit ou inférieur du `content-area` actif, une zone de drop « glow » apparaît (comme VS Code). Dropper l'onglet sur cette zone crée un nouveau panneau avec cet onglet.
##### E. Rendu et performance (0.5 jour)
- [ ] **E1. Indépendance du rendu** : Chaque panneau appelle `renderFile()` dans son propre `content-area`. `renderFile()` est déjà conçu pour écrire dans le `content-area` global → nécessite un paramètre `targetElement` optionnel (défaut `#content-area`).
- [ ] **E2. Cache de données partagé** : Les données fetchées (`/api/file/{vault}/view`) sont mises en cache au niveau du `PaneManager` pour éviter de refetch le même fichier s'il est ouvert dans 2 panneaux différents. Invalidation du cache quand le fichier est sauvegardé.
- [ ] **E3. Rendu paresseux** : Seul le panneau actif fait le rendu complet. Les panneaux inactifs conservent leur DOM intact mais ne sont pas ré-actualisés tant qu'ils ne reçoivent pas le focus (sauf si le fichier a changé sur disque via watcher).
- [ ] **E4. Gestion des éditeurs** : Quand un fichier est en cours d'édition dans le panneau A, il est verrouillé en lecture dans le panneau B (badge « En édition ailleurs »). Pas d'édition simultanée du même fichier dans 2 panneaux (géré par le backend qui verrouille via `ETag`/`If-Match` — à implémenter dans #78).
##### F. CSS & Design (0.5 jour)
- [ ] **F1. Grille de panneaux** : CSS Grid pour la disposition.
- 1 panneau : pas de grille (layout actuel)
- 2 panneaux : `grid-template-columns: 1fr 4px 1fr` (horizontal) ou `grid-template-rows: 1fr 4px 1fr` (vertical)
- 3 panneaux : disposition automatique (2 en haut, 1 en bas, ou l'inverse selon l'ordre de création)
- 4 panneaux : grille 2×2
- [ ] **F2. Poignées de redimensionnement** : Style cohérent avec `.sidebar-resize-handle` existante.
- 4px de large, fond `var(--border)`, hover `var(--accent)` avec transition 150ms
- Curseur `col-resize` ou `row-resize` selon l'orientation
- Barre fine centrale de 2px en `var(--accent)` au survol
- [ ] **F3. Barre d'onglets par panneau** : Style identique à la barre d'onglets actuelle.
- Onglet actif avec accent-color de fond + border-bottom
- Badge « panneau actif » discret : bordure gauche de 2px en `var(--accent)` sur tout le panneau
- [ ] **F4. Thème sombre/clair** : Toutes les nouvelles classes CSS utilisent les variables CSS existantes → compatibilité automatique avec le toggle de thème.
- [ ] **F5. Responsive / Mobile** : Le split view est désactivé en dessous de 768px. Sur mobile, comportement actuel inchangé (panneau unique, toolbar bottom). Un message dans les paramètres : « Le mode multi-panneaux est disponible sur les écrans larges (≥ 768px) ».
##### G. Persistance et restauration (0.5 jour)
- [ ] **G1. Sauvegarde de la disposition** : Dans `localStorage` sous la clé `obsigate-panes` :
```json
{
"layout": "horizontal",
"panes": [
{"id": "pane-1", "tabs": ["vaultA::doc1.md", "vaultA::doc2.md"], "activeTab": "vaultA::doc1.md", "width": "50%"},
{"id": "pane-2", "tabs": ["vaultB::readme.md"], "activeTab": "vaultB::readme.md", "width": "50%"}
]
}
```
- [ ] **G2. Restauration au chargement** : Au démarrage, si `obsigate-panes` existe → restaurer la disposition. Les onglets sont rouverts (fetchés). Si un fichier n'existe plus → l'onglet est ignoré avec un log warning.
- [ ] **G3. Reset** : Option dans les paramètres : « Réinitialiser la disposition des panneaux » → supprime `obsigate-panes` du localStorage, retour au mode panneau unique.
##### H. Compatibilité avec les fonctionnalités existantes (0.5 jour)
- [ ] **H1. Palette de commandes (#31)** : Ajouter les commandes « Split Right », « Split Down », « Focus Next Pane », « Focus Previous Pane », « Close Pane », « Close Other Panes ».
- [ ] **H2. Pop-out (#??)** : Le bouton pop-out ouvre le document dans une popup séparée (comportement existant). Pas de split view dans la popup (fenêtre indépendante).
- [ ] **H3. Mermaid Live Preview (#50)** : La preview Mermaid est locale au panneau actif. Chaque panneau a sa propre preview (gérée par `mermaid-viewer.js` déjà scoped au content-area).
- [ ] **H4. AI Editor (#26-29)** : L'éditeur AI s'ouvre dans une modale plein écran (inchangé). Pas d'édition multi-panneau dans l'éditeur.
- [ ] **H5. Backup & sauvegarde** : `Ctrl+S` sauvegarde le document du panneau actif uniquement (pas de sauvegarde globale). Chaque panneau gère son propre dirty state.
##### I. Tests (1 jour)
- [ ] **I1. Tests unitaires PaneManager** : Création de panneaux, split right/down, fermeture, redimensionnement, navigation entre panneaux, drag & drop d'onglets entre panneaux.
- [ ] **I2. Tests d'intégration frontend** :
- Ouvrir 2 fichiers → split right → vérifier que chaque panneau a le bon contenu
- Drag d'un onglet du pane 1 vers le pane 2 → vérifier le transfert
- Split down → vérifier la disposition verticale
- Fermer un panneau → vérifier que l'autre panneau reprend tout l'espace
- Redimensionner → vérifier la persistance du ratio
- [ ] **I3. Tests E2E (Playwright, #58)** :
- Test : split right → 2 panneaux visibles
- Test : drag tab entre panneaux
- Test : raccourci clavier `Ctrl+Alt+\`
- Test : restauration de la disposition au rechargement
- [ ] **I4. Tests de régression** : Vérifier que le comportement en mode panneau unique (par défaut) est strictement identique au comportement actuel — même DOM, mêmes IDs, mêmes événements.
##### J. Points d'attention / Risques
- **Complexité du DOM** : Passer d'un content-area unique à un système multi-panneau est le changement architectural le plus profond depuis le refactoring en modules ES — toucher à `renderFile()`, `TabManager`, et tous les appels à `document.getElementById("content-area")` disséminés dans le code.
- **Performance mémoire** : 4 panneaux × 10 onglets × contenu HTML = potentiellement lourd. Implémenter le lazy rendering (seul le panneau actif a son DOM dans le document, les autres sont masqués via `display: none` ou `content-visibility: auto`).
- **Mobile** : Le split view n'a pas de sens sur mobile → désactivé. Mais le code doit gérer la transition si l'utilisateur bascule en mode desktop (ex: tablette en paysage).
- **État global vs local** : `currentVault` et `currentPath` sont utilisés par de nombreuses fonctions (recherche, sidebar, breadcrumb). Le passage au panneau actif doit mettre à jour ces variables de manière fiable — un bug ici casserait la navigation.
- **Backup avant split** : Avant la première activation du mode multi-panneau, sauvegarder la disposition actuelle pour pouvoir revenir en arrière en cas de bug.
---
## ⚪ Backlog — Priorité 4 (P4)
### 67. Notifications web — Push API
- **Effort :** 2 jours | **Impact :** 🟢
- **Description :** Notifications push navigateur pour les changements de vault (fichier modifié, ajouté, supprimé). Utilise la Push API et le service worker existant.
- **Sous-tâches :**
- [ ] Souscription Push : endpoint `POST /api/push/subscribe` (stockage `subscription` + `vault`)
- [ ] Envoi : webhook interne `on_file_change` → dispatch notification via Web Push
- [ ] Configuration VAPID : clés publique/privée dans `config.json`
- [ ] UI : permission navigateur + toggle activer/désactiver par vault
- [ ] Payload : titre du fichier, vault, action (created/modified/deleted)
- [ ] Clic sur notification → ouvre le fichier dans ObsiGate
### 68. Health check enrichi
- **Effort :** 1 jour | **Impact :** 🟢
- **Description :** Endpoint `/api/health` étendu avec métriques détaillées : état de l'index, consommation mémoire, uptime, connexions SSE actives, nombre de backups, dernière indexation.
- **Sous-tâches :**
- [ ] Métriques index : nombre de fichiers, nombre de tokens, génération courante
- [ ] Métriques mémoire : RSS, heap used (via `psutil` ou `/proc/self/status`)
- [ ] Métriques uptime : `time.time() - server_start_time`
- [ ] Métriques backups : nombre total, âge du plus vieux, espace disque
- [ ] Format réponse JSON structuré : `{ status, uptime, index, memory, backups, connections }`
- [ ] Endpoint séparé `GET /api/health/detailed` (protégé admin)
### 69. Éditeur mobile natif — Interface tactile optimisée
- **Effort :** 2-3 jours | **Impact :** 🟢
- **Description :** Refonte de l'expérience mobile pour l'édition : barre d'outils contextuelle, presse-papiers optimisé, gestes tactiles (swipe pour actions rapides), mode lecture plein écran.
- **Sous-tâches :**
- [ ] Barre d'outils mobile flottante : **bold**, *italic*, `code`, liste, lien (inspirée de l'éditeur Obsidian mobile)
- [ ] Raccourcis swipe : gauche → backlinks, droite → table des matières
- [ ] Mode lecture : cache sidebar + header, plein écran, swipe horizontal pour page suivante
- [ ] Presse-papiers : bouton « Coller » persistant (iOS contourne restriction clipboard)
- [ ] Adaptation CodeMirror : hauteur ajustable, police agrandissable (pinch zoom)
### 70. Recherche sémantique — Embeddings vectoriels
- **Effort :** 4-5 jours | **Impact :** 🟢
- **Description :** Moteur de recherche sémantique utilisant des embeddings (sentence-transformers) pour trouver des notes par similarité de sens, au-delà des mots-clés exacts.
- **Sous-tâches :**
- [ ] Génération d'embeddings : modèle `all-MiniLM-L6-v2` via `sentence-transformers` (Python) ou appel API externe
- [ ] Stockage : index vectoriel avec `numpy` + `faiss` (ou `usearch` pour performance)
- [ ] Indexation : embedding par chunk de 512 tokens avec recouvrement
- [ ] Recherche hybride : combinaison TF-IDF + similarité cosinus (RRF — Reciprocal Rank Fusion)
- [ ] UI : toggle « Recherche sémantique » dans la barre de recherche
- [ ] UI : score de similarité dans les résultats
### 71. Tableau de bord administrateur
- **Effort :** 2 jours | **Impact :** 🟢
- **Description :** Page d'administration dédiée avec monitoring en temps réel, gestion des utilisateurs avancée, et logs d'audit visualisables.
- **Sous-tâches :**
- [ ] Widgets temps réel : CPU, mémoire, espace disque, requêtes/min (rafraîchissement SSE)
- [ ] Gestion utilisateurs : tableau triable, création/édition/suppression, filtre par rôle
- [ ] Logs d'audit : visualisation des 500 dernières entrées, filtre par utilisateur/action/date
- [ ] Backup stats : graphique d'évolution (taille totale, nombre par vault, âge moyen)
- [ ] Protection : accès restreint au rôle `admin` uniquement
### 72. API publique documentée — OpenAPI 3.1
- **Effort :** 1-2 jours | **Impact :** 🟢
- **Description :** Documentation OpenAPI complète et interactive (Swagger UI + Redoc) pour l'API REST, avec exemples de requêtes, descriptions en anglais, et schémas Pydantic exposés.
- **Sous-tâches :**
- [ ] Audit des endpoints existants → compléter les docstrings manquants
- [ ] Ajout de `response_model` sur tous les endpoints (40+ actuellement, ~15 sans modèle)
- [ ] Exemples dans les schémas : `examples=[...]` pour les endpoints clés
- [ ] Tagging des endpoints par catégorie (Files, Vaults, Search, Auth, AI, Backups)
- [ ] Serveur mock : `prism` ou `openapi-generator` pour tests sans backend
- [ ] Page de documentation intégrée : lien dans le menu header (« API »)
### 73. Synchronisation multi-appareils — Obsidian Sync compatible
- **Effort :** 6-8 jours | **Impact :** 🟢
- **Description :** Synchronisation des vaults entre plusieurs instances d'ObsiGate via un protocole de synchronisation décentralisé ou compatible Obsidian Sync. Alternative self-hosted à Obsidian Sync.
- **Sous-tâches :**
- [ ] Protocole : évaluation CRDT vs OT vs diff/patch pour fichiers markdown
- [ ] Transport : WebSocket ou polling HTTPS avec compression
- [ ] Merge strategy : LWW (Last Writer Wins) avec historique des deux versions
- [ ] Détection de changements : hash SHA-256 par fichier, journal des modifications
- [ ] UI : page « Synchronisation » avec statut par appareil, historique des syncs
- [ ] Conflits : UI de résolution manuelle (diff côte à côte entre version locale et distante)
- [ ] Chiffrement : optionnel, chiffrement AES-256-GCM avant transmission
- [ ] Pairing : échange de clé publique + code QR pour appairage des appareils
---
## 📊 Résumé des efforts
| Priorité | Items | Effort total estimé |
|---|---|---|
| ✅ Complété | #1 → #57 | ~65 jours |
| 🔵 P1 | #58 (Playwright) | 2-3 jours |
| ⚪ P3 | #59 → #66, #74, #75 (10 items) | 29-39 jours |
| ⚪ P4 | #67 → #73 (7 items) | 18-23 jours |
| **Total restant** | **18 items** | **49-65 jours** |
---
## Notes
- Les items P3/P4 ne sont pas ordonnés par priorité interne — à raffiner selon les retours utilisateurs.
- L'effort inclut le développement + tests unitaires + intégration CI, mais pas la documentation utilisateur.
- Les items marqués 🟢 (nice-to-have) sont de bons candidats pour des contributions externes.
- Le mode hors-ligne (#59) et l'i18n (#63) sont les P3 ayant le meilleur rapport effort/valeur.