# Changelog — ObsiGate Toutes les modifications notables d'ObsiGate sont documentées dans ce fichier. Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements non publiés sont dans la section > [Unreleased](#unreleased). La dernière version publiée est **2.2.1**. --- ## [Unreleased] ### Ajouté - **#89 Drag & Drop complet de fichiers/dossiers & intégration Assistant IA** — solution complète de glisser-déposer de fichiers individuels, multiples et **dossiers récursifs** depuis l'OS vers le vault (`POST /api/vault/{vault}/batch-upload`, FileSystem API), overlay plein écran à double cible (dépôt vault vs analyse IA), surbrillance des cibles dans l'arborescence, et dropzone BooksLM pour injection de contexte ou pièces jointes. Fiche : [docs/features/drag-and-drop-ai.md](./docs/features/drag-and-drop-ai.md). - **#89 Pretty print fichiers texte** — bouton « Pretty » dans le mode lecture des fichiers non-markdown (code, JSON, configs, logs...) : formatage JSON, numéros de ligne avec gouttière et coloration syntaxique highlight.js alignée sur le thème clair/sombre, avec colorateur générique de secours (chaînes, commentaires, nombres, niveaux de log) quand highlight.js ne reconnaît pas le langage (txt, log, plaintext). - **#88 Assistant IA : contexte applicatif** — l'assistant en mode **Général** reçoit l'état vivant de l'interface (`app_context`) et connaît désormais les documents ouverts, le répertoire et le vault courants, la recherche en cours (requête + premiers résultats affichés) et les fichiers récemment modifiés du utilisateur. Le prompt Général est enrichi d'un bloc « Contexte applicatif actuel » (`build_general_system_prompt(vaults, app_context, recent_files)`). Fiche : [docs/features/ai-app-context.md](./docs/features/ai-app-context.md). - **#90 Barre d'actions du document — regroupement fonctionnel** : les boutons en haut du document sont réordonnés en quatre groupes séparés par des barres verticales (`span.action-sep`) : `[TOC] [pop-out] [Bookmark] | [Editer] [Source] [.md] [Forge] | [Copier] [PDF] [Export] | [Partager]`. Le bouton « Pretty » (fichiers texte) rejoint le groupe édition, les boutons non applicables à un type de fichier sont omis et les spacers d'un groupe vide ne sont pas rendus. Fichiers texte non markdown : `[pop-out] [Bookmark] | [Editer] [Source] [.ext] [Pretty] | [Copier] | [Partager]` (pas de TOC, pas de PDF/Export). La fenêtre pop-out (`popout.html`) est alignée sur le même ordre markdown (groupes nav / édition / export / partage). Tests : `tests/frontend/toolbar-order.test.mjs` (9). - **#91 Assistant IA — zone de discussion façon Notion** : le fil de discussion est reformaté (messages chronologiques, post utilisateur en bulle alignée à droite, réponse assistant pleine largeur sans fond, appels d'outils agent repliés en bloc « N étapes ») ; après l'envoi, la **question est ancrée en haut** de la zone visible (`scrollIntoView` `block:start` + `scroll-margin-top`) et la réponse se diffuse en dessous — le streaming ne provoque plus de saut de scroll ; un libellé **discret** au-dessus de chaque réponse rapporte le fournisseur et le modèle réellement utilisés (SSE `provider`/`model`) ; une **barre d'actions** au survol sous chaque bloc : « Copier » (texte brut + toast) et, pour les réponses, « Ajouter » (insertion dans le document ouvert dans l'éditeur Forge). Complément : la section « N étapes » affiche désormais des libellés humains produits par le backend (`backend/tools/labels.py`, événements SSE en direct pendant l'exécution + ligne « Réflexion »), et deux nouveaux outils web principaux sont disponibles en mode agent : `web_search` (SearXNG auto-hébergé) et `fetch_url` (page publique → texte, garde SSRF). Le reste des catégories d'outils Notion est documenté pour le futur dans la fiche. Fiche : [docs/features/ai-assistant-conversation-ux.md](./docs/features/ai-assistant-conversation-ux.md) Tests : `tests/frontend/ai.test.mjs` (76), `tests/test_tool_labels.py` (8), `tests/test_web_tools.py` (8). ### Corrigé - **#91 — Bulle utilisateur trop étroite & ancre d'envoi retardée** : le plafond de largeur était appliqué **deux fois** (wrapper 80 % × bulle 85 % ≈ 68 % du fil), d'où des lignes très courtes ; le plafond est désormais porté par la seule bulle (90 %), le wrapper s'étirant → ~81 % de la largeur du fil (≈48 caractères/ligne au lieu de ~28). Par ailleurs, le rendu du message assistant « placeholder » écrasait la position de défilement juste après l'envoi (l'ancre était annulée : la question restait en bas jusqu'au premier token). `_isLoading` est maintenant posé avant ce rendu et l'ancrage d'envoi est **instantané** — la question passe en haut du fil dès la soumission et y reste pendant tout le streaming. - **BUG-041 — Assistant IA bloqué sur un répertoire vide** : l'assistant ne renvoie plus `⚠ Error: Aucun fichier markdown trouvé dans ce dossier` (HTTP 404). Le contexte vide dégrade vers le prompt Général augmenté d'un bloc « Dossier vide », et la requête aboutit sans contexte documentaire. - **BUG-042 — Liens de fichiers de l'assistant non fiables** : les liens des réponses suivent désormais une règle déterministe — un simple nom de fichier copie le nom dans le presse-papiers, un chemin de dossier est révélé dans l'arborescence, un chemin de fichier l'ouvre. Le chemin est **résolu contre l'index du vault** (exact → suffixe → basename unique) avant d'agir ; les chemins non résolus copient le nom au lieu d'afficher `File not found`. Les **noms/chemins contenant des espaces** sont pris en charge (`Mon dossier/Ma note.md`) : liens markdown (y compris cibles `<…>` et `%20`), code inline, et mentions brutes liées uniquement si le chemin existe dans l'index du vault. Les **caractères accentués** sont reconnus (classes de caractères Unicode `\p{L}\p{N}\p{M}`, comparaison normalisée NFC : une mention décomposée `e`+accent correspond à une entrée d'index précomposée) et un chemin **préfixé par le nom du vault** (`TestVault/Recettes/Pizza Maison.md`) est résolu puis ouvert dans ce vault, le préfixe étant retiré. - **BUG-043 — Liste des fournisseurs de l'assistant non synchronisée avec la configuration** : ajouter ou supprimer une clé API dans la configuration du projet met désormais à jour **immédiatement** le menu Fournisseur de la barre latérale de l'assistant, sans recharger la page. Le picker ne lisait `/api/ai/status` qu'une fois, à sa construction, et le panneau de l'assistant est un singleton monté pour toute la session : la liste restait figée. Le nouveau `refreshAIPickers()` (`frontend/js/ai.js`) reconstruit chaque picker monté dans son emplacement `.ai-picker-slot` et est appelé après l'enregistrement (`saveAIKeys`) et la suppression (`deleteAIKey`) d'une clé (`frontend/js/config.js`). Un emplacement est conservé même sans fournisseur configuré, donc le **premier** fournisseur ajouté s'y monte aussi. Une sélection dont le fournisseur n'est plus configuré est purgée de `obsigate_ai_picker` (retour au défaut au lieu d'un modèle fantôme). - **BUG-044 — Capacités des modèles erronées (aucun modèle Mistral vision)** : la bulle ⓘ et le sélecteur de modèle par défaut n'affichaient **aucun** modèle Mistral « Vision capable », alors que `GET https://api.mistral.ai/v1/models` en déclare 28 (`mistral-medium`, `mistral-small`, `ministral-*`, `magistral-*`, `mistral-ocr-*`, `mistral-vibe-cli-*`). Cause : la table de capacités était **entièrement statique** et aucun de ses motifs ne correspondait aux familles Mistral actuelles (seul `pixtral`, retiré de l'API, les matchait). ObsiGate lit désormais les capacités **déclarées par le fournisseur** (nouveau `backend/provider_capabilities.py`, snapshot mis en cache par `GET /api/config/ai-models`) : Mistral (`capabilities.completion_chat` / `vision` / `audio_transcription` / `audio_speech`) et OpenRouter (`architecture.input_modalities` / `output_modalities`) sont pris en charge ; la table statique ne sert plus qu'à combler les drapeaux non déclarés et de repli hors ligne. La table est également corrigée (familles vision Mistral, `mistral-ocr` = vision sans chat) et le défaut du fournisseur Mistral ne prétend plus qu'un modèle non reconnu sait produire des embeddings (BUG-044bis : `mistral-large-latest` / `codestral-latest` étaient annoncés « Embeddings »). Conséquence : la porte vision de l'assistant (`frontend/js/bookslm.js`, `backend/bookslm_routes.py`) accepte enfin les images avec un modèle Mistral vision. ### Sécurité - **#84 Consolidation & sécurité — phase 1 (BUG-021 → BUG-034)** — traitement des vulnérabilités de la revue statique du 2026-09-13 : - **BUG-021/022 — XSS stocké** : nouveau sanitizer serveur en liste blanche (`backend/services/sanitizer.py`, stdlib) appliqué au rendu markdown ; échappement systématique du `title`, du frontmatter et du JSON de la page publique `/s/{token}` (`` neutralisé). Tests : `tests/test_security_hardening.py` (9). - **BUG-023 — brute-force MFA** : rate-limit IP + compte et verrouillage de compte sur `mfa/totp/verify`, `mfa/recovery` et `mfa/webauthn/verify` (`_enforce_mfa_rate_limit`). - **BUG-024 — traversal inter-vaults** : `resolve_safe_path` compare désormais les chemins par **segment** (`Path.relative_to` + repli casse-insensible), plus par préfixe de chaîne : `vault` ne peut plus lire `vault-evil`. - **BUG-025 — ReDoS** : validation des regex utilisateur (longueur max, rejet des quantificateurs imbriqués/backrefs), contenu tronqué et nombre de matchs plafonné (`backend/services/regex_safety.py`), appliqué à la recherche avancée et au find/replace. - **BUG-026 — SSRF webhooks** : validation d'URL (HTTPS par défaut, IP privées/boucle interdites, résolution DNS vérifiée au dispatch, redirections non suivies) et **externalisation du secret** dans `data/webhook_secrets.json` (0600) ou variable `OBSIGATE_WEBHOOK_SECRET_` (plus de secret en clair dans `webhooks.json`). - **BUG-027 — sessions** : rotation du refresh token à chaque usage, révocation du JTI de l'access token au logout, et vérification de la révocation dans le middleware. - **BUG-028 — politique de mot de passe** : validation centralisée (8–128 caractères) à la création, à la modification admin et au changement ; `password_changed_at` invalide tous les jetons émis avant un changement de mot de passe. - **BUG-029 — race `users.json`** : verrou `threading.RLock` autour des cycles lecture-modification-écriture. - **BUG-030 — audits** : l'adresse IP réelle du client (`X-Forwarded-For` si `OBSIGATE_TRUST_PROXY=true`) est injectée dans `current_user` et consignée dans les audits. - **BUG-031 — rate-limiter** : budget **par compte** en plus du budget par IP (rotation d'IP neutralisée) ; limite mono-process documentée. - **BUG-032 — indexation** : `_scan_vault` utilise `os.walk(followlinks=False)` et refuse tout symlink sortant de la racine du vault. - **BUG-033 — recherche O(N)** : la recherche simple et l'outil IA `search_fulltext` utilisent l'inverted index (repli sur le scan pendant la construction). - **BUG-034 — CSP & jetons** : le token d'accès n'est plus persisté dans `sessionStorage` (mémoire + cookie `HttpOnly`) ; directives CSP durcies (`object-src`, `base-uri`, `form-action`, `frame-ancestors`). *Reste : migration CSP par nonce (exige la conversion des gestionnaires d'événements inline).* ### Ajouté - **#83 Barre d'outils d'édition mobile — ruban style Obsidian Android** — la barre de mise en forme flottante est remplacée par un **ruban horizontal défilable** ancré juste au-dessus du clavier virtuel : fond anthracite aux coins arrondis, insertion/enrobage au curseur ou sur la sélection. Commandes : annuler/refaire, `[[ ]]` (lien interne), fichiers/modèles, tag `#`, pièce jointe, H1–H6, gras, italique, barré (`~~`), surligné (`==`), code en ligne/bloc, citation, lien externe, listes à puces/numérotées, case à cocher, indenter/désindenter, coller, A−/A+. Une icône **⚙** ouvre un panneau de personnalisation (ajouter / retirer / réordonner, persistance `localStorage`). i18n FR/EN. Helpers purs exportés (`formatChange`, `normalizeRibbonCommands`, `moveRibbonCommand`). Tests JSDOM `tests/frontend/mobile-editor.test.mjs` (+13) et E2E mobile. - **#82 Assistant IA — menu `@` instantané** — nouvel endpoint `GET /api/vault/{vault}/paths` (liste plate et plafonnée de l'index des chemins). Le frontend la récupère **une fois par vault** (préchargée à l'ouverture du panneau) puis filtre **côté client** pendant la frappe : l'affichage des fichiers/répertoires est immédiat, au lieu d'une requête réseau par caractère. Tests : `tests/test_api_main.py` (+2), `tests/frontend/ai.test.mjs` (+1). - **#82 Assistant IA — Section « Fournisseur & modèle » compacte** — la barre de sélection du modèle devient **discrète** : la liste complète des types d'endpoints (Chat, Embeddings, Vision…) est remplacée par un **bouton d'information ⓘ** qui affiche les capacités dans une **bulle** au survol, au clic ou par **appui long** (mobile). Le choix du modèle passe par une **liste déroulante avec recherche** (filtrage insensible aux accents) au lieu d'un `