Files
ObsiGate/docs/ISSUES_TODOLIST.md
T

86 KiB
Raw Blame History

IMPORTANT — Lire d'abord Ce document utilise un format tabulaire simple et rigoureux conçu pour être maintenu à la fois par un humain (éditeur texte) et par un agent IA. Les règles exactes sont définies dans la section « Comment fonctionne ce document ». La procédure de livraison obligatoire (tests, docs, commit, push, CI) est définie dans DELIVERY_WORKFLOW.md.

🐛 ObsiGate — Suivi des Bugs / TODO de Correction

Rôle du document : registre unique et vivant des bugs découverts et des corrections à apporter au projet ObsiGate (application Web + desktop Tauri). C'est l'outil de communication commun entre l'utilisateur humain et l'IA qui effectue les corrections dans le code.

  • Projet : ObsiGate — Porte d'entrée web pour vaults Obsidian
  • Stack : Python 3.11+ (backend FastAPI) · JavaScript/Vanilla (frontend) · Tauri/Rust (desktop)
  • Dernière mise à jour : 2026-09-17

⚙️ Comment fonctionne ce document (À LIRE ABSOLUMENT)

Ce document est un contrat de travail partagé. Suivez les règles ci-dessous pour qu'un humain OU un agent IA puisse le lire, le modifier et l'exploiter sans ambiguïté.

1. Une table = un état (workflow par rangée)

Chaque bug/TODO est une rangée dans la section 📋 Registre des bugs / TODOs. Une rangée ne change jamais de table au fil de sa vie ; c'est la colonne « Statut » qui encode son avancement :

Statut (colonne) Signification Qui peut le poser
🔴 ouvert Bug confirmé / tâche à faire, en attente de traitement Utilisateur ou IA
🟠 en cours Un agent IA a commencé à corriger IA uniquement
🟢 corrigé La correction est écrite et vérifiée (tests OK) IA uniquement
✅ vérifié L'utilisateur (ou les tests) a validé le correctif Utilisateur uniquement
⚪ abandonné Décision de ne pas corriger (documentée dans notes) Utilisateur ou IA

2. Cycle de vie d'un bug (workflow)

flowchart LR
    A[🔴 ouvert<br/>Découverte d'un bug] --> B[🟠 en cours<br/>IA travaille dessus]
    B --> C[🟢 corrigé<br/>Correctif écrit + tests OK]
    C --> D[✅ vérifié<br/>Re-validation par l'utilisateur]
    C --> E[⚪ abandonné<br/>Refus / hors périmètre]
    D --> F[✅ Fermé : lignée conservée en historique]
  1. Un bug est signalé → posé en 🔴 ouvert par l'utilisateur ou l'IA (à l'issue d'une investigation ou d'une session de test).
  2. L'IA prend en charge → passe le statut en 🟠 en cours avant de commencer à coder, et crée une entrée dans 🛠️ Journal des interventions AI.
  3. La correction est terminée → l'IA exécute les tests pertinents, passe le statut en 🟢 corrigé et remplit la colonne « Correctif / Commit ».
  4. Validation finale → c'est l'utilisateur qui passe en ✅ vérifié une fois le correctif contrôlé. Les rangées ✅ vérifié sont déplacées dans l'historique périodiquement.
  5. Abandon → justifié obligatoirement dans la colonne « Notes / Scope ».

⚠️ Ne JAMAIS supprimer une rangée terminée : déplacez-la dans la section 📜 Historique des bugs résolus à la place.

3. Ordre de lecture pour un agent IA

Avant de corriger quoi que ce soit, un agent IA doit :

  1. Lire ce fichier en entier (en particulier ce guide et le registre).
  2. Identifier les rangées de statut 🔴 ouvert avec un scope IA et une priorité élevée qui lui sont attribuées (colonne « Assigné »).
  3. Poser le statut 🟠 en cours sur la rangée choisie avant toute modification de code.
  4. Corriger en respectant la checklist de vérification et les conventions du projet.
  5. Revenir mettre le statut à 🟢 corrigé, documenter le correctif, et journaliser son passage.
  6. Ne pas marquer ✅ vérifié lui-même (c'est un droit utilisateur, sauf mention contraire explicite).

4. Identifiants uniques (ID)

  • Chaque bug porte un ID stable BUG-### ou TODO-### (voir le format dans le registre).
  • L'ID ne change jamais, même après résolution. Il permet de retrouver le bug dans l'historique et dans le journal d'interventions.
  • Le format du numéro est ### = 3 chiffres incrémental (001, 002, 003…).
  • Si vous (IA) créez un nouveau bug, reprenez le plus grand numéro existant + 1 dans le registre, quel que soit l'ordre des lignes.

5. Comment mettre à jour ce document (bonnes pratiques)

  • Formatage : garder les colonnes alignées par des espaces pour la lisibilité brute. Une rangée = une seule ligne de tableau Markdown.
  • Copier-coller : pour ajouter un bug, dupliquez une ligne existante puis modifiez les champs.
  • Ne pas casser : conservez le séparateur d'en-tête |---|---|…| et les champs fixes (#, Titre, Statut, Priorité, Scope, Assigné, Zone, Cmd de repro, Correctif/Commit, Notes).
  • Frontière des rôles : un agent IA ne doit jamais se passer à lui-même une rangée en ✅ vérifié ; il s'arrête à 🟢 corrigé (sauf si l'utilisateur l'y autorise explicitement).

📋 Registre des bugs / TODOs

Légende colonnes :

  • Statut : 🔴 ouvert | 🟠 en cours | 🟢 corrigé | ✅ vérifié | ⚪ abandonné
  • Priorité : P0 (critique/bloquant) · P1 (élevée) · P2 (normale) · P3 (basse/cosmétique)
  • Scope : 📱 frontend · ⚙️ backend · 🔌 api · 🧩 tests · 🖥️ desktop · 📦 build · 📄 docs · 🤖 ia · ❓ inconnu (À affiner par l'IA)
  • Assigné : IA (à traiter par un agent) · USER (à traiter par l'utilisateur) · — (non attribué)
  • Zone : chemin/nom de fichier concerné (ex. frontend/app.js, backend/search.py)
  • Cmd de repro : commande ou scénario permettant de reproduire / vérifier (vide si N/C)

Bugs ouverts (à traiter)

# Titre Statut Priorité Scope Assigné Zone (fichier) Cmd de repro Correctif / Commit Notes
BUG-001 L'ouverture des fichier PDF ne fonctionne pas et donne l'erreur Internal Server Error 🟢 corrigé P1 fichier PDF IA backend/main.py GET /api/file/{vault}/pdf/stream?path=…(pdf à nom accentué) backend/main.py : Content-Disposition encodé RFC 5987 (helper _content_disposition) 500 car nom Unicode brut dans l'en-tête → header invalide. Vérifié: stream 200 / Range 206 + test tests/test_pdf_stream.py
BUG-002 l'ouverture d'un fichier .excalidraw ne fonctionne pas et affiche toujours Loading Excalidraw… 🟢 corrigé P1 fichier .excalidraw IA frontend/excalidraw-editor.html Ouvrir un fichier .excalidraw frontend/excalidraw-editor.html : alias esm.sh supprimé (408 jotai) + React 19 cohérent + prop excalidrawAPI 2 causes: 408 esm.sh sur ?alias + prop legacy excalidrawRef inopérante en 0.18. Vérifié navigateur: Loading masqué + cycle save OK
BUG-003 mypy : 33 erreurs de typage (étape CI en mode advisory → bloquante) 🟢 corrigé P2 ⚙️ backend IA backend/{main,indexer,export,pdf_reader,bookslm_routes}.py, backend/auth/router.py, .gitea/workflows/ci.yml mypy backend/ --ignore-missing-imports Annotations de types, gardes None (auth/router), import PROVIDERS manquant (bug latent main.py:4523), PdfReader: Any ; CI mypy rendu bloquant 0 erreur après correction. PROVIDERS non importé → NameError avalé par le except (le modèle par défaut n'était jamais prépendé). Tests : 728 passed
BUG-004 Lien cassé vers CONTRIBUTING.md dans README.md 🟢 corrigé P3 📄 docs IA README.md Cliquer le lien « Contributing » README.md : lien → docs/CONTRIBUTING.md (+ DELIVERY_WORKFLOW.md) ; arbre du projet corrigé README.fr.md pointait déjà correctement vers ./docs/CONTRIBUTING.md
BUG-005 Sur mobile via Cloudflare (og.dracodev.net), le site ne charge pas complètement même après purge des caches (téléphone + Cloudflare) 🟢 corrigé P1 📱 frontend + ⚙️ backend IA frontend/sw.js, frontend/index.html, frontend/js/sync.js, backend/main.py Ouvrir og.dracodev.net sur mobile (PWA installée), purger les caches, recharger SW network-first pour le code + caches versionnés (SW_VERSION) ; précache corrigé (/static/js/…) ; migration ponctuelle localStorage ; Cache-Control: no-cache (le middleware renvoyait immutable 1 an sur /static) ; tests tests/frontend/sw.test.mjs + TestStaticCaching Cause : SW cache-first à nom fixe + en-têtes immutable non fingerprinted → ancien build servi indéfiniment (Cache Storage ≠ cache navigateur/CF). Vérifié : 754 tests backend, frontend OK
BUG-006 Clé API DeepSeek réelle commitée en clair dans .env.example 🟢 corrigé P0 📄 docs + 🔐 sécurité IA .env.example git grep "sk-" .env.example .env.example : clé remplacée par un placeholder (sk-xxx…) + modèle deepseek-chat ⚠️ Rotation de la clé requise : elle reste dans l'historique Git → révoquer/régénérer côté DeepSeek. Le fichier .env (réel) est bien gitignoré
BUG-007 Assistant IA : menus / et @ — navigation clavier ↑/↓ inopérante et filtrage des skills cassé par les accents 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js Taper /résumé ou @café, puis ↑/↓ bookslm.js : motifs Unicode \p{L} (détection + sélection), recherche normalisée NFD (insensible aux accents), jeton _menuSeq (rendus async obsolètes), scrollIntoView de l'élément actif Le menu se fermait dès la saisie d'un accent ; la frappe rapide pouvait écraser le menu avec un résultat obsolète. Tests : tests/frontend/ai.test.mjs (+3)
BUG-008 Assistant IA : commande @ — chemins accentués + contexte ad-hoc ignoré en mode Général 🟢 corrigé P1 📱 frontend + ⚙️ backend IA frontend/js/bookslm.js, backend/bookslm_routes.py @fichier-accentué puis envoyer en mode Général Frontend : détection Unicode + repli vault=all sans vault courant. Backend : _resolve_system_prompt résout le vault optionnel dès qu'un contexte @ est présent (mode Général) Le backend ignorait extra_files/extra_directories en mode Général (vault_path is None). Tests : tests/test_bookslm.py (+2) et tests/frontend/ai.test.mjs
BUG-009 Assistant IA : liste de modèles corrompue (art ASCII) et clés i18n brutes (ai.model_search) sous OpenRouter 🟢 corrigé P1 📱 frontend IA frontend/js/ai.js, frontend/js/i18n.js, frontend/sw.js, frontend/style.css Ouvrir le menu modèle sous « openrouter » Locale non content-hashée en cache HTTP → cache:'no-store' + bump SW_VERSION v4 ; styles critiques du picker appliqués en ligne (popover absolu, liste en colonne, options display:block) ; rendu plafonné à 200 modèles + indicateur « … N autres » OpenRouter expose plusieurs centaines de modèles ; si style.css est en cache, la liste s'affichait en bloc inline (art ASCII) et les clés i18n brutes provenaient d'un fr.json obsolète. Tests : tests/frontend/ai.test.mjs (+1)
BUG-010 Assistant IA : la commande @ n'ajoute pas le contexte à la requête en mode Général 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js Assistant en mode Général, taper @fichier, sélectionner, envoyer Capture du vault renvoyé par /api/tree-search dans la sélection @ ; _contextVault() propage ce vault aux requêtes /context et /chat ; recherches @ suivantes limitées à ce vault En mode Général, _vault est nul : la requête partait sans vault et le backend ignorait extra_files. Tests : tests/frontend/ai.test.mjs (+1)
BUG-011 Assistant IA : noms de modèles illisibles dans la liste déroulante 🟢 corrigé P2 📱 frontend IA frontend/js/ai.js, frontend/style.css Ouvrir le menu modèle dans la barre latérale de l'assistant Popover aligné à droite du déclencheur (right:0), largeur 340 px bornée à 100vw - 24px, noms sur plusieurs lignes (overflow-wrap:anywhere, police 0,78 rem) + title La liste s'ouvrait vers la droite et dépassait le bord de la sidebar ancrée à droite ; noms tronqués. Tests : tests/frontend/ai.test.mjs
BUG-012 Assistant IA : la commande @ n'affiche aucun menu de sélection en mode Général 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js Assistant en mode Général (aucun vault), taper @ _showMentionMenu rend toujours le menu (même vide) et _activeVault() retombe sur state.selectedContextVault puis le premier state.allVaults, avant vault=all pour les requêtes Sans vault résolu, l'ancien code masquait le menu et le backend ignorait le contexte. Tests : tests/frontend/ai.test.mjs (+1)
BUG-013 Assistant IA : les liens fichiers/répertoires des réponses affichent « Aucun vault actif pour ouvrir ce lien » 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js En mode Général, cliquer un lien de fichier/dossier dans une réponse _openFileLink() et _revealPath() utilisent _activeVault() (vault de contexte → vault sélectionné → premier vault) au lieu de _resolveVault() seul _resolveVault() est nul sans document ouvert ; les liens échouaient en mode Général. Tests : tests/frontend/ai.test.mjs (+1)
BUG-014 Assistant IA : les flèches ↑/↓ ne naviguent pas dans les menus / et @ 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js Ouvrir /, puis ↑/↓ (y compris après un clic hors zone de texte) Navigation gérée au niveau du panneau en phase de capture (panel.addEventListener('keydown', ..., true)), donc indépendante du focus Le keydown n'était écouté que sur la zone de texte ; dès que le focus changeait, les flèches étaient ignorées. Tests : tests/frontend/ai.test.mjs (+1)
BUG-015 Assistant IA : la ligne sélectionnée des menus / et @ est invisible au clavier 🟢 corrigé P2 📱 frontend IA frontend/style.css Ouvrir / ou @, naviguer avec ↑/↓ État .active en --bg-hover + barre d'accent à gauche (inset 3px 0 0 var(--accent)) au lieu de --surface2 --surface2 est identique à --bg-primary (fond du menu) en thème sombre : la sélection ne se voyait pas. Idem pour la liste de modèles
BUG-016 Mobile : le bouton « mode lecture » flottant recouvre le bouton d'envoi de l'assistant IA 🟢 corrigé P2 📱 frontend IA frontend/js/mobile-editor.js, frontend/style.css Mobile, sans fichier ouvert : ouvrir l'assistant puis constater que 📖 masque ✈️ updateReadingButtonVisibility() n'affiche #me-reading-btn que si state.currentPath est défini et que le panneau assistant est fermé ; resynchronisé à chaque mutation de #content-area et sur bookslm:opened/bookslm:closed ; règle .me-reading-btn[hidden]{display:none} Le bouton flottant (z-index 890) passait au-dessus du panneau plein écran mobile (z-index 100). Tests : tests/frontend/mobile-editor.test.mjs (+2)
BUG-017 Accueil mobile : les tuiles de « Favoris » et « Récents » ne s'affichent pas correctement en largeur (l'onglet « Partagés » s'affiche correctement) 🟢 corrigé P2 📱 frontend IA frontend/style.css Mobile : page d'accueil → onglets Favoris / Récents frontend/style.css : min-width:0 + overflow:hidden sur .dashboard-card (et header/footer) → la grille 1 colonne ne déborde plus Cause : titre/chemin nowrap forçaient la largeur mini de la carte
BUG-018 Éditeur mobile : mise en page inadaptée — boutons « Annuler » / « Sauvegarder » inaccessibles et barre d'outils IA non optimisée 🟢 corrigé P1 📱 frontend IA frontend/style.css Mobile : ouvrir un fichier → barre d'outils de l'éditeur puis panneau assistant frontend/style.css : en-tête mobile compact (marque/séparateur/espaceur/présence masqués, champs compressibles, libellé « Saved » masqué) + cibles tactiles .ai-toolbar-btn agrandies Les 3 boutons d'action restent accessibles ; voir aussi #83 pour le ruban
BUG-019 Éditeur mobile : la barre d'outils du bas n'est pas toujours visible au-dessus du clavier quand le curseur n'est pas en bas de l'éditeur 🟢 corrigé P1 📱 frontend IA frontend/js/mobile-editor.js, frontend/style.css Mobile : ouvrir un fichier, placer le curseur au milieu du document, afficher le clavier initKeyboardAnchor() utilise window.visualViewport (--kb-offset) pour ancrer le ruban au-dessus du clavier ; padding-bottom sur .cm-scroller Fonctionne quel que soit le défilement / la position du curseur
BUG-020 Éditeur mobile : deux barres de défilement verticales à droite de la page en mode édition 🟢 corrigé P3 📱 frontend IA frontend/style.css Mobile : ouvrir l'éditeur et observer le bord droit frontend/style.css : .editor-body en overflow:hidden + colonne flex ; seul .cm-scroller défile Une seule barre de défilement
BUG-021 [🔴 CRITIQUE] XSS stocké via le rendu markdown (escape=False) 🟢 corrigé P0 🔐 sécurité IA backend/services/sanitizer.py, backend/main.py Ouvrir une note contenant <img src=x onerror=alert(1)> backend/services/sanitizer.py (whitelist stdlib) appliqué après _add_heading_ids dans _render_markdown ; tests tests/test_security_hardening.py HTML brut d'un vault injecté dans le DOM → JS dans le navigateur de chaque utilisateur. DOMPurify client non ajouté (défense serveur suffisante)
BUG-022 [🔴 CRITIQUE] XSS stocké sur la page publique /s/{token} (title + frontmatter non échappés) 🟢 corrigé P0 🔐 sécurité IA backend/main.py Partager une note dont le frontmatter contient un title avec <script> backend/main.py : html.escape() sur title/frontmatter + JSON échappé (\u003c) pour le bloc <script> et a.download a.download="{title}.md" se trouvait dans un bloc script → breakout JS. Test d'intégration dans tests/test_security_hardening.py
BUG-023 [🔴 CRITIQUE] Brute-force MFA sans rate-limit ni verrouillage 🟢 corrigé P0 🔐 sécurité IA backend/auth/router.py POST /api/auth/mfa/totp/verify en boucle avec des codes aléatoires _enforce_mfa_rate_limit + _record_mfa_failure sur totp/verify, recovery, webauthn/verify (IP + compte + lockout) TOTP 6 chiffres brute-forceable ; le login était protégé, pas le second facteur
BUG-024 [🔴 CRITIQUE] Traversal inter-vaults : startswith() sans séparateur dans resolve_safe_path 🟢 corrigé P0 🔐 sécurité IA backend/services/paths.py Avec les vaults vault et vault-evil, lire un fichier de vault-evil via vault _is_within() par segments (relative_to + repli casse-insensible) ; tests tests/test_security_hardening.py::TestPathIsolation Rupture d'isolation entre vaults (lecture / écriture / suppression)
BUG-025 [🟡 IMPORTANT] ReDoS : regex utilisateur appliquée au contenu en masse 🟢 corrigé P1 🔐 sécurité IA backend/services/regex_safety.py, backend/search.py, backend/services/mutations.py Recherche avancée avec ^(a+)+$ sur un gros document Validation de pattern (longueur ≤500, rejet quantificateurs imbriqués/backrefs), contenu tronqué (200k) et matchs plafonnés ; 400 sur pattern refusé Le dry-run replace_in_files déclenchait la charge ; la validation est faite avant compilation
BUG-026 [🟡 IMPORTANT] Webhooks : SSRF (URL non validée) + secret en clair 🟢 corrigé P1 🔐 sécurité IA backend/webhooks.py, data/webhook_secrets.json Configurer une URL vers http://169.254.169.254 puis déclencher le webhook Validation d'URL (HTTPS par défaut, IP privées/boucle bloquées), résolution DNS au dispatch, redirections non suivies ; secret dans webhook_secrets.json (0600) ou env OBSIGATE_WEBHOOK_SECRET_<ID> get_webhooks() n'expose plus le secret (has_secret) ; options OBSIGATE_WEBHOOK_ALLOW_HTTP/_ALLOW_PRIVATE
BUG-027 [🟡 IMPORTANT] Sessions : refresh non rotatif, access token non révoqué au logout 🟢 corrigé P1 🔐 sécurité IA backend/auth/jwt_handler.py, backend/auth/router.py, backend/auth/middleware.py Logout puis réutilisation de l'ancien access token Rotation du refresh token (create_refresh_token(remember=…)) + révocation de l'ancien JTI ; access token révoqué au logout et vérifié dans le middleware JTI persistés dans data/revoked_tokens.json
BUG-028 [🟡 IMPORTANT] Politique de mot de passe incohérente + sessions non invalidées au changement 🟢 corrigé P1 🔐 sécurité IA backend/auth/password.py, backend/auth/router.py, backend/auth/user_store.py, backend/auth/middleware.py Créer un utilisateur avec password:"" (admin) ; changer son mot de passe puis réutiliser l'ancien token validate_password_strength (8–128) sur création/modif admin/changement ; password_changed_at invalide les jetons émis avant le changement ; change-password réémet une paire Tests TestPasswordPolicy + TestTokenInvalidation
BUG-029 [🟡 IMPORTANT] Race read-modify-write sur users.json (perte de mises à jour) 🟢 corrigé P1 ⚙️ backend IA backend/auth/user_store.py Activer MFA et changer son mot de passe en parallèle threading.RLock (_users_lock) autour de create_user, update_user, delete_user, record_login_failure RLock réentrant car record_login_failure appelle update_user
BUG-030 [🟡 IMPORTANT] Adresse IP jamais consignée dans les audits (_request_ip toujours « unknown ») 🟢 corrigé P1 ⚙️ backend IA backend/services/net.py, backend/auth/middleware.py Lire le journal d'audit après un save/delete get_client_ip() (request.client.host, X-Forwarded-For si OBSIGATE_TRUST_PROXY=true) injecté dans current_user["_request_ip"] Test TestClientIp
BUG-031 [🟡 IMPORTANT] Rate-limiter en mémoire, mono-process, budget exclusivement IP 🟢 corrigé P1 ⚙️ backend IA backend/ratelimit.py, backend/auth/router.py Lancer plusieurs workers et observer le partage du compteur Budget par compte (is_account_rate_limited/record_account_*) en plus de l'IP, intégré login + MFA ; limite mono-process documentée Rotation d'IP neutralisée ; stockage partagé (Redis) hors périmètre
BUG-032 [🟡 IMPORTANT] Indexation : symlinks suivis (contenu hors vault indexé) + scan initial coûteux 🟢 corrigé P1 ⚙️ backend IA backend/indexer.py Placer un symlink dans le vault vers un dossier externe puis relancer l'index _scan_vault réécrit avec os.walk(followlinks=False) + refus des symlinks sortant de la racine ; test TestSymlinkIndexing Scan incrémental/index persistant : voir #86 (phase 3)
BUG-033 [🟡 IMPORTANT] Recherche classique et tool IA search_fulltext en O(N) sans inverted index 🟢 corrigé P1 ⚙️ backend IA backend/search.py, backend/tools/service.py GET /api/search sur un vault de 50 000 fichiers search() récupère les candidats via l'inverted index (intersection des termes + expansion de préfixes), repli sur le scan pendant la construction search_fulltext en bénéficie automatiquement
BUG-034 [🟡 IMPORTANT] CSP affaiblie ('unsafe-inline' + CDN distants) et token d'accès en sessionStorage 🟢 corrigé P1 🔐 sécurité IA backend/main.py, frontend/js/auth.js, frontend/js/admin.js, frontend/js/sync.js Inspecter les en-têtes CSP ; lire sessionStorage en console Token en mémoire + cookie HttpOnly (plus de sessionStorage) ; CSP durcie (object-src 'none', base-uri, form-action, frame-ancestors). Reste : migration nonce 'unsafe-inline' conservé tant que les gestionnaires inline n'ont pas été convertis (résidu documenté)
BUG-035 [🔵 MINEUR] secret_redactor : faux positifs sur les hashs hex (git, SHA) 🔴 ouvert P2 ⚙️ backend IA backend/secret_redactor.py Lire une note contenant un commit git (40 caractères hexadécimaux) Restreindre le périmètre de détection (contexte clé/token) + whitelist Contenus mutilés dans les lectures et réponses IA
BUG-036 [🔵 MINEUR] Collab WebSocket : token en query string 🔴 ouvert P2 ⚙️ backend IA backend/collab.py Observer l'URL du websocket dans le trafic réseau Passer le token en header / étape d'authentification initiale ; borner la taille des messages Jeton visible dans les logs/proxys
BUG-037 [🔵 MINEUR] Compte « anonymous » administrateur si auth désactivée 🔴 ouvert P2 🔐 sécurité IA backend/auth/middleware.py Démarrer avec l'authentification désactivée Avertissement explicite au démarrage + refus de déploiement public sans auth Comportement par conception mais risqué si mal configuré
BUG-038 [🔵 MINEUR] Argon2 à 64 MB par vérification : risque d'épuisement mémoire 🔴 ouvert P2 🔐 sécurité IA backend/auth/password.py:8 Lancer de nombreux POST /api/auth/login simultanés Recalibrer (~19 MB, t=2, p=1, norme OWASP actuelle) + maintien du rate-limit DoS mémoire possible sur les petites instances
BUG-039 [🔵 MINEUR] Enumération de comptes : 429 (verrouillé) vs 401 (inconnu) 🔴 ouvert P3 🔐 sécurité IA backend/auth/router.py:120 Tenter un login sur un compte verrouillé puis un nom inconnu Répondre 401 uniforme avec un timing équivalent Le statut HTTP distingue l'existence d'un compte
BUG-040 [🔵 MINEUR] Extraction PDF intégrale (100 ko) au scan de démarrage 🔴 ouvert P2 ⚙️ backend IA backend/indexer.py:465 Démarrer sur un vault contenant de nombreux PDF Analyser les PDF en tâche de fond / à la demande (lazy) Ralentit fortement le démarrage et le rebuild d'index
BUG-041 [🟡 IMPORTANT] Assistant IA : échec sur un répertoire vide (« Aucun fichier markdown trouvé dans ce dossier ») au lieu de répondre 🟢 corrigé P1 📱 frontend + ⚙️ backend IA backend/bookslm_routes.py, backend/bookslm.py, frontend/js/bookslm.js Ouvrir l'assistant sur un dossier vide puis envoyer une question _resolve_system_prompt dégrade vers le prompt Général + bloc « Dossier vide » (plus de 404) ; contexte applicatif app_context enrichi (documents ouverts, répertoire, recherche, fichiers récents) Le 404 bloquait toute la requête. Feature #88, fiche docs/features/ai-app-context.md. Tests : tests/test_bookslm.py (+3), tests/frontend/ai.test.mjs
BUG-042 [🟡 IMPORTANT] Assistant IA : liens de fichiers non fiables (« File not found: ») — pas de règle déterministe nom / dossier / chemin 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js Cliquer les liens de fichiers/dossiers dans une réponse de l'assistant (noms avec espaces et/ou accents, chemin préfixé par le nom du vault) _classifyPath distingue name (copie presse-papiers) / dir (révélation arborescence) / file (ouverture) ; _activatePath() résout le chemin contre l'index du vault (exact → suffixe → basename unique) avant d'agir ; espaces + accents pris en charge (classes Unicode \p{L}\p{N}\p{M}, comparaison normalisée NFC, markdown <…>/%20, code inline, mentions brutes confirmées par l'index) ; _splitVaultPrefix retire un préfixe Vault/… et ouvre dans ce vault (_fetchPathsForVault) Les liens morts ouvraient un fichier inexistant. Feature #88. Tests : tests/frontend/ai.test.mjs (+11)
BUG-043 [🟡 IMPORTANT] Assistant IA : la liste des fournisseurs de la barre latérale ne suit pas les ajouts/retraits de clés API dans la configuration du projet 🟢 corrigé P1 📱 frontend IA frontend/js/ai.js, frontend/js/bookslm.js, frontend/js/config.js Ajouter (ou supprimer) une clé de fournisseur AI dans la configuration puis observer le menu Fournisseur de l'assistant sans recharger la page Le picker lit /api/ai/status une seule fois, à sa construction, et le panneau de l'assistant est un singleton monté pour toute la session → liste figée. Nouveau refreshAIPickers() (exporté par ai.js) qui reconstruit chaque picker monté dans son emplacement .ai-picker-slot (conservé même sans fournisseur configuré, donc un premier fournisseur s'y monte aussi) ; appelé après saveAIKeys() et deleteAIKey() (config.js) ; une sélection dont le fournisseur n'est plus configuré est purgée de obsigate_ai_picker (retour au défaut + modèle effacé au lieu d'un nom fantôme) Il fallait recharger la page pour voir un nouveau fournisseur (ou en voir disparaître un). Feature #82. Tests : tests/frontend/ai.test.mjs (+4)
BUG-044 [🟡 IMPORTANT] Capacités des modèles IA erronées : aucun modèle Mistral n'est détecté « Vision capable » (et des modèles texte sont annoncés « Embeddings ») 🟢 corrigé P1 ⚙️ backend + 🔌 api IA backend/model_capabilities.py, backend/provider_capabilities.py, backend/main.py, backend/ai_routes.py curl -H "Authorization: Bearer $MISTRAL_API_KEY" https://api.mistral.ai/v1/models puis GET /api/ai/model-capabilities?provider=mistral&model=mistral-small-latest Nouveau calque déclaratif (backend/provider_capabilities.py) : les capacités publiées par le fournisseur (Mistral capabilities, OpenRouter architecture) sont lues et mises en cache par GET /api/config/ai-models ; elles priment sur la table statique, qui ne comble plus que les drapeaux non déclarés (et sert de repli hors ligne / sans clé). Table statique corrigée : familles vision Mistral (ministral, magistral, mistral-small, mistral-medium, mistral-vibe-cli, labs-leanstral), mistral-ocr = vision sans chat, défaut fournisseur Mistral sans embeddings. Tests : tests/test_provider_capabilities.py, tests/test_model_capabilities.py::TestMistralFamilies, tests/test_ai_models.py::TestDeclaredCapabilities Le panneau de modèle par défaut et la bulle ⓘ n'affichaient aucun Mistral vision alors que l'API en déclare 28 ; seul le motif pixtral (retiré de l'API) matchait. Effet secondaire corrigé : mistral-large-latest / codestral-latest étaient annoncés « Embeddings » (défaut fournisseur). La porte vision de l'assistant (bookslm_routes.py) accepte désormais les images avec un modèle Mistral vision
BUG-045 [🟡 IMPORTANT] Éditeur « Editer » : deux barres de défilement superposées sur les documents longs 🟢 corrigé P1 📱 frontend Éditeur frontend/style.css Ouvrir un fichier long (ex. IT/Docker Guide.md), cliquer Editer, mesurer #editor-body et .cm-scroller .editor-body-cm gardait overflow:auto et un .cm-editor{height:100%} sous la rangée barre d'outils IA → le corps (toolbar+éditeur) ET le scroller CodeMirror débordaient simultanément. L'override global legacy .cm-scroller{min-height:100%;overflow-y:auto!important} aggravait. Passé en flex column : corps overflow:hidden, toolbar flex:0 0 auto, .cm-editor flex:1 1 auto; height:auto, seul le scroller défile ; override legacy retiré. Test : tests/frontend/editor-inline.test.mjs (+1) ; vérifié Playwright sur l'instance de test (un seul conteneur scrollable).
BUG-046 [🔴 BLOQUANT] Assistant IA : « Échec de l'action : [object Object] » à l'application d'un ajout de texte au document courant 🟢 corrigé P0 📱 frontend + ⚙️ backend IA frontend/js/bookslm.js, backend/bookslm.py, backend/bookslm_routes.py Mode agent : demander d'ajouter du texte au document ouvert puis cliquer « Appliquer » Trois causes : (1) continuation de confirmation avec payload:null → second « Appliquer » sans message → 422 ; (2) new Error(detail) sur un detail tableau d'objets FastAPI → « [object Object] » ; (3) prompt documents/directory sans nom de vault → le modèle inventait "vault":"test" → échec silencieux de l'outil. Nouveau _responseError() (aplatit tableau/objet), payload porté à la continuation, bloc « Ces documents appartiennent au vault « X » » + consigne outils d'écriture (build_system_prompt(vault_name=...)). SW_VERSION v20. Tests : tests/test_bookslm.py::test_vault_name_guidance, tests/frontend/ai.test.mjs (+2).
BUG-047 [🔴 BLOQUANT] La version affichée par l'application ne suit pas les livraisons : 66 commits livrés depuis v2.2.1 et l'UI/API restent bloquées sur 2.2.1 (et les numéros codés en dur divergent : package.json 1.0.0, desktop Tauri 2.0.0, Dockerfile 2.2.1, README 1.7.0) 🟢 corrigé P0 ⚙️ build + 📄 docs IA VERSION (nouveau), scripts/bump_version.py (nouveau), .githooks/prepare-commit-msg + .githooks/post-commit (nouveaux), scripts/install-hooks.sh (nouveau), backend/version.py, Dockerfile, docker-compose.yml, build.sh, .gitea/workflows/ci.yml, desktop/build.rs, tests/test_version.py (nouveau) git tag -l | tail -1 puis python scripts/bump_version.py --print-version ; curl -s http://localhost:2020/api/health | jq .version Le numéro provenait du dernier tag git et aucun tag n'était créé aux livraisons (bump_version.sh jamais appelé) → version figée, plus quatre numéros codés en dur ailleurs. Corrigé : VERSION (racine) = source unique de vérité, incrémentée automatiquement à chaque commit par le hook versionné prepare-commit-msg (SemVer : !:/BREAKING CHANGE → MAJEUR, feat → MINEUR, sinon CORRECTIF), tag vX.Y.Z créé par post-commit et publié au push (push.followTags) ; bump_version.py resynchronise package.json, desktop Tauri, ROADMAP, READMEs et fait la rotation du CHANGELOG dans le même commit ; backend, image Docker (COPY VERSION) et desktop lisent ce fichier. Contournement ponctuel : SKIP_VERSION_BUMP=1. Garde-fou : tests/test_version.py::TestRepoVersionAlignment échoue dès qu'un dérivé diverge de VERSION. Vérifié : pytest complet vert, ruff/mypy 0, /api/health → 2.3.0 sur l'instance de test.
BUG-048 [🟡 IMPORTANT] Assistant IA : les entrées « Contextes » et « Skills » du menu « + » n'ouvraient pas leur menu (@ / /) 🟢 corrigé P2 📱 frontend IA frontend/js/bookslm.js Menu « + » de l'assistant → cliquer « Contextes » ou « Skills » e.stopPropagation() sur les entrées du panneau .bookslm-ext-menu (le clic remontait au gestionnaire du panneau qui annulait le rendu asynchrone) Journal 2026-09-16. Tests : tests/frontend/ai.test.mjs (+3)
BUG-049 [🔵 MINEUR] Assistant IA : icône du bouton « + » invisible (largeur SVG nulle) 🟢 corrigé P3 📱 frontend IA frontend/style.css Ouvrir l'assistant et observer le bouton « + » Sélecteur porté à .bookslm-input-area button.bookslm-btn-plus (la règle générique padding: 8px 16px sur un bouton 32 px annulait la largeur de contenu) Vérifié navigateur : SVG 0 px → 18 px. Journal 2026-09-16
BUG-050 [🟡 IMPORTANT] Assistant IA : échec de la création d'un sous-dossier contenant un fichier (appels d'outils parallèles + confirmation) 🟢 corrigé P1 ⚙️ backend + 🤖 ia IA backend/agent/loop.py, backend/services/mutations.py, backend/tools/service.py, backend/bookslm.py Mode Agent : « crée le dossier X et un fichier Y dedans » puis Appliquer backend/agent/loop.py : résultats « deferred » (_deferred_tool_message) pour les tool_calls non atteints lors d'une pause de confirmation ; backend/services/mutations.py : create_directory(..., exist_ok=True) ; backend/tools/service.py + backend/bookslm.py : consignes create_file (parents auto-créés, chemin imbriqué unique) Cause : le message assistant listait plusieurs tool_calls mais la pause n'ajoutait le résultat que du seul appel confirmé → conversation invalide (tool_call_id sans réponse) au resume. Tests : tests/test_agent_loop.py (+1), tests/test_tools_mutations.py (+1), tests/test_api_main.py (+1)
BUG-051 [🟡 IMPORTANT] Assistant IA : la recherche web répond toujours « je ne peux pas accéder à internet » (mode agent ou non) 🟢 corrigé P1 ⚙️ backend + 🤖 ia IA backend/tools/web.py, tests/test_web_tools.py Assistant : « fais une recherche sur l'horaire du Canadien de Montréal 2026-2027 » backend/tools/web.py : chaîne de repli sans clé — SearXNG puis DuckDuckGo (HTML sans JS) puis Bing (HTML), premier fournisseur non vide retenu (provider), replis désactivables via OBSIGATE_WEB_FALLBACK=0 Cause : l'instance SearXNG par défaut (search.dracodev.net) remonte 0 résultat (moteurs amont suspendus/CAPTCHA) → le modèle en déduisait une absence d'accès réseau. Tests : tests/test_web_tools.py (+4)
BUG-052 [🟡 IMPORTANT] Assistant IA : recherche web sans réponse finale (10 étapes + sources affichées, aucun texte dans la conversation) 🟢 corrigé P1 ⚙️ backend + 🤖 ia IA backend/agent/loop.py, tests/test_agent_loop.py Assistant (mode agent) : recherche web qui enchaîne 10 étapes puis n'affiche aucune réponse backend/agent/loop.py : _finalize_answer — dernier appel LLM sans outil (instruction de synthèse) quand le budget d'itérations/quota est épuisé, repli déterministe _fallback_summary (liste des sources), résultats deferred pour les appels non atteints du lot en quota Cause : content="" renvoyé sur STOP_MAX_ITERATIONS/STOP_QUOTA_EXCEEDED alors que le modèle appelait encore des outils. Tests : tests/test_agent_loop.py (+2)
BUG-053 [🟡 IMPORTANT] Assistant IA (mode agent) : le fichier demandé n'est pas créé — le modèle émet un bloc texte obsigate-action au lieu d'appeler l'outil create_file 🟢 corrigé P1 ⚙️ backend + 🤖 ia IA backend/bookslm.py, backend/bookslm_routes.py, tests/test_bookslm.py Mode agent, contexte Général (ou dossier vide) : « créer le fichier TestVault/sport/… avec le tableau des 84 matchs » → réponse avec un bloc obsigate-action tronqué, aucun fichier backend/bookslm.py : protocole d'action scindé — GENERAL_ACTION_TOOL_PROTOCOL (outils natifs, interdiction des blocs obsigate-action) utilisé quand agent=True, protocole texte conservé pour le chat classique ; backend/bookslm_routes.py : _resolve_system_prompt(..., agent=True) depuis l'endpoint agent + règle « Mode agent » pour les prompts dossier/documents, max_tokens agent 4096 → 8192 (contenu de fichier complet) Cause : le prompt Général enseignait encore le protocole texte alors que l'agent dispose du function calling. Tests : tests/test_bookslm.py (+3)
BUG-054 [🟡 IMPORTANT] Éditeur « Editer » : le bouton Sauvegarder reste bloqué sur le spinner de chargement (retour au crochet uniquement après un refresh complet) 🟢 corrigé P1 📱 frontend IA frontend/js/utils.js Ouvrir un fichier → Editer → cliquer Sauvegarder (ou Ctrl+S) ; rouvrir l'éditeur : le bouton reste un spinner désactivé Nouveau helper resetSaveButton() (crochet &#10003; + disabled=false + styles en ligne nettoyés) appelé à l'ouverture (openEditor), à la fermeture (closeEditor) et en cas d'échec (saveFile). Tests : tests/frontend/editor-inline.test.mjs (+4) Le nœud #editor-save est partagé entre sessions : l'état « spinner + désactivé » posé par une sauvegarde manuelle n'était jamais remis à zéro (succès → fermeture puis réouverture, Forge, ou échec réseau dans le catch). Seul un rechargement de index.html restaurait le crochet
BUG-055 [🟡 IMPORTANT] Éditeur Forge : l'autocomplétion (Tab) ajoute des espaces parasites, l'effacement détruit le mot complété et la complétion fantôme est illisible 🟢 corrigé P1 📱 frontend + ⚙️ backend IA frontend/editor-poc.html, frontend/js/autocomplete.js, backend/ai.py, .gitea/workflows/ci.yml, tests/frontend/forge-completion.test.mjs (nouveau) Forge : taper un mot, puis Tab pour compléter ; un espace (voire deux) s'insère avant le mot complété, et le retour arrière efface l'ajout. La prédiction IA s'affichait décalée (texte miroir du document entier) Cause : trois gestionnaires keydown Tab indépendants s'exécutaient tous — l'indentation (insertAtCursor(' ')) s'ajoutait à la complétion de mot et à l'acceptation du ghost. Correctif : gestion unifiée de Tab (liste ouverte > ghost > mot du document > indentation, une seule action), helpers purs partagés (getWordFragment, findWordCompletions, normalizeGhost, chooseTabAction) dans autocomplete.js, liste déroulante si plusieurs candidats, dropdown positionné au curseur, ghost positionné au curseur (fini le miroir du document, nettoyé au déplacement/scroll), complétion de mot sans espace garanti (normalizeGhost tronque au premier espace) et prompt /api/ai/inline-complete simplifié. Tests : tests/frontend/forge-completion.test.mjs (28). Cause du bug : l'indentation Tab n'était pas conditionnée à l'absence de suggestion. Le ghost re-rendait tout le texte transparent + prédiction, d'où l'impression d'espaces et les erreurs d'effacement
BUG-056 [🟡 IMPORTANT] Éditeur Forge en plein écran : l'Assistant IA s'ouvre en arrière-plan et reste invisible 🟢 corrigé P1 📱 frontend IA frontend/editor-poc.html, frontend/js/sync.js, tests/frontend/forge-completion.test.mjs, tests/frontend/editor-inline.test.mjs Forge : passer en plein écran puis cliquer le bouton « Assistant IA » (ou Ctrl+J) — le panneau s'ouvre dans le document parent, masqué par l'iframe plein écran Sortie du plein écran avant d'ouvrir le panneau, des deux côtés : côté iframe (openAssistant → document.exitFullscreen() puis postMessage à la résolution) et côté parent (sync.js sur forge-open-ai → document.exitFullscreen() puis openForCurrentContext()), car le plein écran peut être détenu par le document parent et non par l'iframe (dans ce cas document.fullscreenElement est nul dans l'iframe et sa sortie échoue). Tests : forge-completion.test.mjs (+1), editor-inline.test.mjs (+1) Le panneau assistant est monté dans document.body du parent : l'API Fullscreen ne rend que l'élément plein écran et ses descendants, donc il ne peut pas s'afficher au-dessus de l'iframe Forge en plein écran. La sortie côté iframe seule ne suffisait pas quand le parent détient le plein écran
BUG-057 [🟡 IMPORTANT] Assistant IA : le bouton « Ajouter » est inopérant dans l'éditeur Forge (fonctionne seulement dans « Editer ») 🟢 corrigé P1 📱 frontend IA frontend/js/bookslm.js, frontend/editor-poc.html Ouvrir un document dans Forge, demander une réponse à l'assistant puis cliquer « Ajouter » _insertIntoEditor() cible Forge (#forge-iframe) : postMessage({ type: 'parent-insert', text }) ; editor-poc.html insère au curseur (insertAtCursor) et marque le tampon modifié. Repli textarea inclus. Tests : tests/frontend/ai.test.mjs (+3), tests/frontend/editor-inline.test.mjs (+1) state.editorView (CodeMirror) est nul en Forge : le clic affichait « Aucun document ouvert dans l'éditeur »
BUG-058 [🔵 MINEUR] Éditeur « Editer » : la barre de numérotation de ligne ne suit pas la couleur du thème (gutter clair #f5f5f5 en thème sombre) 🟢 corrigé P2 📱 frontend IA frontend/style.css Ouvrir un document → Editer en thème sombre : la colonne des numéros de ligne reste gris clair alors que le fond de l'éditeur est sombre Thème du gutter CodeMirror via les variables CSS (color-mix(var(--text-primary) …) pour le fond, --text-secondary pour les numéros, --border pour la séparation, --text-primary pour la ligne active) au lieu des valeurs codées en dur de CodeMirror ; test de non-régression dans tests/frontend/editor-inline.test.mjs. Vérifié Playwright (instance de test) : sombre color(srgb 0.90 0.93 0.95 / 0.05) + bordure #21262d, clair color(srgb 0.12 0.14 0.16 / 0.05) + bordure #d0d7de CodeMirror applique background:#f5f5f5 par défaut, indépendamment du thème ObsiGate ; en mode sombre le fond de l'éditeur suit --bg-secondary mais pas le gutter

TODOs techniques (améliorations / nouvelles tâches)

# Titre Statut Priorité Scope Assigné Zone (fichier) Cmd de repro Correctif / Commit Notes
(exemple) TODO-002 Rendre l'index inversé incrémental (40k+ fichiers) 🔴 ouvert P1 ⚙️ backend IA backend/indexer.py, backend/search.py Recherche sur très gros vault — Exemple à remplacer. Cf. plan.md
(À remplir)

🛠️ Journal des interventions AI

Chaque passage d'un agent IA qui modifie du code ou l'état du registre est journalisé. Règle : l'IA ajoute une ligne à la fin de ce tableau à chaque session de correction. Colonnes : Date (YYYY-MM-DD) · ID(s) traité(s) · Action · Fichiers modifiés · Résumé · Statut après.

Date ID(s) traité(s) Action Fichiers modifiés Résumé Statut après
(exemple) 2026-06-15 BUG-001 Correction frontend/app.js Réécriture de renderFile() pour préserver le DOM dashboard 🟢 corrigé (en attente vérif)
2026-09-09 BUG-001, BUG-002 Correction backend/main.py, frontend/excalidraw-editor.html, tests/test_pdf_stream.py BUG-001: Content-Disposition RFC 5987 (nom PDF accentué ne casse plus l'en-tête → plus de 500). BUG-002: suppression alias esm.sh (408 jotai) + React 19 cohérent + prop excalidrawAPI → Loading masqué, save OK. Vérifié: 534 tests backend verts + E2E navigateur. 🟢 corrigé (en attente vérif utilisateur)
2026-09-11 BUG-003, BUG-004 Correction backend/{main,indexer,export,pdf_reader,bookslm_routes}.py, backend/auth/router.py, .gitea/workflows/ci.yml, README.md, README.fr.md BUG-003: 33 erreurs mypy corrigées (annotations, gardes None, import PROVIDERS manquant → bug latent) + étape CI mypy rendue bloquante. BUG-004: lien README.md → docs/CONTRIBUTING.md. Vérifié: mypy 0 erreur, ruff OK, pytest 728 passed, frontend OK. 🟢 corrigé (en attente vérif utilisateur)
2026-09-11 BUG-005 Correction frontend/sw.js, frontend/index.html, frontend/js/sync.js, backend/main.py, .gitea/workflows/ci.yml, tests/frontend/sw.test.mjs, tests/test_api_main.py BUG-005: chargement mobile incomplet via Cloudflare. SW réécrit en network-first pour HTML/JS/CSS + caches versionnés ; précache corrigé (/static/js/…) ; kill-switch de session remplacé par une migration localStorage ponctuelle ; en-têtes /static passés de immutable 1 an à no-cache ; index.html/manifest/SPA en no-cache ; reload unique sur controllerchange. Vérifié: 754 tests backend, frontend 9+28+8 OK, ruff/mypy OK. 🟢 corrigé (en attente vérif utilisateur)
2026-09-11 BUG-006 Correction sécurité .env.example Clé API DeepSeek réelle exposée dans .env.example → remplacée par un placeholder. Rotation de la clé à faire côté DeepSeek (présente dans l'historique Git). 🟢 corrigé (rotation à confirmer par l'utilisateur)
2026-09-12 BUG-007, BUG-008, #82 Correction + refonte UI frontend/js/bookslm.js, frontend/js/ai.js, frontend/style.css, frontend/locales/{fr,en}.json, backend/bookslm_routes.py, tests/frontend/ai.test.mjs, tests/test_bookslm.py BUG-007 : motifs Unicode + recherche NFD insensible aux accents + navigation ↑/↓ fiabilisée (jeton de séquence, scrollIntoView). BUG-008 : contexte ad-hoc @ pris en compte en mode Général (backend) + repli vault=all. #82 : section « Fournisseur & modèle » compacte (recherche modèle + bulle capacités ℹ️ au survol/clic/appui long). Vérifié : tests frontend 41/41, tests/test_bookslm.py 47 passed. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 BUG-009 Correction frontend/js/ai.js, frontend/js/i18n.js, frontend/sw.js, frontend/style.css, frontend/locales/{fr,en}.json, tests/frontend/ai.test.mjs BUG-009 : locale non content-hashée servie depuis le cache HTTP → clés i18n brutes ; passage en cache:'no-store' + SW_VERSION v4. Liste de modèles corrompue (OpenRouter, styles en cache) → styles critiques en ligne, options display:block, plafond 200 + indicateur. Vérifié : tests frontend 42/42. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 BUG-010, BUG-011 Correction frontend/js/bookslm.js, frontend/js/ai.js, frontend/style.css, tests/frontend/ai.test.mjs BUG-010 : capture du vault des résultats @ + _contextVault() propagé aux requêtes /context//chat (mode Général). BUG-011 : popover modèle aligné à droite, largeur 340 px, noms multi-lignes + title. Vérifié en navigateur (Playwright) et tests frontend 43/43. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 BUG-012, #82 Correction + UX frontend/js/bookslm.js, frontend/js/ai.js, frontend/style.css, frontend/sw.js, frontend/locales/{fr,en}.json, tests/frontend/ai.test.mjs BUG-012 : le menu @ est toujours rendu et _mentionVault() retombe sur le vault de contexte puis le premier vault disponible. #82 : suppression des intitulés « Fournisseur & modèle » / « Fournisseur : », description du contexte Général déplacée en info-bulle, placeholder retiré, bouton Envoyer en emoji ✈️, indicateur d'activité (envoi/réception/outils/confirmation/succès/échec), SW_VERSION v5. Vérifié Playwright + tests frontend 45/45. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 BUG-013, BUG-014 Correction frontend/js/bookslm.js, frontend/index.html, frontend/sw.js, tests/frontend/ai.test.mjs BUG-013 : liens fichiers/dossiers via _activeVault() (vault de contexte → sélection → premier vault). BUG-014 : navigation ↑/↓ gérée au niveau du panneau en phase de capture. Purge des caches élargie (SW_VERSION v6, migration obsigate-*). Vérifié : tests frontend 47/47, pytest 930. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 BUG-015, #82 Correction + UX frontend/js/bookslm.js, frontend/style.css, tests/frontend/ai.test.mjs BUG-015 : sélection des menus / et @ rendue visible (--bg-hover + barre d'accent à gauche) car --surface2 = --bg-primary. #82 : retrait de l'indice clavier sous la zone de saisie. Vérifié Playwright (contraste #1f2430 vs #0f1117 + barre accent) et tests frontend 47/47. 🟢 corrigé (en attente vérif utilisateur)
2026-09-12 #82 Amélioration perf + UX backend/services/search.py, backend/main.py, frontend/js/bookslm.js, frontend/js/ui.js, frontend/style.css, tests/test_api_main.py, tests/frontend/ai.test.mjs #82 : endpoint GET /api/vault/{vault}/paths + préchargement et filtrage client du menu @ (instantané) ; sélecteurs Fournisseur/Modèle agrandis (0,8 rem / 34 px) ; « 🧠 BooksLM » ajouté au menu contextuel de la racine des vaults. Vérifié : pytest 151 (ciblés) + ruff/mypy, tests frontend 49/49. ✅ livré (en attente vérif utilisateur)
2026-09-12 #82 UX frontend/js/ai.js, frontend/style.css, tests/frontend/ai.test.mjs Sélecteurs Fournisseur/Modèle alignés à droite dans la barre de l'assistant et réordonnés : capacité du modèle (ⓘ) → fournisseur → modèle. Bulle de capacités ouverte vers la droite (left: 0). Test JSDOM de l'ordre. Vérifié : tests frontend 49/49. ✅ livré (en attente vérif utilisateur)
2026-09-12 BUG-016 Correction frontend/js/mobile-editor.js, frontend/style.css, tests/frontend/mobile-editor.test.mjs, tests/e2e/mobile-editor.spec.js BUG-016 : le bouton « mode lecture » n'apparaît que si un fichier est ouvert et que l'assistant est fermé, sinon il recouvrait le bouton d'envoi sur mobile. Vérifié : tests frontend 24/24 (mobile) + suite JSDOM verte. 🟢 corrigé (en attente vérif utilisateur)
2026-09-13 BUG-017, BUG-018, BUG-019, BUG-020, #83 Enregistrement docs/ISSUES_TODOLIST.md, docs/ROADMAP.md Signalements mobile consignés : accueil (tuiles Favoris/Récents), éditeur (boutons Annuler/Sauvegarder inaccessibles + barre IA), visibilité de la barre d'outils au-dessus du clavier, double barre de défilement. Feature #83 ajoutée : barre d'outils d'édition mobile style Obsidian Android. 🔴 ouvert (à traiter)
2026-09-13 Revue sécurité statique (BUG-021 → BUG-040, roadmap #84 → #87) Enregistrement docs/ISSUES_TODOLIST.md, docs/ROADMAP.md Revue statique 2026-09-13 consignée : XSS markdown (escape=False) + page de partage publique, brute-force MFA, traversal par préfixe paths.py, ReDoS, SSRF webhooks, cycle de vie des sessions, politique de mot de passe, races users.json, audits IP, rate-limit, indexation symlinks, recherche O(N), CSP. BUG-021→BUG-040 ouverts au registre ; roadmap #84→#87 (consolidation/sécurité, refonte architecturale, performance, CI/CD). 🔴 ouvert (à traiter)
2026-09-13 BUG-021 → BUG-034 (#84 phase 1) Correction backend/services/sanitizer.py, backend/services/paths.py, backend/services/net.py, backend/services/regex_safety.py, backend/main.py, backend/search.py, backend/webhooks.py, backend/indexer.py, backend/ratelimit.py, backend/auth/{router,user_store,password,jwt_handler,middleware}.py, frontend/js/{auth,admin,sync}.js, tests/test_security_hardening.py, tests/test_auth_api.py Sanitizer XSS serveur (markdown + page de partage), rate-limit/lockout MFA, isolation vaults par segments, caps regex (ReDoS), SSRF webhooks + secrets externalisés, rotation/révocation des jetons, politique de mot de passe + invalidation des sessions, verrous users.json, IP réelle dans les audits, rate-limit par compte, symlinks d'index ignorés, recherche simple via inverted index, token en mémoire + cookie HttpOnly + CSP durcie. Vérifié : pytest 961 passed / 6 skipped, ruff 0, mypy 0, tests frontend 36 modules + 9 JSDOM suites verts. 🟢 corrigé (en attente vérif utilisateur)
2026-09-13 BUG-017 → BUG-020, #83 Correction + feature frontend/js/mobile-editor.js, frontend/style.css, frontend/index.html, frontend/locales/{fr,en}.json, tests/frontend/mobile-editor.test.mjs, tests/e2e/mobile-editor.spec.js BUG-017 : cartes de tableau de bord compressibles (min-width:0) ; BUG-018 : en-tête éditeur mobile compact + barre IA tactile ; BUG-019 : ancrage du ruban via visualViewport ; BUG-020 : suppression de la double scrollbar. #83 : ruban d'édition horizontal défilable style Obsidian (commandes étendues + personnalisation ⚙ persistée). Vérifié : tests frontend 35/35 (mobile) + 50/50 (IA) + 9 JSDOM suites. 🟢 corrigé (en attente vérif utilisateur)
2026-09-14 BUG-042 (complément) Correction frontend/js/bookslm.js, tests/frontend/ai.test.mjs, docs/features/ai-app-context.md Prise en charge des espaces dans les noms de fichiers et chemins des liens de l'assistant : cibles markdown avec espaces / <…> / %20 décodés, code inline reconnu (_looksLikePath élargi, rejette toujours les extraits de code), mentions brutes liées uniquement si présentes dans l'index du vault (_linkifySpacePaths + _confirmPathInCache, plus long suffixe aligné sur un mot pour ne pas avaler le mot de prose précédent). Vérifié : tests frontend 57/57 (IA) + 9 suites JSDOM, validate-imports 36 modules, pytest 963 passed / 6 skipped. 🟢 corrigé (en attente vérif utilisateur)
2026-09-14 BUG-041, BUG-042, #88 Correction + feature frontend/js/bookslm.js, backend/bookslm.py, backend/bookslm_routes.py, frontend/locales/{fr,en}.json, tests/test_bookslm.py, tests/frontend/ai.test.mjs, docs/features/ai-app-context.md BUG-041 : contexte de dossier vide → dégradation gracieuse vers le prompt Général + bloc « Dossier vide » (fin du 404). BUG-042 : liens de fichiers déterministes (nom → presse-papiers, dossier → arborescence, chemin → viewer) + résolution du chemin contre l'index du vault avant ouverture. #88 : app_context (documents ouverts, répertoire/vault courants, recherche + résultats affichés) et fichiers récemment modifiés injectés dans le prompt Général. Vérifié : pytest 963 passed / 6 skipped, ruff 0, mypy 0, tests frontend 52/52 + validate-imports 36 modules. 🟢 corrigé (en attente vérif utilisateur)
2026-09-14 BUG-042 (accents + préfixe vault) Correction frontend/js/bookslm.js, tests/frontend/ai.test.mjs, docs/features/ai-app-context.md Caractères accentués : classes de caractères Unicode (\p{L}\p{N}\p{M}) pour PATH_WITH_DIR_RE et PATH_NAME_RE (_looksLikePath), comparaison normalisée NFC via _normKey() → une mention décomposée (e + accent combinant, style macOS) correspond à une entrée d'index précomposée, et les liens sans espace mais accentués sont enfin produits. Préfixe de vault : _splitVaultPrefix() retire un premier segment égal à un vault connu (TestVault/Recettes/Pizza Maison.md) ; _fetchPathsForVault() interroge l'index de ce vault sans écraser le cache du vault actif ; _openFileLink/_revealPath reçoivent le vault cible ; repli « retirer le premier segment » si le préfixe est inconnu. Vérifié : tests frontend 62/62 (IA) + 9 suites JSDOM, validate-imports 36 modules, pytest 963 passed / 6 skipped, et vérification contre l'instance live (données réelles accentuées/espacées : Recettes/Préparation.md, Recettes/Pâtes carbonara.md, Recettes/Pizza Maison.md) — 8/8 contrôles. 🟢 corrigé (en attente vérif utilisateur)
2026-09-14 BUG-043, #82 Correction frontend/js/ai.js, frontend/js/bookslm.js, frontend/js/config.js, tests/frontend/ai.test.mjs, docs/features/ai-provider-picker.md La liste des fournisseurs de la barre latérale de l'assistant suit désormais la configuration du projet : nouveau refreshAIPickers() (ai.js) qui reconstruit chaque picker monté dans son emplacement .ai-picker-slot (host conservé dans la barre de l'assistant, montage par replaceChildren), appelé après saveAIKeys() et deleteAIKey() (config.js) ; slot préservé même sans fournisseur configuré (le premier fournisseur ajouté s'y monte) ; sélection persistée d'un fournisseur non configuré purgée de obsigate_ai_picker (retour au défaut, modèle effacé). Vérifié : tests frontend 66/66 (IA) + 9 suites JSDOM vertes, validate-imports 36 modules, pytest 963 passed / 6 skipped, ruff OK, et vérification en navigateur (Playwright) sur l'instance de test : ajout de nvidia → fournisseur visible sans rechargement, retrait → disparition + sélection réinitialisée ({"provider":null,"model":null}), un seul montage du picker dans son host. CI Gitea verte (lint, test, security, build, e2e).
2026-09-15 BUG-044 Correction backend/provider_capabilities.py (nouveau), backend/model_capabilities.py, backend/main.py, backend/ai_routes.py, tests/test_provider_capabilities.py (nouveau), tests/test_model_capabilities.py, tests/test_ai_models.py, docs/features/ai-provider-picker.md, CHANGELOG.md BUG-044 : les capacités des modèles IA sont désormais lues chez le fournisseur quand il les publie (Mistral capabilities, OpenRouter architecture, détection par forme du payload), mises en cache par GET /api/config/ai-models (aucune requête supplémentaire) et prioritaires sur la table statique qui ne comble plus que les drapeaux non déclarés (repli hors ligne / sans clé). Table statique corrigée : familles vision Mistral (ministral, magistral, mistral-small, mistral-medium, mistral-vibe-cli, labs-leanstral), mistral-ocr = vision sans chat, défaut fournisseur Mistral sans embeddings (mistral-large / codestral n'étaient plus des « embedders »). Vérifié : diagnostic live avant/après sur l'API Mistral (28 modèles vision déclarés, 0 détectés avant → 28 après, 0 écart dans les deux sens), pytest 1007 passed / 6 skipped, ruff 0 (backend), mypy 0 (68 fichiers), tests frontend (unit 9/9, IA 66/66, validate-imports 37 modules), et vérification sur l'instance de test reconstruite (obsigate-test, http://localhost:2020) via les deux endpoints du panneau : mistral-small/medium-latest, ministral-8b-latest, magistral-small-latest → chat,vision ; mistral-ocr-latest → vision seul ; mistral-large-latest/codestral-latest → chat ; mistral-embed → embeddings seul ; deepseek-chat (fournisseur muet) inchangé. 🟢 corrigé (en attente vérif utilisateur)
2026-09-16 BUG-045, BUG-046 Correction frontend/style.css, frontend/js/bookslm.js, backend/bookslm.py, backend/bookslm_routes.py, frontend/sw.js, tests/test_bookslm.py, tests/frontend/ai.test.mjs, tests/frontend/editor-inline.test.mjs, CHANGELOG.md BUG-045 : fin de la double barre de défilement de l'éditeur « Editer » — #editor-body en flex column overflow:hidden, .cm-editor flex:1 1 auto, seul le scroller CodeMirror défile ; suppression de l'override global .cm-scroller (min-height/overflow-y !important). BUG-046 : « Échec de l'action : [object Object] » à l'application d'un ajout de texte au document courant — (1) _responseError() aplatit le detail 422 (tableau d'objets FastAPI) en message lisible, (2) la continuation de confirmation hérite du payload d'origine (le second « Appliquer » ne POSTe plus sans message), (3) le prompt documents/dossier nomme le vault et enjoint les outils d'écriture d'utiliser ce nom exact (le modèle inventait "vault":"test" → échec silencieux). SW_VERSION v20. Vérifié : pytest 1038 passed / 6 skipped, ruff 0 (backend), tests frontend IA 84/84, editor-inline 25/25, validate-imports 38 modules ; reproduction et validation Playwright sur l'instance de test (un seul conteneur scrollable après correctif). 🟢 corrigé (en attente vérif utilisateur)
2026-09-16 #93 (complément) Correction frontend/js/editor-inline.js, frontend/js/utils.js, frontend/js/viewer.js, frontend/js/ui.js, frontend/js/pane-manager.js, frontend/js/sync.js, tests/frontend/editor-inline.test.mjs, docs/features/editeur-inline.md, docs/CONTRIBUTING.md, CHANGELOG.md Garde-fous de session d'édition inline (#93) : l'activation d'un onglet (TabManager.activate, PaneTabManager.activate) appelle detachInlineEditor() avant de vider la zone de contenu, et l'événement SSE index_updated sur le fichier affiché recharge le tampon de l'éditeur (reloadExternalWrite()) au lieu de re-rendre la vue lecture — ces deux chemins détruisaient la session CodeMirror/Forge en cours. Nouveau helper queryEditor() (l'en-tête/pied/marque voyagent avec le conteneur en mode inline, modal.querySelector() les manquait) ; closeEditor() remet le conteneur dans la modale avant de restaurer en-tête/pied/marque. docs/CONTRIBUTING.md documente scripts/install-hooks.sh (hooks de versionnage) dans la mise en place d'un clone. Vérifié : validate-imports 38 modules, unit 9/9, suites JSDOM (editor-inline 25/25, pane-manager, mobile-editor 35/35, toolbar-order, ai 84/84, sw 8/8, collab 10/10, semantic-search 4/4, desktop 23, plugins 21, excalidraw-viewer) toutes vertes, pytest 1082 passed / 6 skipped. 🟢 corrigé (en attente vérif utilisateur)
2026-09-16 BUG-047 Correction + build VERSION (nouveau), scripts/bump_version.py (nouveau), .githooks/prepare-commit-msg + .githooks/post-commit (nouveaux), scripts/install-hooks.sh (nouveau), scripts/bump_version.sh, backend/version.py, Dockerfile, docker-compose.yml, build.sh, .gitea/workflows/ci.yml, desktop/build.rs, package.json, README.md, README.fr.md, docs/ROADMAP.md, docs/DELIVERY_WORKFLOW.md, docs/DEVELOPMENT_AND_RELEASES.md, tests/test_version.py (nouveau) Version alignée de bout en bout. Cause : la version affichée venait du dernier tag git et aucun tag n'était créé aux livraisons → 66 commits livrés mais UI/API figées sur 2.2.1 ; quatre autres numéros codés en dur dérivaient (package.json 1.0.0, desktop 2.0.0, Dockerfile 2.2.1, README 1.7.0). Nouveau modèle : VERSION (racine) = source unique de vérité (MAJEUR.MINEUR.CORRECTIF), incrémentée automatiquement au commit par le hook versionné prepare-commit-msg (SemVer depuis le message : !:/BREAKING CHANGE → MAJEUR, feat → MINEUR, sinon CORRECTIF ; aucun bump pour merge/revert/chore(release)/amend) qui resynchronise package.json, desktop Tauri, ROADMAP, READMEs et publie la section [Unreleased] du CHANGELOG en [X.Y.Z] — date ; post-commit rattache ces fichiers au commit qui vient d'être créé (amend immédiat, le commit n'étant pas encore poussé) puis crée le tag vX.Y.Z, publié au push (push.followTags). bump_version.py (avec --dry-run, --set, --major|--minor|--patch, --print-version) reste utilisable à la main ; scripts/install-hooks.sh installe les hooks. Côté build : Docker COPY VERSION (plus d'ARG VERSION ni d'env compose codés en dur), build.sh et CI lisent VERSION, desktop/build.rs aussi. Vérifié : tests/test_version.py (46 tests, dont le garde-fou TestRepoVersionAlignment : VERSION ↔ package.json ↔ desktop ↔ CHANGELOG ↔ ROADMAP ↔ READMEs ↔ pipeline sans numéro codé en dur), pytest complet, ruff/mypy 0, /api/health → 2.3.0 sur l'instance de test reconstruite. 🟢 corrigé (en attente vérif utilisateur)

| 2026-09-16 | #94, #95, #96, #97 | Feature | backend/ai_history.py (nouveau), backend/bookslm_routes.py, frontend/js/bookslm.js, frontend/js/config.js, frontend/index.html, frontend/style.css, frontend/locales/{fr,en}.json, tests/test_bookslm.py, tests/frontend/ai.test.mjs, docs/features/ai-assistant-history.md (nouvelle), docs/ROADMAP.md, CHANGELOG.md | Assistant IA : #95 historique permanent (backend data/ai_history/{user}.json, cap 200, endpoints CRUD /api/ai/bookslm/history[…] résumés/full, sync frontend debounced 600 ms + repli localStorage + migration des clés legacy bookslm-sessions-*/bookslm-history-*, événement bookslm:history-updated) ; #96 onglet sidebar #sidebar-tab-ai (messages-square) + panneau #sidebar-panel-ai (liste chronologique, ouverture via openWithSession) ; #97 bouton « + » remplaçant « Attach an image » + panneau modulaire .bookslm-ext-menu (registre _extensions : Fichiers, Image, Contextes, Skills, Deep Research = mode agent + prompt, Recherche web & Canva en « Bientôt ») ; #94 bouton d'envoi circulaire + icône Lucide arrow-up. Vérifié : pytest 1088 passed / 6 skipped, ruff 0, mypy 0 (71 fichiers), tests frontend IA 84/84, unit 9/9, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-048, #98 | Correction + feature | frontend/js/bookslm.js, frontend/js/config.js, frontend/js/sidebar.js, frontend/style.css, frontend/locales/{fr,en}.json, .gitea/workflows/ci.yml, tests/frontend/ai.test.mjs, tests/frontend/ai-sidebar.test.mjs (nouveau), docs/features/ai-assistant-history.md, docs/ROADMAP.md, CHANGELOG.md | BUG-048 : les entrées « Contextes » et « Skills » du menu « + » ouvraient bien leur menu (@ / /), mais le clic remontait au gestionnaire du panneau qui annulait le rendu asynchrone → menu jamais affiché ; correction par e.stopPropagation() sur les entrées du panneau .bookslm-ext-menu. #98 : la barre de filtrage de la sidebar agit désormais sur l'onglet « Historique IA » — filterAIHistory() (config.js) filtre par titre, aperçu, répertoire, contexte ou libellé de mode, insensible casse/accents (_aiNorm), cache sessions _aiSessionsCache, message « aucune correspondance » (bookslm.history_no_match) dans la liste et placeholder dédié (sidebar.filter_ai) ; initSidebarFilter (sidebar.js) route saisie/touche casse/bouton « × » vers filterAIHistory quand l'onglet IA est actif ; chaque entrée du panneau « + » porte l'icône Lucide plus. Vérifié : tests frontend IA 87/87 (+3), nouvelle suite ai-sidebar 6/6, unit 9/9, 9 suites JSDOM vertes, validate-imports 38 modules, pytest / ruff / mypy inchangés (aucune modification backend). | 🟢 corrigé (en attente vérif utilisateur) — CI Gitea verte (lint, test, security, build, e2e) pour v2.5.0 (run #1511) | | 2026-09-16 | BUG-049, #99, #100 | Correction + feature | frontend/style.css, frontend/js/config.js, frontend/js/viewer.js, frontend/js/sidebar.js, frontend/js/bookslm.js, frontend/locales/{fr,en}.json, .gitea/workflows/ci.yml, tests/frontend/ai.test.mjs, tests/frontend/sidebar-filters.test.mjs (nouveau), docs/features/sidebar-filters.md (nouvelle), docs/features/ai-assistant-history.md, docs/ROADMAP.md, CHANGELOG.md | BUG-049 : icône du bouton « + » de l'assistant invisible — la règle générique .bookslm-input-area button (spécificité supérieure) imposait padding: 8px 16px sur un bouton width: 32px ⇒ largeur de contenu nulle ⇒ SVG width: 0px ; sélecteur porté à .bookslm-input-area button.bookslm-btn-plus (+ :hover), vérifié en navigateur (Playwright : SVG 0 px → 18 px). #99 : la barre de filtrage de la sidebar agit désormais sur les vues Récents (filterRecentFiles, titre/chemin/vault/aperçu/tags) et Sauvegardes (filterSavedSearches, cumulable avec les pills type), insensible casse/accents (_sidebarNorm/_savedNorm), message d'absence de résultat (sidebar.no_results) et placeholders dédiés (sidebar.filter_recent, sidebar.filter_saved) ; initSidebarFilter refactoré en routeFilter/routeClear couvrant les 5 onglets. #100 : « Deep Research » ajoute une pastille .bookslm-chip-deep-research (au lieu d'injecter la directive dans le composeur), active le mode Agent et injecte la directive au moment de l'envoi. Vérifié : tests frontend IA 88/88 (+1), sidebar-filters 8/8 (nouveau), ai-sidebar 6/6, unit 9/9, 9 suites JSDOM vertes, validate-imports 38 modules, vérification navigateur du bouton « + » ; backend inchangé (pytest / ruff / mypy valides). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-050 | Correction | backend/agent/loop.py, backend/services/mutations.py, backend/tools/service.py, backend/bookslm.py, tests/test_agent_loop.py, tests/test_tools_mutations.py, tests/test_api_main.py, docs/features/ai-tools-mcp.md, CHANGELOG.md | BUG-050 : création d'un sous-dossier contenant un fichier en mode Agent. (1) La boucle d'agent renvoyait la conversation sans réponse pour les tool_calls non atteints lorsqu'un appel mutateur déclenchait une confirmation → le provider rejetait le tour de reprise (« tool_call_id » orphelin) ; les appels restants reçoivent désormais un résultat deferred explicite (_deferred_tool_message) que le modèle réémet après confirmation. (2) create_directory est idempotent côté outil IA (exist_ok=True, succès si le dossier existe), le REST restant strict (409). (3) Consignes renforcées : create_file crée les dossiers parents, un seul appel avec chemin imbriqué suffit (backend/bookslm.py, descriptions d'outils). Vérifié : pytest 1091 passed / 6 skipped, ruff 0 (backend), mypy 0 (71 fichiers), tests frontend validate-imports 38 modules + unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-051 | Correction | backend/tools/web.py, tests/test_web_tools.py, docs/features/ai-tools-roadmap.md, docs/features/ai-assistant-conversation-ux.md, docs/ROADMAP.md, CHANGELOG.md | BUG-051 : web_search ne dépend plus d'une seule instance SearXNG. Nouvelle chaîne de fournisseurs (_provider_chain) : SearXNG (auto-hébergé, JSON) → DuckDuckGo (html.duckduckgo.com/html/, extraction result__a/result__snippet, décodage du lien uddg=) → Bing (www.bing.com/search, extraction h2 > a + p.b_lineclamp*, décodage de la redirection u=a1<base64url>), UA navigateur, premier fournisseur non vide retenu et exposé (provider). Le champ warning final liste les fournisseurs essayés ; replis désactivables via OBSIGATE_WEB_FALLBACK=0 ; erreur web_search_unavailable uniquement si tous les fournisseurs sont injoignables. Vérifié : pytest 1082 passed / 6 skipped (14 erreurs MCP préexistantes, sans lien), ruff 0 (backend), mypy 0 (backend/tools/web.py), tests/test_web_tools.py 13/13, recherche live Bing (horaire Canadiens) sur l'hôte. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-051 (complément) | Correction | backend/tools/web.py, CHANGELOG.md | BUG-051 suite : un User-Agent navigateur seul ne suffit pas — Bing renvoie une SERP factice (résultats sans rapport, ex. « highest paying jobs » / « Sam Reid ») aux requêtes sans en-têtes de navigation. Ajout de BROWSER_HEADERS (Accept-Language, Sec-Fetch-*, Upgrade-Insecure-Requests) pour DuckDuckGo et Bing. Vérifié en conteneur (obsigate-test, v2.7.1) : web_search('Canadien de Montreal horaire matchs 2026 2027') → provider: bing, 5 résultats pertinents (nhl.com/fr/canadiens, rds.ca, fr.wikipedia.org). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-052 | Correction | backend/agent/loop.py, tests/test_agent_loop.py, CHANGELOG.md | BUG-052 : la boucle d'agent ne rendait plus jamais de réponse vide. _finalize_answer : à l'épuisement du budget d'itérations (STOP_MAX_ITERATIONS) ou du quota d'appels (STOP_QUOTA_EXCEEDED), un dernier appel LLM sans outil reçoit une instruction de synthèse (« N'appelle plus aucun outil. Réponds maintenant… ») et son texte devient la réponse ; si l'appel échoue ou reste vide, _fallback_summary compose une liste déterministe des sources (web_search/fetch_url) pour ne jamais renvoyer un tour vide. Les tool_calls non atteints lors d'un arrêt sur quota reçoivent un résultat deferred (conversation valide pour la synthèse). Vérifié : tests/test_agent_loop.py 16/16 (+2 : synthèse finale, repli sources), suite complète 1084 passed / 6 skipped (14 erreurs MCP préexistantes), ruff 0 (backend), mypy 0 (backend/agent/loop.py). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-16 | BUG-053 | Correction | backend/bookslm.py, backend/bookslm_routes.py, tests/test_bookslm.py, CHANGELOG.md | BUG-053 : en mode agent, le prompt Général (et dossier vide) enseignait le protocole texte obsigate-action ; le modèle décrivait donc l'action au lieu d'appeler l'outil natif create_file (bloc volumineux de surcroît tronqué avant fermeture → aucun fichier créé). Le prompt est scindé : GENERAL_ACTION_TOOL_PROTOCOL (appel direct des outils natifs, interdiction explicite des blocs obsigate-action) pour build_general_system_prompt(agent=True), le protocole texte restant utilisé par le chat classique ; _resolve_system_prompt propage agent et ajoute une règle « Mode agent » aux prompts dossier/documents ; max_tokens de l'agent porté à 8192 pour un contenu de fichier complet. Vérifié : tests/test_bookslm.py 68/68 (+3 : prompt agent sans protocole texte, prompt classique inchangé, prompt système de l'endpoint agent), suite complète 1087 passed / 6 skipped (14 erreurs MCP préexistantes), ruff 0 (backend), mypy 0 (backend/bookslm.py, backend/bookslm_routes.py). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-054 | Correction | frontend/js/utils.js, tests/frontend/editor-inline.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-054 : le bouton #editor-save (nœud partagé entre toutes les sessions d'édition) restait bloqué sur le spinner de chargement et désactivé — une sauvegarde manuelle (clic ou Ctrl+S) remplaçait le crochet par le loader et ne le restaurait jamais : succès (l'éditeur se ferme, la réouverture réaffichait le spinner), sauvegarde Forge, ou échec réseau (le catch ne restaurait ni l'icône ni l'état). Nouveau helper resetSaveButton() (crochet &#10003;, disabled=false, styles en ligne nettoyés) appelé à l'ouverture (openEditor), à la fermeture (closeEditor) et en cas d'échec (saveFile). Vérifié : tests/frontend/editor-inline.test.mjs 29/29 (+4), validate-imports 38 modules / 0 erreur. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | #101 | Feature | frontend/editor-poc.html, frontend/js/sync.js, frontend/js/viewer.js, frontend/js/utils.js, frontend/index.html, frontend/style.css, frontend/locales/{fr,en}.json, tests/frontend/editor-inline.test.mjs, docs/features/forge-assistant.md (nouvelle), docs/ROADMAP.md, CHANGELOG.md | #101 : le bouton « AI Panel » de Forge ouvre désormais l'Assistant IA partagé (postMessage forge-open-ai → bookslm.openForCurrentContext()) au lieu du mini-chat isolé (supprimé) ; Forge lit localStorage['obsigate_ai_picker'] (aiPickerSelection()) pour ses appels /api/ai/* et sa complétion fantôme (repli ollama), endpoints corrigés (make-longer/make-shorter, target_lang) ; bouton plein écran natif ajouté à Forge (iframe allow="fullscreen") et à Editer (#editor-fullscreen, conteneur #editor-container, sortie à la fermeture, Échap laissé au navigateur) ; i18n editor.fullscreen/editor.exit_fullscreen. Vérifié : tests/frontend/editor-inline.test.mjs 40/40 (+10), unit 9/9, validate-imports 38 modules, 13 suites JSDOM vertes. | 🟢 livré (en attente vérif utilisateur) | | 2026-09-17 | BUG-055 | Correction | frontend/editor-poc.html, frontend/js/autocomplete.js, backend/ai.py, .gitea/workflows/ci.yml, tests/frontend/forge-completion.test.mjs (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-055 : trois gestionnaires keydown Tab indépendants s'exécutaient à chaque appui — l'indentation (insertAtCursor(' ')) s'ajoutait à la complétion de mot (insertAtCursor(suffixe)) et à l'acceptation du ghost text, d'où l'espace parasite avant le mot complété puis un effacement destructeur. Gestion unifiée de Tab (liste ouverte > ghost > mot du document > indentation), helpers purs partagés (getWordFragment/findWordCompletions/normalizeGhost/chooseTabAction) extraits dans autocomplete.js, liste déroulante au curseur quand plusieurs mots correspondent, ghost positionné au curseur (plus de miroir du document entier, nettoyé au déplacement/scroll), complétion de mot sans espace garantie et prompt /api/ai/inline-complete simplifié (128 tokens). Vérifié : tests/frontend/forge-completion.test.mjs 28/28 (nouveau), unit.test.mjs 9/9, editor-inline.test.mjs 40/40, ai.test.mjs 88/88, validate-imports 38 modules, pytest 1101 passed / 6 skipped, ruff 0, mypy 0. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-055 (complément) | Correction | frontend/editor-poc.html, frontend/js/utils.js, tests/frontend/forge-completion.test.mjs, tests/frontend/editor-inline.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-055 (complément) : une complétion acceptée au Tab disparaissait 1–2 s plus tard. Cause : l'auto-sauvegarde (2 s) déclenche un index_updated SSE sur le fichier affiché, et reloadExternalWrite rechargeait le tampon Forge depuis le disque, écrasant toute frappe postérieure à la sauvegarde. Le rechargement SSE est désormais ignoré si le tampon est modifié (parent-reload sans force + isDirty ; garde équivalente sur le point d'auto-sauvegarde CodeMirror) ; seul obsigate:file-written (assistant IA) passe force=true. L'auto-sauvegarde ne remet plus l'état « enregistré » si des modifications sont arrivées pendant la requête (Forge + CodeMirror), et acceptGhost() annule la requête de prédiction en attente. Vérifié : forge-completion.test.mjs 31/31 (+3), editor-inline.test.mjs 41/41 (+1), 14 suites frontend vertes, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-056 | Correction | frontend/editor-poc.html, frontend/js/sync.js, tests/frontend/forge-completion.test.mjs, tests/frontend/editor-inline.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-056 : en plein écran Forge, l'Assistant IA s'ouvrait en arrière-plan. La sortie du plein écran est désormais faite côté iframe (openAssistant → document.exitFullscreen() puis postMessage à la résolution) et côté parent (sync.js sur forge-open-ai → document.exitFullscreen() puis openForCurrentContext()), car le plein écran peut appartenir au document parent (l'iframe voit alors fullscreenElement nul et sa sortie échoue — c'était le cas non couvert par le premier correctif). Vérifié : forge-completion.test.mjs 32/32 (+1), editor-inline.test.mjs 42/42 (+1), 14 suites frontend vertes, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-057, #102 | Correction + feature | frontend/js/bookslm.js, frontend/editor-poc.html, frontend/style.css, frontend/locales/{fr,en}.json, tests/frontend/ai.test.mjs, tests/frontend/editor-inline.test.mjs, docs/archive/COMPLETED_v1-v2.md, docs/ROADMAP.md, CHANGELOG.md | BUG-057 : le bouton « Ajouter » de l'assistant ne ciblait que state.editorView (CodeMirror) ; en Forge il affichait « Aucun document ouvert dans l'éditeur ». _insertIntoEditor() gère désormais les trois surfaces : CodeMirror, l'iframe Forge (postMessage({ type: 'parent-insert', text }) → insertAtCursor dans editor-poc.html) et le textarea de repli. #102 : chaque bloc de code d'une réponse reçoit un bouton « Ajouter la section » (.bookslm-code-insert, révélé au survol) qui insère le contenu du bloc sans les délimiteurs ```. Vérifié : ai.test.mjs 91/91 (+3), editor-inline.test.mjs 43/43 (+1), forge-completion.test.mjs 32/32, unit 9/9, validate-imports 38 modules, pytest 1101 passed / 6 skipped, ruff 0, mypy 0. | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-058 | Correction | frontend/style.css, tests/frontend/editor-inline.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-058 : la barre de numérotation de ligne de l'éditeur « Editer » ne suivait pas le thème — CodeMirror peint .cm-gutters avec des valeurs claires codées en dur (#f5f5f5, bordure #ddd), visibles en thème sombre. Correctif : le gutter dérive des variables CSS ObsiGate (background: color-mix(in srgb, var(--text-primary) 5%, transparent), color: var(--text-secondary), border-right: 1px solid var(--border), ligne active color-mix(… 10% …) / --text-primary), donc il suit les 15 thèmes et les 4 modes. Vérifié : editor-inline.test.mjs 44/44 (+1), unit 9/9, validate-imports 38 modules, pytest 1101 passed / 6 skipped, ruff 0, mypy 0, et Playwright sur l'instance de test (route style.css remplacée par le fichier local) — sombre color(srgb 0.90 0.93 0.95 / 0.05) + bordure #21262d, clair color(srgb 0.12 0.14 0.16 / 0.05) + bordure #d0d7de, plus de rgb(245,245,245). | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-17 | BUG-059 | Correction | frontend/js/bookslm.js, tests/frontend/ai.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-059 : dans une conversation ouverte (post ancré en haut), tout clic dans la fenêtre de messages — lien de fichier, étapes, sélection de texte — faisait sauter toute la conversation au bas de la fenêtre. Cause : le gestionnaire mousedown de dépintage (prévu pour la molette/tactile/poignée de scroll) se déclenchait aussi sur un simple clic, et le retrait du padding d'ancre (paddingBottom) bornait le scrollTop à la nouvelle hauteur max → saut au bas. Correctif : helper pur isScrollbarPress(target, clientX, container) — un appui ne dépine que s'il vise la poignée de scroll (cible = conteneur + zone de gouttière droite) ; molette et tactile conservent leur comportement. Vérifié : ai.test.mjs 92/92 (+1), unit 9/9, validate-imports 38 modules, pytest / ruff / mypy inchangés côté backend. | 🟢 corrigé (en attente vérif utilisateur) |


📜 Historique des bugs résolus

Les rangées ✅ vérifié (ou ⚪ abandonné) sont déplacées ici régulièrement pour garder le registre actuel court et lisible. Conservez l'ID d'origine pour la traçabilité.

# Titre Date résolution Résolu par Correctif / Commit Notes
(aucun pour l'instant)

🧪 Procédure de vérification d'une correction (pour l'IA)

Quand une correction est écrite, l'agent IA doit avant de passer en 🟢 corrigé :

  1. Exécuter les tests du projet :
    • Backend : python -m pytest tests/ -v (ou pytest)
    • S'il existe des tests ciblés sur la zone modifiée, les lancer en priorité.
  2. Vérifier le lancement du module concerné (import sans erreur, endpoint répond).
  3. Relire sa propre diff pour éviter les régressions.
  4. Mettre la colonne « Correctif / Commit » à jour puis le statut en 🟢 corrigé.
  5. Si un test échoue ou que la correction est incomplète, rester en 🟠 en cours et le noter.

✔️ Checklist de traitement d'un bug (rappel pour toute IA)

  • Bug identifié avec un ID et repris dans le registre
  • Statut posé en 🟠 en cours avant de coder
  • Correction conforme aux conventions du projet
  • Tests exécutés et vert (colonne « Correctif / Commit » renseignée)
  • Journal des interventions AI mis à jour
  • Statut passé en 🟢 corrigé (pas ✅ vérifié sauf autorisation)
  • (Après validation utilisateur) rangée déplacée dans l'historique

📚 Liens utiles