Files
ObsiGate/docs/ROADMAP.md
T
bruno 0cbbbf260a
CI / lint (push) Successful in 54s
CI / security (push) Successful in 38s
CI / test (push) Successful in 1m7s
CI / build (push) Successful in 33s
CI / e2e (push) Failing after 10m52s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
fix(search,excalidraw,e2e): recherche cassee + corruption .excalidraw.md + tests E2E #78
- frontend/js/search.js : onSearchFilter appelee avec (vault, query, results) — renvoyait undefined, cassait le rendu et basculait sur la recherche offline (0 resultat). Regression plugins depuis 30f2df3.
- frontend/excalidraw-editor.html + js/excalidraw-viewer.js : ignorer onChange au montage (plus d'auto-save spurieux qui reecrivait .excalidraw.md en JSON brut) + preservation du format Obsidian frontmatter + compressed-json a la sauvegarde.
- frontend/js/app.js : __OBSIGATE_BOOTED pose apres init() (splash + E2E attendent les handlers lies).
- tests/e2e : goHome/login attendent le boot ; excalidraw.spec.js reecrit avec les selecteurs reels (modal, context menu, canvas.first()).
- ROADMAP : C8 (creation via menu contextuel) marque fait.
- Verifie : 86/86 E2E chromium-desktop, 599 backend, ruff, validate-imports, JSDOM.
2026-09-10 22:51:08 -04:00

84 KiB
Raw Blame History

ObsiGate — Roadmap

Version : 2.1.0-dev | Dernière mise à jour : 2026-09-10 Revue de cohérence roadmap ↔ code : cases cochées selon l'état réel vérifié dans le dépôt (#78 docs H1-H3, #67 push, #68 health, #74/#61 optionnels non retenus). Voir aussi CHANGELOG.md, AUDIT_TECHNIQUE.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 🟡

✅ Complété (suite — v1.7 → v2.1)

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 :
    • Installation Playwright + config (playwright.config.ts)
    • Fixtures : vault de test (utilise les vaults Docker existants)
    • Test : dashboard → stats, tabs, Quick Help, sidebar
    • Test : recherche full-text → résultats, snippets, tri pertinence/date
    • Test : ouverture fichier → viewer Markdown, métadonnées
    • Test : éditeur → Forge, basic modal, Ctrl+S
    • Test : rendu Mermaid dans le viewer + preview Forge
    • Test : export PDF → bouton présent
    • Test : mode sombre → toggle, persistence localStorage
    • Test : responsive mobile → layout, recherche, barre flottante
    • Test : barre flottante résultats → compteur, nav, toggles Aa/wd
    • Test : sauvegardes → filtres Tous/Recherches/Répertoires
    • Test : raccourcis clavier → Ctrl+K, /, Escape
    • Test : menu contextuel répertoire
    • Intégration CI : job e2e dans .gitea/workflows/ci.yml

⚪ Backlog — Priorité 3 (P3)

59. Mode hors-ligne PWA complet — ✅ TERMINÉ

  • 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 :
    • IndexedDB : stockage local de l'index des fichiers (paths, titles, tags)
    • Moteur de recherche offline via IndexedDB (cursor + filtre)
    • Cache des fichiers markdown récemment ouverts (derniers 50, prune)
    • Stratégie de cache : Network First avec fallback IndexedDB
    • File de synchronisation : modifications offline → appliquées au retour réseau
    • UI indicateur : badge « Hors-ligne » + compteur de modifications en attente
    • Gestion des conflits : détection et résolution manuelle (choix version locale vs serveur)

61. Plugins système — Extensions utilisateur ✅

  • Effort : 4-5 jours | Impact : 🟢 | Statut : ✅ Livré (backend + frontend + tests + docs)
  • 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, onFileCreate, onFileDelete, onVaultMount
    • Sandbox d'exécution : Web Worker isolé pour le code plugin (blob URL, postMessage structuré, CSP sans importScripts)
    • UI : page « Plugins » dans les paramètres (installer, activer/désactiver, désinstaller, template, viewer)
    • Distribution : dépôt de plugins communautaire (fichier JSON index) — ⚪ NON RETENU (backlog)
    • Hot-reload : activation/désactivation sans rechargement de page (marker .disabled)
    • Sécurité : manifest de permissions, validation path-traversal, CSP restrictif
  • Livré :
    • Backend backend/plugins.py — validation manifest (name regex, semver, hooks/permissions autorisés), stockage par vault <vault>/.obsigate-plugins/, lifecycle complet, validation ZIP (path traversal, limite 100 fichiers, 500KB/fichier), 9 endpoints /api/plugins/* (admin-gated pour install/uninstall/enable/disable), template API.
    • Frontend frontend/js/plugins.js — PluginManager, sandbox Web Worker (code via blob URL, protocole postMessage structuré), UI Settings > Plugins, hooks dispatch (executeHook/onFileRender/onSearchFilter/…).
    • Tests : tests/test_plugins.py (44) + tests/frontend/plugins.test.mjs (21) — validation, lifecycle, ZIP/dir sécurité, protocole sandbox, isolation DOM/CSP.
    • Docs : docs/PLUGINS.md.
  • En backlog (non retenu) : dépôt communautaire (index JSON), signature de code des plugins.

62. Collaboration temps réel — Édition simultanée

  • Effort : 5-7 jours | Impact : 🟢
  • Description : Permettre à plusieurs utilisateurs d'éditer le même document markdown en même temps, comme Google Docs. Chaque personne voit en temps réel ce que les autres tapent, avec leur curseur affiché en couleur.
    • WebSocket : connexion persistante bidirectionnelle entre le navigateur et le serveur. Contrairement à HTTP où le client doit constamment demander « y a-t-il du nouveau ? » (polling), le WebSocket permet au serveur de pousser les changements instantanément. Une room WebSocket est créée par fichier ouvert — tous les utilisateurs qui éditent le même fichier rejoignent la même room.
    • Yjs + CRDT : Yjs est une bibliothèque qui implémente un algorithme CRDT (Conflict-free Replicated Data Type). Imagine deux personnes qui tapent en même temps au même endroit — sans CRDT, on aurait un conflit et du texte perdu. Avec CRDT, les deux modifications sont fusionnées mathématiquement sans perte. Chaque caractère reçoit un identifiant unique, et l'ordre final est déterministe même si les opérations arrivent dans le désordre. Pas besoin de verrouiller le fichier ni de résoudre des conflits manuellement.
    • Awareness : chaque utilisateur voit le curseur des autres (position, sélection) représenté par un nom et une couleur. Un indicateur dans la barre d'outils montre qui est connecté.
    • Persistance : le serveur sauvegarde périodiquement le document (debounce 2s après la dernière modification) pour que les changements survivent à une déconnexion.
  • Pourquoi c'est important : Permet le travail d'équipe sur la documentation, les notes de réunion, les spécifications techniques, les brainstorms. C'est le passage d'ObsiGate de « outil personnel » à « outil d'équipe ».
  • 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 — ✅ TERMINÉ

  • Effort : 2-3 jours | Impact : 🟡 | Statut : ✅ Terminé
  • Description : Support de l'anglais et du français via un système de clés de traduction.
  • Sous-tâches :
    • Extraction des chaînes : ~1200 clés UI extraites
    • Format : JSON fr.json + en.json dans frontend/locales/ → 1206 clés parfaitement synchronisées
    • Fonction t(key) → frontend/js/i18n.js avec _applyDOM(), data-i18n, data-i18n-attr, data-i18n-placeholder, data-i18n-html, support des templates {var}
    • Sélecteur de langue dans les paramètres (persisté localStorage obsigate-lang)
    • Traduction des messages backend → les toast/showToast sont maintenant i18n dans tous les fichiers JS
    • Documentation multilingue → README.md + README.fr.md
    • Nettoyage des clés inutilisées → locales nettoyées
    • Tous les fichiers JS utilisent t() → plus de texte FR en dur (ai.js, sync.js, graph.js, autocomplete.js)
    • Interface principale : dashboard, sidebar, editor, search, settings → EN/FR complet
    • Guide d'utilisation : 18 sections (Intro → Astuces) → tous les paragraphes traduits
    • Thèmes, palette de commandes, raccourcis, webhooks → EN/FR complet
    • Messages système : toasts, statuts, événements → EN/FR complet

64. MFA — Authentification multi-facteurs — ✅ TERMINÉ (TOTP + WebAuthn + recovery codes)

  • Effort : 2 jours (réalisé) | Impact : 🟡
  • Description : Ajout d'un second facteur d'authentification obligatoire pour les comptes administrateur. Deux méthodes sont proposées :
    • TOTP (Time-based One-Time Password) : l'utilisateur scanne un QR code avec son app d'authentification (Google Authenticator, Authy, Bitwarden) qui génère un code à 6 chiffres renouvelé toutes les 30 secondes. Au login, après avoir saisi son mot de passe, l'utilisateur doit entrer le code affiché sur son téléphone. Même si le mot de passe est volé, le compte reste protégé car l'attaquant n'a pas le téléphone.
    • WebAuthn (clés de sécurité physiques) : l'utilisateur enregistre une clé USB (YubiKey, SoloKey) ou utilise la biométrie de son appareil (empreinte digitale, Face ID, Windows Hello). Au login, le navigateur demande de toucher la clé physique ou de scanner le doigt. C'est le niveau de sécurité le plus élevé — résistant au phishing car la clé vérifie le domaine du site avant de répondre.
    • Codes de secours : 8 codes à usage unique imprimables, à conserver en lieu sûr, qui permettent de se connecter même si on perd son téléphone ou sa clé. Chaque code ne fonctionne qu'une seule fois.
  • Pourquoi c'est important : Le vol de mot de passe est la cause #1 de brèches de sécurité. Avec un vault Obsidian contenant des notes personnelles, projets sensibles, secrets et tokens API, l'authentification par simple mot de passe n'est plus suffisante. Le MFA empêche 99.9% des attaques de prise de compte automatisées (source : Microsoft Security).
  • Sous-tâches :
    • TOTP : génération de secret, QR code, vérification code 6 chiffres
    • WebAuthn : enregistrement de clé, assertion, attestation (backend/auth/webauthn_mfa.py, lib webauthn==2.6.0, challenges in-memory TTL 180s à usage unique) — FAIT en 2026-09 (commit ab795ec)
    • UI : page « Sécurité du compte » avec activation/désactivation MFA + gestion des clés WebAuthn (liste, ajout, retrait)
    • Flow login : mot de passe → challenge TOTP OU WebAuthn selon mfa_method retourné par /login
    • Recovery codes : 8 codes de backup à usage unique (générés à l'activation, hachés SHA-256)
    • Stockage : mfa_secret + webauthn_credentials[] dans users.json
    • Tests : tests/test_mfa.py (29) + tests/test_webauthn.py (10, authentificateur virtuel CBOR/EC P-256)

65. Thèmes personnalisés — CSS variables — ✅ TERMINÉ

  • Effort : 1-2 jours (réalisé) | 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 → 40+ variables
    • Presets: light, dark, high-contrast, sepia (générés dynamiquement)
    • UI : sélecteur de thème dans les paramètres (swatches grid)
    • Import/export de thème personnalisé (JSON)
    • Application dynamique via document.documentElement.style.setProperty

66. Export multi-formats — ✅ TERMINÉ

  • Effort : 1-2 jours (réalisé) | 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 (zipfile + mistune, 0 nouvelle dep)
    • UI : dropdown Export dans toolbar viewer (HTML / MD bundle / ePub)
    • Endpoints : GET /api/export/html, GET /api/export/md-bundle, GET /api/export/epub

74. Support complet des documents PDF — ✅ TERMINÉ

  • Effort : 4-5 jours | Impact : 🟡 | Statut : ✅ COMPLET (2026-09-07 — C3 + Range 206 + config G3 + indexation incrémentale, commit 7042307. Optionnels D2/E4/H2/I2 non retenus)
  • 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.
  • Implémentation réelle (vérifiée 2026-09-07) :
    • Bugs corrigés (2026-09) : api_pdf_stream crashait en 500 (NameError: current_user jamais injecté) ; l'indexation incrémentale du watcher faisait read_text() sur les PDFs (garbage) ; Range/206 et pdf/info absents malgré le texte ci-dessous.
    • GET /api/file/{vault}/pdf/info — métadonnées seules sans transférer le document (C3)
    • Stream avec Accept-Ranges + 206 Partial Content (single range, suffix-range, 416) (C2)
    • OBSIGATE_PDF_MAX_SIZE_MB (50) + OBSIGATE_PDF_EXTRACT_TIMEOUT (30s via thread-pool) (B4/G3)
    • Backend backend/pdf_reader.py (existant) — extraction pypdf + pymupdf (fallback), métadonnées, TOC
    • backend/indexer.py — .pdf dans SUPPORTED_EXTENSIONS, extraction dans index_document()
    • backend/main.py — flag is_pdf: True retourné par api_file_view, endpoint GET /api/file/{vault}/pdf/stream avec support Range/206
    • backend/search.py — filtre ext:pdf (déjà implémenté avant cette PR)
    • frontend/js/viewer.js:451-480 — branche if (data.is_pdf) + iframe + toolbar + TOC + bouton download
    • Tests : tests/test_pdf.py (26 tests verts) — text/metadata/TOC + indexation scan/incrémentale + filtre ext + stream 200/206/416 + /pdf/info + limite de taille
    • Bug fixé dans cette PR : PdfReader NameError dans pdf_reader.py quand pymupdf est installé (la variable PdfReader n'était déclarée que dans la branche except ImportError)
    • backend/requirements-test.txt (nouveau) — reportlab pour générer des PDFs de test
  • Sous-tâches :
A. Backend — Extraction de texte PDF (1-1.5 jour)
  • A1. Dépendance : pypdf>=4.0 retenu dans requirements (pure Python, simplicité Docker) ; PyMuPDF (fitz) utilisé automatiquement en priorité s'il est importable — l'inverse du plan initial, fonctionnellement équivalent.
  • 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. Lecture PDF dans les DEUX chemins d'indexation (_scan_vault + _index_single_file_sync, utilisé par le watcher) : détection .pdf → extract_pdf_text(). Fix 2026-09 : seul le scan complet gérait les PDFs, l'incrémental indexait du garbage.
  • B3. Métadonnées PDF (adapté) : titre PDF prioritaire sur le nom de fichier dans l'index ; pages/author exposés via api_file_view + /pdf/info (non stockés dans l'entrée d'index).
  • 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 — ⚪ NON RETENU : 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 — ⚪ NON RETENU : 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 : pypdf>=4.0 retenu (pymupdf optionnel, utilisé s'il est importable).
  • 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 : OBSIGATE_PDF_MAX_SIZE_MB (50) + OBSIGATE_PDF_EXTRACT_TIMEOUT (30s) — documentés dans .env.example et README FR/EN.
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 — ⚪ NON RETENU :
    • 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 ? » — ⚪ NON RETENU (README + guide couvrent déjà le support PDF)
  • 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) — ✅ TERMINÉ (E2E matriciels inclus)

  • Effort : 5-7 jours (réalisé) | Impact : 🟡
  • Statut : Fonctionnel — 100% implémenté + 37 tests E2E (16 split-view.spec.js + 21 split-view-matrix.spec.js, 2026-09-08)
  • Fichiers clés : frontend/js/pane-manager.js (1082 loc), frontend/js/viewer.js (modifié), frontend/js/ui.js (modifié), frontend/js/dashboard.js (modifié), frontend/js/palette.js (modifié), frontend/style.css (modifié), tests/test_pane_manager.py (22 tests)
  • 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). Drag & drop des onglets entre les panneaux, drag-to-split, menu contextuel, raccourcis clavier, palette de commandes.
  • Architecture :
    • PaneManager — orchestre la grille de panneaux (1-4), crée/détruit les instances PaneTabManager
    • PaneTabManager (factory createPaneTabManager(paneId)) — chaque panneau a son instance indépendante avec ses propres onglets, cache, état
    • Singleton TabManager — délègue automatiquement vers le PaneTabManager actif quand en mode split ; gère les onglets en single-pane
    • Migration automatique — les onglets migrent singleton→PaneTabManager au premier split, et inversement au collapse
    • getContentArea() — helper global dans viewer.js, priorise window._activePaneContentArea (set par PaneTabManager)
  • Implémenté :
✅ A. PaneManager — Gestionnaire de panneaux
  • A1. Module PaneManager + factory createPaneTabManager — état indépendant par panneau
  • A2. Création dynamique du DOM — .pane-grid avec .pane-container, IDs suffixés -N
  • A3. Redimensionnement — drag handles, double-clic reset 50/50, contrainte 20-80%, localStorage
  • A4. Indépendance des panneaux — chaque panneau a ses propres onglets, scroll, cache, source view
✅ B. Refactoring du TabManager
  • B1. Découplage du DOM — init(tabBar, tabList, contentArea) par paramètre
  • B2. Factory createPaneTabManager(paneId) — instances indépendantes
  • B3. Compatibilité ascendante — pane 0 réutilise les IDs existants, panes 1+ suffixés -N
  • B4. Synchronisation state global — setActivePane() met à jour currentVault/currentPath
✅ C. Actions de division
  • C1. Menu contextuel — « Diviser à droite », « Diviser en bas », « Fermer le panneau »
  • C1b. Sous-menu « Déplacer vers... » — flyout hover listant les autres panneaux
  • C2. Raccourcis clavier — Ctrl+Alt+\ (split R), Ctrl+Alt+Shift+\ (split D), Ctrl+Alt+W (close pane), Ctrl+Shift+Alt+W (close others), Ctrl+Alt+←/→/↑/↓ (navigate)
  • C3. Boutons dans la barre d'onglets — ⊞→ et ⊞↓ visibles au survol
  • C4. Fermeture panneau — retour single-pane si dernier, focus voisin, onglets perdus (pas déplacés)
✅ D. Drag & Drop
  • D1. Drag cross-pane — text/plain JSON {paneId, tabId, index}, indicateurs visuels
  • D2. Drag depuis l'arborescence — format tree:{vault,path}, drop sur panneau spécifique
  • D3. Drag-to-split — zone droite 28% → split right, zone basse 28% → split down, overlay bleu
✅ E. Rendu et performance
  • E1. Rendu indépendant — window._activePaneContentArea override pour renderFile()
  • E2. Cache de contenu — chaque PaneTabManager a son propre _tabCache, pas de refetch
  • E3. Rendu paresseux — contenu masqué via display:none sur panneaux inactifs
  • E4. Verrouillage éditeur — même fichier déjà ouvert ailleurs → focus le panneau existant
✅ F. CSS & Design
  • F1-F5. Grid layout, resize handles avec hover accent, barre onglets par panneau, variables CSS (dark/light), responsive <768px
✅ G. Persistance et restauration
  • G1. localStorage obsigate-panes — layout, panes[{id, width, activeTab, tabs[]}]
  • G2. Restauration au chargement — reconstruction grille + onglets + activation
  • G3. Reset dans la palette de commandes — « 🔄 Réinitialiser les panneaux »
✅ H. Compatibilité
  • H1. Palette de commandes — « Diviser à droite », « Diviser en bas », « Fermer le panneau », « Panneau suivant/précédent » (catégorie Panneaux)
  • H2. Pop-out inchangé
  • H3. Mermaid scoped au content-area
  • H4. AI Editor en modale (inchangé)
  • H5. Ctrl+S par panneau
✅ I. Tests
  • I1. 22 tests statiques (structure, CSS, brace balance, régression backend)
  • I4. 307 tests de régression passent
  • I2. Tests d'intégration frontend (JSDOM) — tests/frontend/pane-manager.test.mjs (9 tests)
  • I3. Tests E2E Playwright (#58) — 2026-09 : split-view.spec.js (16) + split-view-matrix.spec.js (21) = 37 tests verts en local + CI
Reste à faire
  • C1b. Sous-menu « Déplacer vers... » dans le menu contextuel des onglets
  • D2. Drag & drop depuis l'arborescence vers un panneau spécifique
  • E3. Rendu paresseux (mémoire) — contenu masqué via display:none sur panneaux inactifs
  • E4. Verrouillage éditeur multi-panneau — même fichier ouvert dans 2 panneaux → focus le panneau existant
  • G3. Bouton reset dans la palette de commandes (🔄 Réinitialiser les panneaux)
  • I2. Tests d'intégration frontend (JSDOM) — tests/frontend/pane-manager.test.mjs (9 tests, 100% verts)
  • I3. Tests E2E Playwright — matrice complète ouverture/fermeture couverte (voir split-view-matrix.spec.js)
Matrice de couverture ouverture/fermeture (validée 2026-09-08, 37/37 verts)
Cas Surface testée Spec
Ouverture fichier → onglet clic arbre, double-clic preview vs persistant split-view
Split droit raccourci Ctrl+Alt+\ · bouton ⊞→ · menu contextuel · palette · drag-to-split bord droit les 5
Split bas raccourci Ctrl+Alt+Shift+\ · bouton ⊞↓ · menu contextuel · drag-to-split bord bas les 4
Déplacer onglet drag cross-pane · flyout « Déplacer vers... » · drag depuis arbre vers panneau les 3
Navigation Ctrl+Alt+←/→ (horizontal) · Ctrl+Alt+↑/↓ (vertical) · palette suivant/précédent les 3
Fermeture onglet bouton X · double-clic · clic molette · Ctrl+W (single) les 4
Fermeture menu ctx Fermer · Fermer les autres · Fermer à droite · Fermer tout → dashboard les 4
Fermeture panneau Ctrl+Alt+W (clavier) · menu « Fermer le panneau » · Ctrl+Shift+Alt+W (autres) · auto-collapse panneau vidé (C4) · fermeture pane 0 → voisin survit · onglets du panneau fermé perdus (documenté) les 5
Persistance layout restauré au reload · reset palette vide localStorage les 2
Edge plafond 4 panneaux (PANE_MAX) · pas de doublon à la réouverture (E4) les 2

Non couvert par E2E (hors périmètre ouverture/fermeture) : poignée de redimensionnement drag/dblclick-reset (A3) — logique vérifiée par les 22 tests statiques + JSDOM.


76. BooksLM — Console AI contextuelle par répertoire (style NotebookLM) — ✅ TERMINÉ

  • Effort : 5-6 jours (réalisé) | Impact : 🟡
  • Description : Console de chat AI contextuelle accessible via le menu contextuel des répertoires dans l'arborescence. Au clic sur « BooksLM », un panneau de chat s'ouvre à droite du viewer et indexe automatiquement toutes les ressources markdown (et PDF via #74) du répertoire courant et de ses sous-répertoires récursivement comme contexte pour un assistant AI. L'assistant peut répondre à des questions, résumer, synthétiser, et croiser l'information à travers tous les documents du scope — exactement comme NotebookLM de Google, mais pour n'importe quel répertoire de votre vault Obsidian.
  • Fonctionnement général :
    • L'utilisateur fait un clic-droit sur un répertoire dans l'arborescence → option « BooksLM »
    • Un panneau latéral (450-500px) s'ouvre à droite, poussant le viewer existant
    • Le backend collecte tous les fichiers .md (et .pdf si #74 est complété) récursivement
    • Le contenu est assemblé en un contexte système pour le LLM (fenêtre de contexte optimisée)
    • L'utilisateur peut chatter avec l'AI qui a une connaissance complète du répertoire
    • L'historique de chat est optionnellement sauvegardé (localStorage ou fichier .books-lm.json dans le répertoire)
  • Sous-tâches :
A. Backend — Collecte et préparation du contexte (1.5-2 jours) — ✅ livré (backend/bookslm.py, bookslm_routes.py)
  • A1. Nouvel endpoint POST /api/ai/bookslm/context : Reçoit {vault, directory} → parcourt récursivement le répertoire → lit tous les fichiers supportés → retourne un objet {files: [{path, title, content, type: "md"|"pdf"}], total_chars, file_count, directory_tree}
  • A2. Limites configurables : BOOKSLM_MAX_FILES (défaut 200), BOOKSLM_MAX_TOTAL_CHARS (défaut 200 000), BOOKSLM_MAX_FILE_CHARS (défaut 30 000 par fichier). Les fichiers au-delà sont tronqués avec un message [... continue dans le fichier].
  • A3. Filtrage intelligent : Ignorer les fichiers cachés (. préfixe), les dossiers _attachments/, les fichiers binaires non-supportés. Respecter .gitignore ou .obsigate-ignore si présent.
  • A4. Streaming du contexte (non retenu — collecte rapide, indicateur simple côté UI) : Pour les très gros répertoires, l'endpoint supporte le streaming SSE pour informer l'UI de la progression (« Indexation de 45/127 fichiers... »).
B. Backend — Endpoint chat BooksLM (1 jour) — ✅ livré
  • B1. Endpoint POST /api/ai/bookslm/chat : Reçoit {vault, directory, message, conversation_history: [{role, content}]} → construit le contexte système à partir des fichiers du répertoire → appelle le provider AI configuré → stream la réponse via SSE.
  • B2. Prompt système : Template par défaut optimisé : « Tu es un assistant de recherche qui aide à comprendre et analyser les documents d'un répertoire. Voici le contenu de tous les documents disponibles. Réponds en te basant UNIQUEMENT sur ces documents. Cite tes sources avec le nom du fichier. Si l'information n'est pas dans les documents, dis-le clairement. »
  • B3. Mode « Sources » : Chaque réponse inclut les fichiers référencés (détectés via mention de titre ou contenu). L'UI affiche des badges de source cliquables.
  • B4. Mise en cache du contexte : Le contexte du répertoire est caché en mémoire (hash du contenu) pour éviter de re-parser tous les fichiers à chaque message. Invalidé si un fichier est modifié (watcher).
  • B5. Provider : Utilise la même abstraction provider que l'AI Editor (#27) — DeepSeek, OpenRouter, Gemini. Ajouter BOOKSLM_DEFAULT_MODEL dans .env (défaut : DEEPSEEK_MODEL).
C. Frontend — Panneau de chat BooksLM (2 jours) — ✅ livré (frontend/js/bookslm.js)
  • C1. Module frontend/js/bookslm.js : Nouveau module ES avec la classe BooksLM :
    • Gère l'état : _isOpen, _currentDirectory, _messages[], _contextFiles[], _isLoading
    • Crée le DOM du panneau : conteneur latéral .bookslm-panel (450px, redimensionnable via poignée)
    • Header : titre « BooksLM », nom du répertoire courant, bouton fermer, bouton « Nouvelle conversation »
    • Zone de messages : scrollable, bulles utilisateur (droite) et assistant (gauche) avec Markdown rendu
    • Zone d'entrée : textarea avec Ctrl+Enter pour envoyer, bouton envoyer
    • Barre d'état : nombre de fichiers indexés, nombre total de caractères
  • C2. Intégration au menu contextuel : Dans frontend/js/context-menu.js, ajouter l'option « 🧠 BooksLM » pour les nœuds de type directory dans l'arborescence. Visible seulement si le vault est accessible.
  • C3. Rendu Markdown dans le chat : Utiliser le renderer Markdown existant (ou un sous-ensemble simplifié) pour afficher les réponses de l'AI avec support du gras, italique, code, listes, et tableaux.
  • C4. Streaming des réponses : Connexion SSE pour afficher la réponse de l'AI token par token (effet « typing » naturel).
  • C5. Badges de sources : Après chaque réponse, afficher les fichiers sources mentionnés sous forme de badges cliquables qui ouvrent le fichier dans le viewer principal.
  • C6. Mode plein écran : Bouton pour basculer en mode plein écran (cache la sidebar, le panneau prend tout l'espace). Utile pour les sessions de recherche intense.
D. Frontend — Actions et UX (0.5-1 jour) — ✅ livré (D5 partiel)
  • D1. Copier la réponse : Bouton copie sur chaque message assistant.
  • D2. Régénérer : Bouton pour régénérer la dernière réponse (utile si la réponse est hors-sujet).
  • D3. Exporter la conversation : Bouton pour exporter l'historique en Markdown → sauvegarder comme note dans le répertoire courant.
  • D4. Historique des conversations : Stockage dans localStorage par clé bookslm-history-{vault}-{directory}. Liste déroulante dans le header pour charger une conversation précédente.
  • D5. Indicateur de contexte (barre de progression %) — non retenu, compteur fichiers/caractères affiché) : Barre de progression montrant l'utilisation du contexte (% de la limite BOOKSLM_MAX_TOTAL_CHARS). Si le répertoire est trop gros, suggérer de réduire le scope.
  • D6. Suggestions de questions : Après l'indexation, afficher 3 questions suggérées basées sur les titres et métadonnées des fichiers (« Résume ce répertoire », « Quels sont les thèmes principaux ? », « Y a-t-il des contradictions entre ces documents ? »).
E. CSS & Design (0.5 jour) — ✅ livré
  • E1. Panneau latéral : Animation slide-in depuis la droite (300ms ease-out). Ombre portée pour séparation visuelle.
  • E2. Poignée de redimensionnement : Similaire à .sidebar-resize-handle, curseur col-resize, largeur min 350px, max 800px. Persistance dans localStorage.
  • E3. Bulles de chat : Style cohérent avec le thème actuel. Messages utilisateur avec accent-color, messages assistant avec fond var(--surface2).
  • E4. Responsive : Sur mobile (<768px), le panneau passe en plein écran (pas de split view). Navigation par swipe pour revenir au viewer.
  • E5. Thème sombre/clair : Toutes les variables CSS utilisent les customs properties existantes → compatibilité automatique.
F. Intégration et compatibilité (0.5 jour) — ✅ livré (F1 adapté : panneau dédié, pas un pane du PaneManager)
  • F1. Compatibilité Split View (#75) (adapté : panneau latéral indépendant, cohabite avec le split view) : Si le split view est actif, BooksLM s'ouvre en remplacement du panneau le plus à droite (ou en 3e colonne). Le panneau BooksLM est traité comme un type spécial de pane dans le PaneManager.
  • F2. Compatibilité AI Editor (#26-29) : BooksLM utilise le même système de provider AI. Les clés API configurées pour l'AI Editor fonctionnent pour BooksLM.
  • F3. Compatibilité PDF (#74) : Si le support PDF est implémenté, les PDFs dans le répertoire sont inclus dans le contexte (texte extrait).
  • F4. Palette de commandes (#31) : Ajouter les commandes « BooksLM: Ouvrir pour le répertoire courant » et « BooksLM: Nouvelle conversation ».
G. Tests (1 jour) — ✅ G1 livré (28 tests), G2/G3 non retenus
  • G1. Tests unitaires backend :
    • test_bookslm_context.py : collecte récursive, respect des limites, filtrage fichiers cachés, streaming SSE
    • test_bookslm_chat.py : construction du prompt, caching du contexte, invalidation après modification
  • G2. Tests d'intégration frontend (non retenus) :
    • Ouverture du panneau BooksLM depuis le menu contextuel
    • Envoi d'un message et affichage de la réponse
    • Badges de sources cliquables
    • Export de conversation
    • Redimensionnement du panneau
  • G3. Tests E2E (Playwright) (non retenus à ce jour) :
    • Test : clic-droit sur répertoire → BooksLM → panneau visible
    • Test : chat fonctionnel → message envoyé → réponse reçue
    • Test : fermeture et réouverture → historique restauré
H. Points d'attention / Risques
  • Taille du contexte : Un répertoire avec 500 fichiers markdown peut facilement dépasser 1M de caractères → essentiel de tronquer intelligemment (préférer les fichiers modifiés récemment, ou prioriser selon la structure : README.md, index.md en premier).
  • Coût API : Chaque message envoie le contexte complet au LLM → potentiellement coûteux en tokens. Ajouter un avertissement si le contexte dépasse 100K tokens estimés.
  • Performance : La collecte récursive de 200+ fichiers peut prendre plusieurs secondes → l'UI doit montrer la progression (streaming SSE) et le backend doit être non-bloquant (background task).
  • Sécurité : Ne pas envoyer les fichiers ignorés (.env, .git/, .hermes/, tokens, secrets) dans le contexte. Le secret_redactor.py existant (#16) doit être appliqué au contenu avant envoi au LLM.
  • Privacy : Si le provider AI est externe (OpenRouter, DeepSeek, Gemini), le contenu des fichiers est envoyé à un tiers → warning dans l'UI avec option d'activer/désactiver par vault.
  • Scope répertoire : Ne PAS inclure les fichiers hors du répertoire (pas de remontée au parent ou de traversée de vault). Le scope est strictement le répertoire + sous-répertoires.

78. Éditeur Excalidraw — Ouverture et édition de fichiers .excalidraw

  • Effort : 3-4 jours | Impact : 🟡 | Statut : ✅ TERMINÉ (2026-09-10 — éditeur iframe complet, détection, création, autosave, support .excalidraw.md, B5 extraction texte pour la recherche, C8 création via menu contextuel, F3 E2E tests/e2e/excalidraw.spec.js, doc H1-H3. F2 non retenu. BUG-002 corrigé)

  • Description : Prise en charge native des fichiers .excalidraw dans ObsiGate avec un éditeur visuel complet intégré. L'utilisateur peut ouvrir un fichier .excalidraw depuis l'arborescence et obtenir l'éditeur de diagrammes Excalidraw directement dans ObsiGate — dessiner, modifier, sauvegarder, comme dans l'app Excalidraw standalone, mais intégré au flux de travail du vault Obsidian.

  • Pourquoi c'est important : Excalidraw est devenu le standard de fait pour les diagrammes et croquis dans l'écosystème Obsidian (plugin communautaire avec 1M+ téléchargements). Les utilisateurs créent des .excalidraw dans leur vault et s'attendent à pouvoir les visualiser et éditer. Actuellement ObsiGate traite ces fichiers comme du JSON brut — illisible. Avec l'éditeur intégré, ObsiGate devient un viewer/éditeur Excalidraw à part entière, supprimant le besoin d'ouvrir Obsidian Desktop ou l'app web Excalidraw séparément.

  • Fonctionnement général :

    • L'utilisateur clique sur un fichier .excalidraw dans l'arborescence → ObsiGate détecte l'extension et le type de contenu (type: "excalidraw" dans le JSON)
    • Au lieu du viewer markdown ou JSON brut, une iframe sandbox charge l'éditeur Excalidraw avec les données du fichier
    • L'utilisateur peut dessiner, ajouter des formes, du texte, des flèches, des images — l'expérience Excalidraw complète
    • Les modifications sont sauvegardées automatiquement (Ctrl+S ou auto-save) via postMessage → le parent écrit dans le fichier via l'API ObsiGate
    • L'éditeur respecte le thème sombre/clair d'ObsiGate
  • Architecture :

    ObsiGate SPA (content-area)
      └── <iframe sandbox="allow-scripts allow-same-origin">
            └── /frontend/excalidraw-editor.html
                  ├── import * as ExcalidrawLib from "esm.sh/@excalidraw/excalidraw"
                  ├── React + ReactDOM (fournis par Excalidraw)
                  ├── Écoute postMessage("init", {data, theme})
                  └── Poste postMessage("save", {data}) au parent
    
  • Choix technique — Pourquoi une iframe plutôt qu'une intégration directe ?

    • Isolation : Excalidraw est un composant React avec son propre DOM virtuel, ses propres polices, et des styles CSS globaux. L'iframe empêche les conflits CSS avec ObsiGate (variables CSS, polices, z-index des modales).
    • Sandbox : L'iframe isole le code d'Excalidraw — une erreur dans l'éditeur ne crashe pas l'application principale.
    • Chargement lazy : Excalidraw pèse ~2 Mo minifié + React ~40 Ko. L'iframe n'est chargée QUE quand l'utilisateur ouvre un fichier .excalidraw. Pas d'impact sur le temps de chargement initial.
    • Communication standard : postMessage est une API web native, simple et sécurisée. L'iframe n'a pas accès au DOM parent, seulement au canal de messages.
    • CSS indépendant : Le thème sombre/clair est passé comme paramètre → l'iframe applique son propre thème sans toucher aux variables CSS d'ObsiGate.
  • Sous-tâches :

A. Fichier frontend/excalidraw-editor.html — Éditeur autonome (1.5 jour)
  • A1. Structure HTML : Page minimale avec un <div id="excalidraw-container"> en plein écran. Pas de header ObsiGate — tout l'espace est pour le canvas.
  • A2. Import Excalidraw :
    <script type="module">
      import * as ExcalidrawLib from "https://esm.sh/@excalidraw/[email protected]";
      window.ExcalidrawLib = ExcalidrawLib;
    </script>
    
    Version épinglée (@0.18.0) pour la stabilité. Mise à jour manuelle testée.
  • A3. Configuration du chemin d'assets : Définir window.EXCALIDRAW_ASSET_PATH pour pointer vers le CDN des fonts/polices d'Excalidraw (nécessaire pour le rendu des polices handwriting).
  • A4. Initialisation React (import map esm.sh → React 18.3.1 épinglé) : Excalidraw nécessite React + ReactDOM. Les importer depuis esm.sh également :
    <script type="module">
      import React from "https://esm.sh/react@18";
      import ReactDOM from "https://esm.sh/react-dom@18";
      window.React = React;
      window.ReactDOM = ReactDOM;
    </script>
    
  • A5. Rendu du composant : Monter <ExcalidrawLib.Excalidraw> dans le conteneur avec les initialData reçues. Configurer les callbacks onChange pour détecter les modifications.
  • A6. Barre d'outils minimaliste (dans l'iframe, superposée en haut à droite) :
    • Bouton « 💾 Sauvegarder » → envoie les données au parent
    • Badge « Modifié » (disparaît après sauvegarde)
    • Indicateur de thème 🌙/☀️
    • Optionnel : bouton « Export PNG » et « Export SVG » (natif Excalidraw)
  • A7. Communication postMessage :
    • Réception : écouter message → si type === "init", charger data.elements + data.appState + data.files dans l'état Excalidraw. Si type === "theme", basculer theme (dark/light).
    • Émission : postMessage({type: "save", data: {elements, appState, files}}, "*") quand l'utilisateur sauvegarde.
    • Émission : postMessage({type: "ready"}, "*") au chargement pour signaler que l'iframe est prête.
    • Émission : postMessage({type: "modified", dirty: true/false}, "*") pour l'indicateur de modification.
  • A8. Gestion des erreurs : Si les données sont invalides (JSON corrompu, pas un fichier Excalidraw), afficher un message d'erreur stylisé dans l'iframe.
B. Backend — Détection et API (0.5 jour)
  • B1. Ajout à SUPPORTED_EXTENSIONS : Ajouter .excalidraw dans backend/indexer.py:56 pour que les fichiers apparaissent dans l'arborescence et soient indexés.
  • B2. Icône : Ajouter .excalidraw dans EXT_ICONS (frontend/js/utils.js) → icône pen-tool ou edit-3 (Lucide).
  • B3. Détection dans api_file_view() : Dans backend/main.py, pour les fichiers .excalidraw :
    • Lire le JSON
    • Vérifier data.get("type") === "excalidraw"
    • Retourner is_excalidraw: true + les données parsées (elements, appState, files)
    • Si le JSON est invalide ou n'est pas un fichier Excalidraw valide → fallback sur le viewer JSON standard
  • B4. Endpoint de sauvegarde : Le endpoint existant PUT /api/file/{vault} fonctionne déjà pour écrire du contenu. L'iframe envoie le JSON modifié via postMessage → le parent appelle l'API existante. Aucun nouvel endpoint nécessaire.
  • B5. Indexation du contenu texte (FAIT 2026-09) : extract_excalidraw_indexable() extrait element.text des éléments (JSON pur et .excalidraw.md compressé) → recherche TF-IDF fonctionnelle.
  • B6. Contenu initial pour nouveaux fichiers : Définir le squelette JSON minimum pour un fichier .excalidraw vide :
    {"type":"excalidraw","version":2,"elements":[],"appState":{"viewBackgroundColor":"#ffffff"},"files":{}}
    
    Ce squelette est retourné par le backend quand on crée un fichier .excalidraw (utilisé par POST /api/file/{vault}).
C. Frontend — Intégration dans le viewer (1 jour)
  • C1. Module frontend/js/excalidraw-viewer.js (nouveau) : Fonction renderExcalidraw(container, data, vault, path) :
    • Crée une <iframe> avec src="/frontend/excalidraw-editor.html" et sandbox="allow-scripts allow-same-origin"
    • Stocke une référence à l'iframe pour la communication
    • Attend le message ready de l'iframe
    • Envoie postMessage({type: "init", data: {elements, appState, files}, theme}) à l'iframe
    • Écoute les messages save → appelle saveFile(vault, path, JSON.stringify(data)) via l'API existante
    • Écoute les messages modified → met à jour l'indicateur dans la barre d'onglets
    • Gère le thème : écoute themeChanged → envoie postMessage({type: "theme", theme}) à l'iframe
  • C2. Dispatch dans viewer.js : Dans renderFileContent() ou renderFile() :
    • Après la détection data.is_json, ajouter une branche : si data.is_excalidraw === true → appeler renderExcalidraw(container, data, vaultName, filePath)
    • Ne PAS passer par le viewer markdown standard
  • C3. Barre d'outils contextuelle : Dans la toolbar du viewer (celle avec Copier/Source/Éditer/PDF/pop-out) :
    • Pour les fichiers .excalidraw : remplacer « Éditer (Forge) » par « Ouvrir dans Excalidraw.com » (lien externe, nouvel onglet)
    • Garder « Télécharger » (.excalidraw) et « pop-out »
    • Badge « Excalidraw » avec icône pen-tool
  • C4. Auto-save : Débounce 2 secondes après la dernière modification dans l'iframe → sauvegarde automatique silencieuse (comme l'éditeur markdown #29). L'iframe émet modified → le parent démarre un timer → au bout de 2s sans nouvelle modification → postMessage({type: "requestSave"}) → l'iframe répond avec save → le parent écrit via l'API.
  • C5. Raccourci Ctrl+S : L'iframe intercepte Ctrl+S → envoie save au parent → le parent sauvegarde → confirmation visuelle (toast « Excalidraw sauvegardé »).
  • C6. Compatibilité Split View (#75) : L'iframe s'affiche dans le content-area du panneau actif. Le PaneTabManager gère le cache : quand on switch d'onglet, l'état de l'iframe est préservé (elle reste dans le DOM, juste masquée). Plusieurs iframes Excalidraw peuvent coexister dans différents panneaux.
  • C7. Création via la modale « Nouveau fichier » : Dans frontend/js/ui.js, fonction showCreateFileModal() :
    • Ajouter <option value=".excalidraw">Excalidraw (.excalidraw)</option> dans le <select id="file-ext-select"> (après .json)
    • Quand l'extension .excalidraw est sélectionnée, le backend crée le fichier avec le squelette JSON minimum (B6)
    • Après création → openFile(vault, path) → le viewer détecte is_excalidraw: true → l'iframe s'ouvre avec le canvas vierge
    • Fonctionne aussi via la palette de commandes Ctrl+Alt+Space → « Nouveau fichier » (action create-file existante)
  • C8. Création via le menu contextuel (FAIT 2026-09 — frontend/js/ui.js _createExcalidraw(), option « Nouveau diagramme Excalidraw » sur les répertoires, E2E couvert) : ouvre la modale avec .excalidraw pré-sélectionné.
D. CSS & Design (0.5 jour)
  • D1. Styles de l'iframe dans ObsiGate : L'iframe occupe 100% du content-area (width: 100%; height: 100%; border: none;). Aucun padding ni marge.
  • D2. Thème dark/light : L'iframe reçoit le thème courant → Excalidraw applique son thème interne (theme="dark" ou theme="light"). Les couleurs sont cohérentes avec ObsiGate grâce à la palette d'Excalidraw.
  • D3. Écran de chargement : Pendant le chargement de l'iframe (React + Excalidraw ~2 Mo), afficher un spinner « Chargement de l'éditeur Excalidraw... » dans le content-area. L'iframe envoie ready → le spinner disparaît.
  • D4. Responsive : L'iframe s'adapte à la largeur du panneau. En mode mobile (<768px), l'éditeur Excalidraw est utilisable (UI tactile native).
E. Gestion des conflits et edge cases (0.5 jour)
  • E1. Fichier modifié à l'extérieur : Si le fichier est modifié par Syncthing/watcher pendant l'édition → détecter via le watcher → afficher un bandeau « Ce fichier a été modifié à l'extérieur. Recharger ? » avec boutons [Recharger] [Ignorer].
  • E2. Plusieurs onglets : Deux onglets sur le même fichier .excalidraw → le second détecte que le fichier est déjà ouvert → focus l'onglet existant (comportement existant du TabManager #E4).
  • E3. Fichier vide ou nouveau : Couvert par C7/C8 — la création d'un .excalidraw produit un canvas vierge avec le squelette JSON minimum (B6). L'iframe gère nativement le cas elements: [].
  • E4. Fichier corrompu (fallback viewer JSON + tests test_invalid_json_excalidraw_fallback/test_json_without_excalidraw_type) : Si le JSON ne contient pas type: "excalidraw" ou est invalide → fallback sur le viewer JSON standard avec un message « Ce fichier .excalidraw semble corrompu ».
  • E5. Pop-out : Le bouton pop-out fonctionne — il ouvre l'éditeur dans une popup séparée avec sa propre iframe. Utile pour éditer sur un deuxième écran.
  • E6. Annulation (Ctrl+Z) : Natif dans Excalidraw — l'historique d'annulation est géré par l'état interne de l'iframe. Pas besoin d'interaction avec le parent.
F. Tests (0.5 jour) — F1 ✅ (6 tests test_excalidraw.py)
  • F1. Tests backend :
    • test_excalidraw_detection.py : fichier .excalidraw valide → is_excalidraw: true, JSON invalide → fallback JSON, fichier sans type: excalidraw → fallback
    • test_excalidraw_search.py : texte extrait des éléments → recherchable via TF-IDF
  • F2. Tests frontend — ⚪ NON RETENU :
    • Chargement de l'iframe avec des données de test
    • Communication postMessage (init → ready → save)
    • Changement de thème propagé à l'iframe
  • F3. Tests E2E (Playwright) (FAIT 2026-09 — tests/e2e/excalidraw.spec.js) :
    • Ouvrir un fichier .excalidraw → l'iframe se charge → le canvas Excalidraw est visible
    • Dessiner un rectangle → sauvegarder → recharger → le rectangle est toujours là
    • Basculer thème sombre → l'iframe passe en dark mode
G. Points d'attention / Risques
  • Taille du bundle : React + ReactDOM + Excalidraw ≈ 2.5 Mo minifié. Chargé depuis esm.sh (CDN global, cache HTTP). L'impact n'est perceptible qu'à la première ouverture d'un .excalidraw. Solution : précharger l'iframe en arrière-plan (<link rel="prefetch">) après le chargement de l'app.
  • Performance React dans iframe : React dans une iframe fonctionne parfaitement — c'est un contexte JavaScript indépendant. Testé sur Chrome, Firefox, Safari, Edge.
  • CORS et esm.sh : Les modules ESM depuis esm.sh sont servis avec les headers CORS appropriés. L'iframe est same-origin (/frontend/excalidraw-editor.html) donc pas de problème.
  • Mises à jour d'Excalidraw : La version est épinglée (@0.18.0). Pour mettre à jour, changer le numéro dans le HTML + tester. Le format de données .excalidraw est stable (v2 depuis 2021).
  • Sécurité postMessage : Vérifier event.origin dans les deux sens. L'iframe n'accepte que les messages de window.parent. Le parent n'accepte que les messages de l'iframe connue. Pas de "*" en production.
  • Tauri Desktop (#77) : L'iframe se charge depuis le filesystem local (tauri://localhost/frontend/excalidraw-editor.html). Les imports ESM depuis esm.sh fonctionnent si le réseau est disponible. Pour le mode offline, bundler Excalidraw dans l'app desktop (à traiter dans #77, pas ici).
  • Pas d'édition collaborative : Cette implémentation est mono-utilisateur. La collaboration temps réel (#62) pourra être étendue aux fichiers .excalidraw ultérieurement via le même mécanisme Yjs.
H. Documentation utilisateur
  • H1. Mettre à jour README : ajouter .excalidraw dans les formats supportés (FR + EN)
  • H2. Ajouter dans le guide d'utilisation (Quick Help) : section « Diagrammes Excalidraw » (i18n FR/EN)
  • H3. Note : « Les fichiers .excalidraw créés avec le plugin Obsidian Excalidraw sont compatibles »

⚪ Backlog — Priorité 4 (P4)

67. Notifications web — Push API

  • Effort : 2 jours | Impact : 🟢
  • Description : Recevoir des notifications sur le bureau ou le téléphone quand un fichier est modifié, ajouté ou supprimé dans un de vos vaults, même si ObsiGate n'est pas ouvert dans le navigateur.
    • Fonctionnement : Le navigateur s'abonne auprès du serveur via la Push API (standard W3C). Le serveur stocke l'abonnement (endpoint + clés de chiffrement). Quand un fichier change (détecté par le watcher existant), le serveur envoie une notification chiffrée au push service du navigateur (Firebase pour Chrome, APNs pour Safari, etc.), qui la relaye au navigateur même s'il est fermé. Le service worker ObsiGate affiche alors la notification système.
    • Contenu : titre du fichier modifié, nom du vault, type d'action (créé/modifié/supprimé). Un clic sur la notification ouvre directement le fichier dans ObsiGate.
    • Configuration : activation/désactivation par vault. L'utilisateur choisit pour quels vaults il reçoit des notifications.
    • VAPID : protocole d'authentification volontaire qui permet au serveur de s'identifier auprès du push service sans avoir à s'enregistrer comme application. Une paire de clés publique/privée est générée — la clé publique est partagée avec le navigateur, la clé privée reste sur le serveur.
  • Pourquoi c'est important : Collaborer sans avoir à constamment rafraîchir l'interface pour voir si quelqu'un a modifié quelque chose. Particulièrement utile en équipe ou pour les vaults partagés via Syncthing — on sait immédiatement quand une note est mise à jour.
  • 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 : Un endpoint /api/health qui ne se contente pas de dire « je suis vivant », mais donne un diagnostic complet de l'état du serveur. Essentiel pour le monitoring et le debugging.
    • Métriques exposées :
      • Index : nombre de fichiers indexés, nombre de tokens, date de la dernière indexation complète. Permet de détecter si l'indexeur est bloqué ou ne tourne plus.
      • Mémoire : consommation RAM du processus (RSS), heap Python utilisé. Permet de détecter les fuites mémoire avant qu'elles ne crashent le serveur.
      • Uptime : depuis quand le serveur tourne. Simple, mais indispensable pour corréler un problème avec un redémarrage.
      • Connexions : nombre de connexions SSE actives (recherche en cours, streaming AI, etc.). Permet de savoir combien d'utilisateurs sont connectés.
      • Backups : nombre total, âge du backup le plus ancien, espace disque consommé. Permet de détecter si les backups s'accumulent anormalement.
      • Disque : espace libre sur la partition /data. Évite le crash silencieux quand le disque est plein.
    • Format : JSON structuré, facile à intégrer dans des outils de monitoring (Prometheus, Grafana, Uptime Kuma, Healthchecks.io).
    • Sécurité : l'endpoint public /api/health retourne ok ou degraded, l'endpoint détaillé est protégé par authentification admin.
  • Pourquoi c'est important : Actuellement, la seule façon de savoir si ObsiGate a un problème est de constater que ça ne marche plus. Avec un health check enrichi, on peut configurer des alertes automatiques (via Uptime Kuma ou un cron) qui préviennent AVANT que l'utilisateur ne remarque le problème.
  • 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 : La recherche actuelle (TF-IDF) ne trouve que les documents contenant EXACTEMENT les mots tapés. La recherche sémantique comprend le SENS de la requête et trouve des documents pertinents même s'ils utilisent des mots différents.
    • Exemple concret : Vous cherchez « comment sauvegarder mes données ». La recherche TF-IDF ne trouvera que les documents contenant « sauvegarder » ET « données ». La recherche sémantique trouvera aussi un document titré « Stratégie de backup automatique » ou « Protection contre la perte de fichiers » parce qu'elle comprend que ces phrases parlent de la même chose.
    • Fonctionnement technique :
      • Chaque document (ou chunk de ~512 tokens) est converti en un vecteur (une liste de 384 nombres) par un modèle de langage léger comme all-MiniLM-L6-v2 (80 Mo, s'exécute en ~2ms par document sur CPU). Ce vecteur capture le sens — deux phrases qui veulent dire la même chose auront des vecteurs très proches.
      • Au moment de la recherche, la requête utilisateur est elle aussi convertie en vecteur.
      • On calcule la similarité cosinus entre le vecteur de la requête et les vecteurs de tous les documents. Les documents avec la similarité la plus élevée sont retournés.
      • Recherche hybride : on combine le score TF-IDF (pertinence par mots-clés exacts) et le score sémantique (pertinence par sens) via RRF (Reciprocal Rank Fusion) — les documents bien classés par les deux méthodes remontent en premier.
    • Stockage : les vecteurs sont stockés avec FAISS (Facebook AI Similarity Search), une bibliothèque optimisée qui permet de chercher parmi des millions de vecteurs en quelques millisecondes.
    • Indexation : les embeddings sont générés une fois à l'indexation du fichier (pas à chaque recherche). Un fichier modifié voit son embedding regénéré automatiquement par le watcher.
  • Pourquoi c'est important : La recherche par mots-clés échoue dans ~30% des cas où l'utilisateur ne se souvient pas des mots exacts utilisés dans ses notes. La recherche sémantique résout ce problème. C'est particulièrement utile pour les gros vaults (500+ notes) où on ne peut pas tout parcourir manuellement.
  • 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 — Backend ✅, Frontend ✅

  • Effort : 2 jours | Impact : 🟢 | Statut : ✅ Terminé (2026-08, commits 46be24f / 88ab8db / 01453bc)
  • Description : Une page web dédiée accessible uniquement aux administrateurs qui centralise tout le monitoring et la gestion du serveur ObsiGate en un seul endroit. Un cockpit de pilotage pour le sysadmin.
  • Implémentation réelle (vérifiée) :
    • Backend backend/admin.py (nouveau, 261 lignes) — 4 endpoints admin-gated (require_admin) :
      • GET /api/admin/stats — CPU/RAM/Disk/Uptime via psutil
      • GET /api/admin/audit — 500 dernières entrées d'audit avec filtres user/action/limit/offset
      • GET /api/admin/backup-stats — compte + taille + age par vault
      • GET /api/admin/stream — Server-Sent Events qui push les stats toutes les 5s
    • backend/main.py — routeur monté + middleware gzip bypass pour /api/admin/stream
    • backend/requirements.txt — ajout psutil>=5.9
    • Tests : tests/test_admin.py (13 tests, 100% verts) — couvrent auth + filtres + format SSE
  • Complété (2026-08) :
    • frontend/admin.html (472 l.) + frontend/js/admin.js (544 l.) — dashboard standalone avec navigation par sections sticky + thèmes
    • Lien « Admin » dans le user menu (#admin-menu-row, gating role === "admin")
    • Widgets temps réel via EventSource /api/admin/stream + snapshot /api/admin/stats
    • CRUD users + fix routing /admin.html + fix scroll

72. API publique documentée — OpenAPI 3.1

  • Effort : 1-2 jours | Impact : 🟢
  • Description : Une page de documentation interactive et auto-générée de toutes les API REST d'ObsiGate, accessible via un bouton dans l'interface. L'équivalent d'un manuel technique mais qui se teste en direct.
    • OpenAPI 3.1 : c'est le format standard mondial pour décrire une API REST. Un seul fichier JSON/YAML contient la description de tous les endpoints, leurs paramètres, les formats de réponse, les codes d'erreur, et les modèles de données. Ce standard est supporté par des centaines d'outils.
    • Swagger UI : une interface web qui lit le fichier OpenAPI et génère automatiquement une documentation interactive. L'utilisateur voit chaque endpoint, peut remplir les paramètres dans un formulaire, cliquer « Execute » et voir la réponse réelle de l'API en direct. Parfait pour les développeurs qui veulent intégrer ObsiGate à leurs scripts ou comprendre comment fonctionne l'API.
    • Redoc : une alternative à Swagger UI, plus propre et orientée lecture, idéale pour la documentation publique.
    • Contenu documenté :
      • Les 40+ endpoints existants, regroupés par catégorie (Fichiers, Vaults, Recherche, Auth, AI, Backups).
      • Chaque endpoint avec description, paramètres obligatoires/optionnels, exemples de requête et réponse.
      • Les modèles de données Pydantic exposés comme schémas JSON (ex: structure d'un FileInfo, d'un SearchResult).
      • Les codes d'erreur possibles avec leur signification.
    • Auto-génération : FastAPI génère déjà partiellement le schéma OpenAPI. Le travail consiste à compléter les docstrings manquantes, ajouter response_model sur les endpoints qui n'en ont pas, et enrichir avec des exemples.
  • Pourquoi c'est important : Une API sans documentation est comme un logiciel sans interface — techniquement fonctionnel mais inutilisable. Avec une doc OpenAPI, ObsiGate devient intégrable dans n'importe quel écosystème. Un développeur peut en 5 minutes comprendre comment uploader un fichier, chercher dans un vault, ou récupérer le contenu d'une note — et écrire un script qui automatise ses workflows.
  • 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

🔵 En cours (P2) — Application Desktop

77. Application Desktop native — Tauri (Windows / Linux / macOS)

  • Effort : 8-12 jours | Impact : 🟡 | Framework : Tauri v2 (Rust + Webview)

  • Description : Packager ObsiGate en application desktop native autonome. L'utilisateur télécharge un .exe (Windows) ou .AppImage (Linux), l'installe, et lance ObsiGate comme n'importe quelle app — sans Docker, sans terminal, sans navigateur. Le backend Python est embarqué, le frontend s'affiche dans une webview native. L'expérience est identique à l'application web, avec des capacités supplémentaires (accès fichiers natif, notifications OS, tray icon).

  • Architecture :

    ObsiGate.exe (Tauri shell ~5 Mo)
    ├── python-embed/          ← Python 3.11 embarqué (~30 Mo)
    │   ├── backend/           ← Code FastAPI existant
    │   └── site-packages/     ← Dépendances gelées
    ├── frontend/              ← HTML/CSS/JS (identique au web)
    └── obsigate-desktop       ← Binaire Rust (lance Python + ouvre webview)
    
  • Pourquoi Tauri plutôt qu'Electron ?

    • Binaire de ~35-40 Mo contre ~180 Mo pour Electron (pas de Chromium embarqué)
    • RAM idle ~50 Mo contre ~200 Mo — la webview utilise le moteur du navigateur système
    • Rust gère le cycle de vie du backend Python (spawn, health check, kill propre)
    • Signature de code native Windows/macOS pour éviter les faux positifs antivirus
    • Auto-update natif via le mécanisme de Tauri (vérifie un endpoint JSON)
  • Sous-tâches :

A. Initialisation du projet Tauri (1 jour) — ✅ livré (vérifié 2026-09)
  • Toolchain : tauri-cli 2.11.4 / rustc 1.94.1
  • Projet Tauri v2 dans desktop/ (Cargo.toml, build.rs)
  • tauri.conf.json : fenêtre 1200×800 (min 800×600), titre "ObsiGate"
  • Build : cibles .msi/.nsis (Windows), .deb/.AppImage (Linux)
  • Icônes desktop dans desktop/icons/ (.ico, .icns, .png)
B. Intégration du backend Python (2-3 jours) — ✅ livré (variante : uvicorn spawné directement, pas de sidecar.py)
  • Bundle Python : desktop/python-embed/ (python3.11-embed + site-packages, validé par validate-structure.sh)
  • Lancement backend : spawn_backend() Rust lance python-embed -m uvicorn backend.main:app (équivalent sidecar), logs dans %APPDATA%/ObsiGate/logs/backend.log
  • main.rs : spawn processus fils, health check (GET /api/health, 30 essais × 2s), kill propre (SIGTERM→wait→kill)
  • Menu tray (voir section C ✅)
  • Gestion du port : pick_free_port() scan 17890..17899 si occupé (commit c066b2c, 3 tests Rust)
C. Fonctionnalités desktop natives (2-3 jours) — ✅ COMPLÉTÉ
  • Sélecteur de dossier : pick_vault_folder via tauri_plugin_dialog → ajoute le vault dans config.json
  • Thème système : get_system_theme lit le thème OS → appliqué automatiquement
  • Notifications natives : tauri-plugin-notification intégré — remplace le service worker Push API
  • Associations de fichiers : .md → « Ouvrir avec ObsiGate » dans tauri.conf.json
  • Menu natif : Fichier (Nouvelle fenêtre, Fermer, Quitter) / Édition (Annuler, Rétablir, Couper, Copier, Coller, Tout sélectionner) / Aide (À propos)
  • Raccourcis clavier : Ctrl+N, Ctrl+W, Ctrl+Q, Ctrl+Z, Ctrl+Shift+Z, Ctrl+X/C/V/A
  • Tray icon : menu contextuel (Ouvrir, À propos, Quitter) + toggle fenêtre au clic gauche
  • Single instance : tauri-plugin-single-instance — deuxième lancement focus la fenêtre existante
  • Auto-update : tauri-plugin-updater configuré → vérifie les releases Gitea
  • Pas de terminal visible : #![windows_subsystem = "windows"] + CREATE_NO_WINDOW sur le processus Python
  • Persistance fenêtre : position/taille sauvegardée dans config.json
D. Build et distribution (2 jours) — ✅ COMPLÉTÉ
  • CI/CD automatisé : workflow Gitea Actions .gitea/workflows/desktop-build.yml — build Windows + Linux à chaque push sur main (si desktop/ modifié), upload des artefacts .msi/.AppImage/.deb en release
  • Build Windows local : cargo build --release vérifié (rustc 1.94.1) — cargo tauri build --bundles msi prêt
  • Build Linux local : workflow CI couvre .deb, .rpm, .AppImage
  • Auto-update : tauri-plugin-updater configuré → vérifie https://git.dracodev.net/api/v1/repos/Projets/ObsiGate/releases/latest
  • Signature de code : configurer le certificat (optionnel mais recommandé pour Windows)
  • Page de release : README desktop existe (desktop/README.md)
E. Expérience utilisateur (1 jour) — ✅ COMPLÉTÉ
  • Écran de chargement pendant le démarrage du backend (« ObsiGate démarre... » avec spinner) — splash inline #boot-splash dans index.html, retiré quand app.js signale le boot ; statut mis à jour depuis Rust
  • Gestion des erreurs : backend crash → showBackendCrashBanner() appelé par le monitor loop toutes les 5s
  • Sauvegarde des préférences desktop : position/taille fenêtre sauvées dans %APPDATA%/ObsiGate/config.json au close + restauration au startup
  • Première expérience : config par défaut auto-créée au premier lancement (vault ~/voute_obsidian, dir ~USERPROFILE)
  • Wizard interactif « Choisissez votre vault » au premier lancement (optionnel — config auto suffisante)
  • Jumplist vaults récents dans le menu Démarrer (optionnel)
F. Tests (1 jour) — ✅ COMPLÉTÉ
  • 16 tests Rust unitaires : config roundtrip, JSON parsing (empty/partial/corrupted), vault dedup, dir remove, backend URL, paths, branding, edge cases

  • Build debug + release vérifié (rustc 1.94.1, tauri-cli 2.11.4)

  • CI desktop workflow existant (desktop-build.yml)

  • Test E2E : installation → premier lancement → wizard vault → ouverture fichier (manuel)

  • Test E2E : tray icon → réduire → restaurer (manuel)

  • Test E2E : notifications natives → fichier modifié → popup OS (manuel)

  • Test E2E : association .md → double-clic → ouvre dans ObsiGate (manuel)

  • Test E2E : auto-update → nouvelle version → install (manuel)

  • Test E2E : désinstallation propre (manuel)

  • Prérequis techniques :

    • Rust ≥ 1.75 (stable) — installé via rustup
    • Tauri CLI ≥ 2.0 — cargo install tauri-cli
    • Python 3.11 embed — téléchargé depuis python.org
    • NSIS (Windows) — pour le générateur d'installateur .exe
    • AppImageKit (Linux) — pour le packaging portable

📊 Résumé des efforts

Priorité Items Effort total estimé
✅ Complété #1 → #59, #63-68, #71, #74, #75, #76, #78 (58 E2E, 59 offline, 63 i18n, 64 MFA TOTP+WebAuthn, 65 thèmes, 66 export, 67 push, 68 health, 71 admin, 74 PDF, 75 split view, 76 BooksLM, 78 Excalidraw) ~80 jours réalisés
🔵 P2 restant #77 Desktop : signature code (optionnel), wizard 1er lancement (optionnel), 6 tests E2E manuels ~1-2 jours
⚪ P3 restant #62 Collaboration Yjs (5-7j) ~5-7 jours
⚪ P4 restant #69 Mobile éditeur (2-3j) · #70 Sémantique (4-5j) · #72 OpenAPI (1-2j) · #73 Sync (6-8j) 13-18 jours
Total restant 6 items + finitions ~19-27 jours

#75 : E2E matriciels 37/37 verts (split-view.spec.js + split-view-matrix.spec.js) — 2026-09-08.


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.
  • #59 (hors-ligne), #63 (i18n), #64 (MFA), #65-66, #67-68, #71, #74-76, #78 sont livrés — les P3 restants à plus fort rapport effort/valeur : #72 (OpenAPI, 1-2j) et #62 (collaboration).
  • #78 est terminé (B5 recherche texte, C8 menu contextuel, F3 E2E, doc H1-H3) ; F2 non retenu. BUG-002 (loading infini Excalidraw) corrigé par les commits a4ea322/185d603.