136 KiB
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-24
⚙️ 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]
- Un bug est signalé → posé en
🔴 ouvertpar l'utilisateur ou l'IA (à l'issue d'une investigation ou d'une session de test). - L'IA prend en charge → passe le statut en
🟠 en coursavant de commencer à coder, et crée une entrée dans 🛠️ Journal des interventions AI. - La correction est terminée → l'IA exécute les tests pertinents, passe le statut en
🟢 corrigéet remplit la colonne « Correctif / Commit ». - 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. - 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 :
- Lire ce fichier en entier (en particulier ce guide et le registre).
- Identifier les rangées de statut
🔴 ouvertavec un scopeIAet une priorité élevée qui lui sont attribuées (colonne « Assigné »). - Poser le statut
🟠 en courssur la rangée choisie avant toute modification de code. - Corriger en respectant la checklist de vérification et les conventions du projet.
- Revenir mettre le statut à
🟢 corrigé, documenter le correctif, et journaliser son passage. - 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-###ouTODO-###(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) |
🟢 corrigé | P2 | ⚙️ backend | IA | backend/secret_redactor.py |
Lire une note contenant un commit git (40 caractères hexadécimaux) | Masquage hex conditionné au contexte (_redact_bare_hex_secrets) : secret exigé dans les 60 caractères précédents, exemption explicite pour commit/sha*/hash/checksum/git/etag. Tests : tests/test_api_main.py::TestSecretRedactor (+4) |
Contenus mutilés dans les lectures et réponses IA |
| BUG-036 | [🔵 MINEUR] Collab WebSocket : token en query string | 🟢 corrigé | P2 | ⚙️ backend | IA | backend/collab.py |
Observer l'URL du websocket dans le trafic réseau | authenticate_websocket ne lit plus ?token= : cookie HttpOnly access_token uniquement ; rejet des trames > MAX_MESSAGE_CHARS (16 Mio) avant analyse. Tests : tests/test_collab.py (+3) |
Jeton visible dans les logs/proxys |
| BUG-037 | [🔵 MINEUR] Compte « anonymous » administrateur si auth désactivée | 🟢 corrigé | P2 | 🔐 sécurité | IA | backend/auth/middleware.py, backend/main.py |
Démarrer avec l'authentification désactivée | _guard_insecure_auth() : avertissement explicite + refus de démarrage sur bind non-loopback sans OBSIGATE_ALLOW_INSECURE=true. Tests : tests/test_auth.py::TestInsecureAuthGuard (+6) |
Comportement par conception mais risqué si mal configuré |
| BUG-038 | [🔵 MINEUR] Argon2 à 64 MB par vérification : risque d'épuisement mémoire | 🟢 corrigé | P2 | 🔐 sécurité | IA | backend/auth/password.py |
Lancer de nombreux POST /api/auth/login simultanés |
Recalibré à m=19456 Kio (19 Mio), t=2, p=1 (OWASP) ; anciens hachages valides + rehash auto. Test : tests/test_auth.py::TestPasswordHashing::test_argon2_memory_recalibrated |
DoS mémoire possible sur les petites instances |
| BUG-039 | [🔵 MINEUR] Enumération de comptes : 429 (verrouillé) vs 401 (inconnu) | 🟢 corrigé | P3 | 🔐 sécurité | IA | backend/auth/router.py |
Tenter un login sur un compte verrouillé puis un nom inconnu | Login uniforme : inconnu / désactivé / verrouillé / rate-limit par compte → 401 Identifiants invalides + hachage factice (timing équivalent) ; seul le rate-limit IP reste 429. Tests : tests/test_auth_api.py (+3) |
Le statut HTTP distinguait l'existence d'un compte |
| BUG-040 | [🔵 MINEUR] Extraction PDF intégrale (100 ko) au scan de démarrage | 🟢 corrigé | P2 | ⚙️ backend | IA | backend/indexer.py, backend/main.py |
Démarrer sur un vault contenant de nombreux PDF | _scan_vault ne lit que les métadonnées ; enrich_pdf_texts() extrait le texte après l'index (démarrage) et après chaque réindexation. Tests : tests/test_pdf.py (+3) |
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 ✓ + 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 |
| BUG-060 | [🟡 IMPORTANT] Viewer PDF : l'affichage des pages ne fonctionne pas — seule la barre d'outils « PDF — N pages » s'affiche, le contenu reste vide | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/viewer.js, tests/frontend/pdf-viewer.test.mjs (nouveau), tests/e2e/pdf-viewer.spec.js (nouveau) |
Cliquer un fichier .pdf dans l'arborescence |
frontend/js/viewer.js : le rendu PDF passe de <embed type="application/pdf"> à <iframe> (autorisée par frame-src 'self', le stream étant same-origin). Tests : tests/frontend/pdf-viewer.test.mjs (+6) et tests/e2e/pdf-viewer.spec.js (fixture test_vault/sample-pdf.pdf) |
Cause : la CSP durcie en BUG-034 pose object-src 'none', directive qui gouverne <embed>/<object> → le lecteur PDF natif était bloqué (barre d'outils rendue, corps vide). Le test E2E échoue bien avec l'ancien <embed>. object-src 'none' conservé (le correctif ne désarme pas la CSP) |
| BUG-061 | [🟡 IMPORTANT] Assistant IA : le bouton « Plein écran » n'agrandit plus le panneau | 🟢 corrigé | P2 | 📱 frontend | IA | frontend/style.css, tests/frontend/ai.test.mjs |
Ouvrir l'assistant, redimensionner le panneau, puis cliquer « Plein écran » | La largeur du panneau est écrite en ligne par la poignée de redimensionnement / la largeur persistée (localStorage) ; l'inline l'emportait sur .bookslm-panel.fullscreen { width: 100vw }. Ajout de !important sur la règle plein écran. Tests : ai.test.mjs (+1 : classe basculée + règle CSS). Vérifié Playwright : 640 px → 1400 px (viewport) |
|
| BUG-062 | [🟡 IMPORTANT] Viewer PDF : le document ne prend pas toute la largeur quand la navigation est masquée | 🟢 corrigé | P2 | 📱 frontend | IA | frontend/style.css, tests/frontend/pdf-viewer.test.mjs, tests/e2e/pdf-viewer.spec.js |
Ouvrir un PDF puis masquer la barre de navigation gauche | La règle .sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px } (colonne de lecture centrée) s'appliquait aussi aux viewers plein cadre. Ajout de .content-area:has(.pdf-viewer-container) (et .image-viewer-container) avec max-width: none; margin: 0. Test E2E : max-width calculé = none, conteneur = largeur du contenu |
|
| BUG-063 | [🟡 IMPORTANT] Viewer PDF : la table des matières s'affiche mais ne navigue pas | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/viewer.js, tests/frontend/pdf-viewer.test.mjs, tests/e2e/pdf-viewer.spec.js (fixture test_vault/sample-pdf-toc.pdf) |
Ouvrir un PDF avec signets, puis cliquer une entrée de la TOC | Deux causes : (1) contentWindow.location.hash='page=N' n'atteint pas le document (lecteur PDF natif dans une fenêtre about:blank) ; (2) un simple changement de fragment sur iframe.src est une navigation same-document ignorée par le lecteur natif. navigatePdfToPage() (liens data-page + listeners, plus d'onclick inline) recharge réellement l'iframe via un paramètre de query qui change (&_pdfpage=<ts>#page=N). Test E2E : src finit par &_pdfpage=<n>#page=3. Vérifié en Chrome headful : page 1 → page 8 → page 1 (captures identiques au retour) |
Le fragment seul ne suffisait pas : Chrome applique #page=N au chargement, pas lors d'un changement de fragment |
| BUG-064 | [🟡 IMPORTANT] Éditeur Excalidraw : le diagramme ne s'affiche jamais (canvas vide), pour tout fichier .excalidraw / .excalidraw.md |
🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | frontend/excalidraw-editor.html, backend/main.py, tests/frontend/excalidraw-viewer.test.mjs, tests/test_security_hardening.py, tests/e2e/excalidraw.spec.js, test_vault/diagram-app-export.excalidraw |
Ouvrir un .excalidraw (ou .excalidraw.md) dans ObsiGate |
Deux causes : (1) la feuille de style d'Excalidraw n'était jamais chargée → éditeur non stylisé + .excalidraw sans hauteur fixe → boucle de resize jusqu'au plafond 2^25 (33 554 432 px) → scène blanche. Correctif : <link> CSS depuis esm.sh + style-src CSP autorisant https://esm.sh. (2) appState.collaborators objet JSON → collaborators.forEach is not a function ; sanitizeAppState() reconvertit en Map et écarte width/height/offsetLeft/offsetTop. |
Vérifié navigateur : hauteur canvas 525 px (avant 33 554 432), dessin affiché, UI stylisée, 0 erreur. E2E + tests statiques CSP/CSS ajoutés. |
| BUG-065 | [🟡 IMPORTANT] Éditeur Excalidraw : l'auto-save recharge la page en pleine édition | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/excalidraw-viewer.js, frontend/js/utils.js, frontend/excalidraw-editor.html, tests/frontend/excalidraw-viewer.test.mjs |
Ouvrir un .excalidraw puis modifier un élément : au bout de 2 s la vue se recharge |
Chaque modification déclenchait un PUT save 2 s plus tard → SSE index_updated → reloadExternalWrite → openFile → recréation de l'iframe (refresh visible). Auto-save supprimée : sauvegarde explicite (bouton 💾 / Ctrl+S). reloadExternalWrite ignore le fichier si un iframe Excalidraw est ouvert (iframe[data-excalidraw-vault/path]). Le badge « Modified » ne réagit plus aux changements d'appState (resize/zoom) mais à la signature des éléments. |
Vérifié Playwright : plus de refresh, badge stable après bascule plein écran. Test statique (absence de requestSave/saveTimer). |
| BUG-066 | [🔵 MINEUR] Configuration : icônes manquantes dans la table des matières (« Fichiers cachés », « Partages publics ») | 🟢 corrigé | P3 | 📱 frontend | IA | frontend/locales/{fr,en}.json |
Ouvrir Configuration → observer le sommaire : les entrées « Fichiers cachés » et « Partages publics » n'ont pas d'icône | config.section_hidden → « 🗂️ Fichiers cachés » / « 🗂️ Hidden files », config.section_shares → « 📤 Partages publics » (EN avait déjà l'icône). Test : tests/frontend/unit.test.mjs (+1 : toutes les entrées du sommaire portent une icône FR/EN) |
Les libellés du sommaire utilisent des clés i18n distinctes des titres de section (auto.f8ba6127, config.section_partages-publics) qui, elles, avaient l'icône |
| BUG-067 | [🔵 MINEUR] Guide d'utilisation : l'entrée « 📱 Mobile » du sommaire ne fait rien (section absente) | 🟢 corrigé | P3 | 📱 frontend | IA | frontend/index.html |
Ouvrir le Guide → cliquer « 📱 Mobile » dans le sommaire : rien ne se passe | L'ancre #help-mobile-editor était présente dans la TOC mais aucune section id="help-mobile-editor" n'existait (l'édition mobile n'était qu'un h3 de help-edition). Fix #105 : section dédiée créée avec ancre + entrée de nav cohérente. |
Vérifié par test statique tests/test_guide.py::test_nav_anchors_resolve |
| BUG-068 | Configuration — section « 🔒 Sécurité du compte » inachevée : boutons hors thème, QR code invisible, fiabilité des fonctions à valider | 🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | frontend/js/auth.js, frontend/style.css, backend/auth/router.py |
Configuration → 🔒 Sécurité du compte | frontend/style.css (+config-btn-primary/danger thème), backend/auth/router.py (qr_data_url segno local), frontend/js/auth.js (QR local + fallback, recovery WebAuthn, carte mot de passe, escapeHtml labels), locales FR/EN, backend/requirements.txt (+segno) ; tests tests/test_mfa.py (+1) + tests/frontend/mfa-settings.test.mjs (nouveau, 9) |
pytest 1241 passed / 6 skipped, ruff 0, mypy 0, frontend unit + validate-imports verts |
| BUG-069 | Login 2FA bloqué sans erreur : après user+pwd corrects, la page de login reste affichée et le challenge MFA n'apparaît jamais | 🟢 corrigé | P0 | 📱 frontend | IA | frontend/js/auth.js, frontend/index.html |
Activer 2FA → logout → login (bon user+pwd) | frontend/js/auth.js (showMfaChallenge → .login-card + erreur mfa.challenge_unavailable si montage impossible), locales FR/EN ; tests tests/frontend/mfa-settings.test.mjs (+2) |
Reproduit au navigateur avant correctif (challenge jamais affiché), vérifié après : challenge affiché, code erroné → erreur, code valide (200) → app ; frontend mfa-settings 11/11, unit + validate-imports verts |
| BUG-070 | Activation clé physique WebAuthn impossible : « Validation du credential WebAuthn échouée » à chaque tentative | 🟢 corrigé | P0 | ⚙️ backend | IA | backend/auth/webauthn_mfa.py, backend/auth/router.py |
Config → Sécurité → Ajouter une clé → cérémonie navigateur → 400 | resolve_relying_party() (rp_id/origines dérivés de la requête, config explicite prioritaire, forwarded si TRUST_PROXY) sur les 4 endpoints ; challenges multiples (5 derniers) acceptés ; .env.example ; tests tests/test_webauthn.py (+8) |
Logs : origin http://localhost:2020 rejetée + challenge mismatch au retry. Vérifié navigateur (authentificateur virtuel CDP) : register 200 + clé listée, clé de test retirée (admin de nouveau TOTP seul) ; pytest 1249 passed, ruff/mypy 0 |
| BUG-071 | Configuration « Configurations » inutilisable en mode mobile : sommaire masqué sans bouton d'accès, navigation par ancre sans JS, grilles 2 colonnes et rangées d'ajout qui débordent (≤768px) | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/index.html, frontend/js/config.js, frontend/js/i18n.js, frontend/style.css, frontend/locales/{fr,en}.json |
Mobile (≤768px) : ouvrir Configurations → aucun sommaire ni moyen d'atteindre une section ; champs « Clés IA » / jetons / webhooks débordent | index.html (+#config-hamburger .help-hamburger, config.toc_toggle FR/EN) ; config.js (toggle, scroll doux + actif + repli auto mobile, reset à l'ouverture) ; i18n.js (data-i18n-attr multi-paires ;) ; style.css (bloc mobile #config-modal : sommaire haut 46vh, grilles 1fr, add-rows wrap + !important, items wrap, 44px) ; tests tests/frontend/config-mobile.test.mjs (nouveau, 11) + CI ; E2E tests/e2e/config-mobile.spec.js (nouveau, 3/3 projet chromium-mobile, ignoré en desktop) |
pytest 1249 passed / 6 skipped, ruff 0, mypy 0, validate-imports 39 modules, unit 10/10, JSDOM ai 93/93 + sidebar 6/6 + mobile 35/35 + ai-keys 7/7 |
| BUG-072 | Visionneuse d'images : le plein écran et le panneau « Métadonnées » ne sont pas conservés lors de la navigation ←/→, et le panneau s'affiche sous la pellicule au lieu d'une barre latérale | 🟢 corrigé | P2 | 📱 frontend | IA | frontend/js/viewer.js, frontend/style.css |
Ouvrir une image, activer le plein écran (ou Métadonnées), puis naviguer avec les flèches précédent/suivant | État persistant _imageViewerState { lightbox, meta } + drapeau _imageViewerNavPending posé par go()/pellicule : renderFile ne réinitialise que hors navigation image→image. Panneau reconstruit dans .image-viewer-body (sidebar droite, border-left, width:280px; max-width:40%) ; la règle lightbox ne masque plus que la pellicule. Boutons image-btn-lightbox/image-btn-metadata (+ aria-pressed), Escape resynchronisé. Tests : tests/frontend/image-viewer.test.mjs (+2), E2E tests/e2e/image-viewer.spec.js (+1). |
Navigation → openFile → renderImageViewer recréait le conteneur : les états lightbox/metaPanel étaient perdus. Le panneau était rendu en bas (colonne) au lieu d'une sidebar droite |
| BUG-073 | Mobile : la barre de navigation fixe du bas masque le bas de tous les documents et pages affichés (les dernières lignes restent définitivement sous la barre) | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/style.css |
Mobile (≤768px) : ouvrir un document long, défiler jusqu'au bas → la fin du contenu passe sous la barre #mobile-toolbar et n'est jamais atteignable |
Clearance mobile retargetée de .main-layout (sélecteur mort, absent de index.html) vers .main-body (calc(64px + env(safe-area-inset-bottom, 0))) ; règle sœur morte .editor-modal.active ~ .main-layout supprimée ; reset body.reading-mode .main-body { padding-bottom: 0 } (barre masquée en mode lecture) ; body.np-active .content-area ramené à 76px (dégagement dock seul, géométrie totale inchangée). Tests : tests/frontend/mobile-toolbar.test.mjs (7, au CI), E2E tests/e2e/mobile-toolbar.spec.js (2, chromium-mobile, skip desktop) |
La règle de clearance du bloc mobile cible .main-layout, classe absente de index.html (vrai conteneur : .main-body) → sélecteur mort, aucun dégagement réservé |
| BUG-074 | [🟡 IMPORTANT] Assistant IA : le bloc d'étapes affiche « 1 step » sans titre alors que l'agent réalise plusieurs actions (compteur toujours à 1) | 🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | frontend/js/bookslm.js, backend/agent/loop.py |
Mode agent : demander une création multi-fichiers/dossiers → chaque message ne montre qu'« 1 étape ▶ » sans détail | backend/agent/loop.py + frontend/js/bookslm.js : compteur = actions (hors réflexions) + titre = 1re action dans le <summary> ; reprise de confirmation diffusée dans le même message (fusion des étapes). Tests : tests/frontend/ai.test.mjs (+3) |
Le résumé <summary> ne porte aucun titre ; chaque reprise de confirmation crée un nouveau message assistant qui ne contient qu'une action ; les réflexions gonflent le compteur |
| BUG-075 | [🟡 IMPORTANT] Assistant IA : chaque action mutatrice demande son propre « Appliquer » — aucun résumé des actions en attente ni approbation globale | 🟢 corrigé | P1 | ⚙️ backend + 📱 frontend | IA | backend/agent/loop.py, backend/bookslm_routes.py, frontend/js/bookslm.js |
Mode agent : demander une structure de répertoires multi-fichiers → valider une action après l'autre | backend/agent/loop.py (pending.actions, lot exécuté au resume) ; backend/bookslm_routes.py (confirm_all → ctx.confirmed) ; frontend/js/bookslm.js (carte multi-actions + « Tout approuver (N) »). Tests : tests/test_agent_loop.py, tests/test_bookslm.py, tests/frontend/ai.test.mjs |
La pause de confirmation ne capture que le premier appel mutateur du lot (les suivants sont deferred) ; carte unique sans liste ; nouveau confirm_all à ajouter pour autoriser la suite de l'exécution en une approbation |
| BUG-076 | [🟡 IMPORTANT] Assistant IA : après une action de l'agent, l'arborescence et le document ouvert ne sont pas rafraîchis dynamiquement | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/bookslm.js |
Mode agent : créer/supprimer un fichier ou dossier, modifier le document ouvert → l'UI ne bouge pas | frontend/js/bookslm.js : MUTATING_TOOLS/FILE_WRITE_TOOLS, refresh d'arborescence débouncé sur event tool, _notifyFileWritten étendu (xlsx/docx/csv/pdf). Tests : tests/frontend/ai.test.mjs, tests/frontend/editor-inline.test.mjs |
Aucun refresh explicite sur les événements tool mutateurs (repose uniquement sur le watcher SSE) ; _notifyFileWritten ignore les créations de documents (xlsx/docx/csv/pdf) |
| BUG-077 | [🟡 IMPORTANT] Assistant IA : aucun bouton « Stop » pour arrêter l'exécution de l'agent à tout moment | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/bookslm.js |
Mode agent : lancer une longue tâche → le bouton Envoyer est désactivé, impossible d'arrêter (seule la fermeture du panneau abort) | frontend/js/bookslm.js + frontend/style.css : bouton d'envoi → Stop (_syncSendButton/_stopGeneration/_markStopped), i18n ai.stop/ai.stopped. Tests : tests/frontend/ai.test.mjs (+2) |
_abortCtrl n'est déclenché que par close() ; aucun signal d'arrêt côté client pendant le stream |
| BUG-078 | [🟡 IMPORTANT] Fichiers de code : la coloration syntaxique (highlight.js) disparaît — les feuilles de thème sont basculées à partir de la clé de thème au lieu du mode | 🟢 corrigé | P1 | 📱 frontend | IA | frontend/js/themes.js, frontend/js/ui.js, tests/frontend/unit.test.mjs |
Ouvrir un fichier .py/.sh/.ps1/.yml : le code s'affiche en texte brut, sans couleurs |
frontend/js/themes.js : applyTheme bascule hljs-theme-dark/hljs-theme-light selon le mode (isDark). frontend/js/ui.js : initTheme/applyTheme résolvent le mode persisté (obsigate-theme-mode) au lieu de traiter la clé (defaut-obsigate) comme un mode. Test : unit.test.mjs (+1). |
Les deux feuilles étaient désactivées car defaut-obsigate !== "dark" et !== "light" ; résultat non déterministe selon l'ordre UI.initTheme() (clé) / Sync.init() → themes.initThemes() (mode). Vérifié Playwright : 5/5 chargements colorés (.py), sépia/contraste élevé sur la palette claire |
| BUG-083 | Job CI security rouge : le runner Gitea Act tronque le script pip-audit au premier # (citation de l'echo non fermée → unexpected EOF while looking for matching '"') |
🟢 corrigé | P0 | 📦 build | IA | .gitea/workflows/ci.yml, tests/test_ci_workflow.py (nouveau) |
CI job security, étape Pip-audit |
run: assaini (echo sans #, réf #87 en commentaire YAML) ; tests/test_ci_workflow.py (2 tests : aucun # dans le code des run:, upload.test.mjs verrouillé en étape JSDOM) ; vérifié : 56 passed (ci_workflow + e2e_harness + version), contre-preuve OK sur l'ancien ci.yml |
Seul run: du workflow contenant un # (see #87 dans l'echo). Les # des noms d'étapes (Bandit, Npm audit) sont inoffensifs (ces étapes passent). Correctif : echo sans #, réf #87 en commentaire YAML |
| BUG-082 | CI lint rouge : suites frontend à import statique jsdom exécutées dans l'étape racine où jsdom n'est jamais installé |
🟢 corrigé | P0 | 🧩 tests | IA | .gitea/workflows/ci.yml, tests/frontend/upload.test.mjs, tests/frontend/config-ai-keys.test.mjs, tests/test_ci_workflow.py |
CI job lint → ERR_MODULE_NOT_FOUND: jsdom (introduit par 7bee4a2, jamais vert depuis ; d'abord upload.test.mjs, puis config-ai-keys.test.mjs révélé après le 1er fix) |
upload.test.mjs + config-ai-keys.test.mjs déplacés dans l'étape JSDOM (les deux branches) ; garde-fou test_ci_workflow.py (aucun fichier racine à import statique jsdom + suites verrouillées en JSDOM) ; vérifié : étape racine verte + upload et config-ai-keys verts depuis tests/frontend/ |
jsdom ne vit que dans tests/frontend/node_modules (installé par l'étape JSDOM). Correctif : déplacer les suites concernées dans l'étape JSDOM |
| BUG-081 | GET /api/auth/mfa/status → 500 quand l'auth est désactivée (user None, AttributeError sur user.get) |
🔴 ouvert | P1 | ⚙️ backend | IA | backend/auth/router.py::mfa_status (l.821-831) |
Auth désactivée : curl http://127.0.0.1:2029/api/auth/mfa/status → 500 (reproduit live 2026-09-27, e2e-server.err.log l.116-183) |
— | require_auth laisse passer le pseudo-user anonymous, get_user(username) → None non gardé. Trouvé via les logs E2E pendant BUG-080 |
| BUG-080 | [🔴 BLOQUANT] E2E locaux bloqués toute la nuit : npm run test:e2e:ps ne termine jamais (serveurs orphelins sur le port 2029, npx playwright install sans --yes ni garde-fou, suite ~130 tests sans timeout global) |
🟢 corrigé | P0 | 🧩 tests | IA | scripts/run-e2e-local.ps1, scripts/run-e2e-local.sh, scripts/e2e-server.ps1, playwright.config.ts, tests/test_e2e_harness.py (nouveau) |
npm run test:e2e:ps |
run-e2e-local : npx --yes, skip install Chromium si présent (E2E_INSTALL_BROWSERS=1), timeouts E2E_TIMEOUT_SEC (900)/E2E_BROWSER_INSTALL_TIMEOUT_SEC (600, exit 124) ; playwright.config.ts : globalTimeout 15 min local / 30 min CI (E2E_GLOBAL_TIMEOUT_MS) ; e2e-server.ps1 : pidfile = vrai owner du port, stop tue l'arbre complet. Tests : tests/test_e2e_harness.py (8/8), cycle start/stop live (pidfile cohérent, port libéré) |
Constat 2026-09-27 : e2e-server.ps1 start OK (READY 12 s) mais run suivant pendu toute la nuit ; 2 python orphelins (PID 81180 parent + 81936 sur le port, pidfile périmé). Double processus systématique (parent .venv parqué + enfant qui sert — aussi sur flowdeck/3.13 : environnemental, sans impact après correctif). Trouvé au passage : BUG-081 (/api/auth/mfa/status → 500 auth désactivée) |
| BUG-079 | GET /api/diagnostics → 500 « dictionary changed size during iteration » (stats d'index) |
🟢 corrigé | P1 | ⚙️ backend | IA | backend/main.py |
Charger la page de diagnostic pendant une indexation : GET /api/diagnostics → 500 |
backend/main.py (api_diagnostics) : snapshot avant itération — list(index.items()) et inv.word_index.copy() (copie C atomique sous le GIL) ; test de non-régression tests/test_api_main.py::TestConfig::test_diagnostics_concurrent_index_writes |
Le handler itérait les dicts en direct alors que l'indexeur les modifiait depuis un autre thread (rebuild initial dans _search_executor, hooks incrémentaux add_document/remove_document) → RuntimeError dans le générateur → 500. Test déterministe (RaceDict fait grossir le dict en cours d'itération) : échoue sans le correctif, passe avec. Vérifié : pytest 1305 passed / 6 skipped, ruff 0, mypy 0 |
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 ✓, 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) |
| 2026-09-17 | BUG-060 | Correction | frontend/js/viewer.js, .gitea/workflows/ci.yml, tests/frontend/pdf-viewer.test.mjs (nouveau), tests/e2e/pdf-viewer.spec.js (nouveau), test_vault/sample-pdf.pdf (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-060 : l'ouverture d'un PDF n'affichait aucune page (barre d'outils « PDF — N pages » présente, corps vide). Cause : la CSP durcie en BUG-034 pose object-src 'none' — directive qui gouverne <embed>/<object> — alors que le viewer rendait le PDF via <embed type="application/pdf"> : le lecteur natif était bloqué. Correctif : rendu dans une <iframe> (autorisée par frame-src 'self', le stream /api/file/{vault}/pdf/stream étant same-origin) ; object-src 'none' conservé. Tests : pdf-viewer.test.mjs 6/6 (statique : pas d'<embed>, CSP frame-src 'self', iframe pleine hauteur), pdf-viewer.spec.js (E2E : iframe + stream application/pdf 200/206 + zéro violation CSP ; échoue bien avec l'ancien <embed>). Vérifié : pytest 1184 passed / 6 skipped, frontend 14 suites JSDOM vertes, validate-imports 38 modules, ruff/mypy 0. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-035, BUG-036, BUG-037, BUG-038, BUG-039, BUG-040 | Correction | backend/secret_redactor.py, backend/collab.py, backend/auth/{middleware,password,router}.py, backend/indexer.py, backend/main.py, tests/test_api_main.py, tests/test_auth.py, tests/test_auth_api.py, tests/test_collab.py, tests/test_pdf.py, CHANGELOG.md, docs/ISSUES_TODOLIST.md | Lot de 6 bugs mineurs (P2/P3) : BUG-035 masquage hex conditionné au contexte (git/SHA épargnés) ; BUG-036 jeton WebSocket cookie-only (plus de ?token=) + plafond de trame 16 Mio ; BUG-037 garde-fou au démarrage (refus d'un bind public sans auth sauf OBSIGATE_ALLOW_INSECURE=true) ; BUG-038 Argon2 recalibré 19 Mio/t=2/p=1 ; BUG-039 login uniforme 401 (fini 429/403 distinctifs) ; BUG-040 extraction PDF différée via enrich_pdf_texts(). Vérifié : pytest 1204 passed / 6 skipped, ruff 0, mypy 0 (77 fichiers), frontend validate-imports 38 modules + unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-061, BUG-062, BUG-063 | Correction | frontend/style.css, frontend/js/viewer.js, tests/frontend/ai.test.mjs, tests/frontend/pdf-viewer.test.mjs, tests/e2e/pdf-viewer.spec.js, test_vault/sample-pdf-toc.pdf (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | Viewer PDF & assistant IA : BUG-061 le bouton plein écran du panneau assistant l'emportait mal sur la largeur inline (redimensionnement/persistée) → width: 100vw !important ; BUG-062 le plafond de lecture 1200 px s'appliquait au PDF quand la navigation était masquée → :has(.pdf-viewer-container) en max-width:none ; BUG-063 la TOC PDF ne naviguait pas (contentWindow = about:blank) → navigatePdfToPage() recharge l'iframe avec #page=N, liens data-page sans onclick inline. Vérifié : ai.test.mjs 93/93, pdf-viewer.test.mjs 8/8, validate-imports 38 modules (311 exports), unit 9/9, E2E pdf-viewer.spec.js 3/3, et Playwright sur l'instance de test (plein écran 640→1400 px, src → #page=3, max-width:none). | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-063 (complément) | Correction | frontend/js/viewer.js, tests/frontend/pdf-viewer.test.mjs, tests/e2e/pdf-viewer.spec.js, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-063 non résolu au premier correctif : définir iframe.src = base + '#page=N' ne change que le fragment → navigation same-document que le lecteur PDF natif ignore. Diagnostic en Chrome headful (comparaison de captures) : fragment présent au chargement = OK ; changement de fragment après chargement = aucun effet ; changement de query + fragment = OK. navigatePdfToPage() ajoute donc un paramètre de query horodaté (&_pdfpage=<ts>#page=N) pour forcer un vrai rechargement. Vérifié via l'UI de l'app (Chrome headful) : page 1 → page 8 → retour page 1 (hash de capture identique au retour). Tests : pdf-viewer.test.mjs 8/8, E2E pdf-viewer.spec.js 3/3. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-064 | Correction | frontend/excalidraw-editor.html, backend/main.py, tests/frontend/excalidraw-viewer.test.mjs, tests/test_security_hardening.py, tests/e2e/excalidraw.spec.js, test_vault/diagram-app-export.excalidraw (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-064 : aucun diagramme Excalidraw ne s'affichait (canvas vide). Diagnostic navigateur : .excalidraw sans hauteur fixe → boucle de redimensionnement 525 → 56 181 → 33 554 432 px (2^25, plafond Excalidraw) ; canvas de 33 Mpx impossible à dessiner → scène blanche. Cause 1 : la feuille de style @excalidraw/excalidraw n'était jamais chargée (seuls 18 règles CSS présentes, toutes ObsiGate) — l'éditeur était non stylisé. Correctif : <link rel="stylesheet" href="https://esm.sh/@excalidraw/[email protected]/dist/prod/index.css"> + https://esm.sh ajouté à style-src de la CSP. Cause 2 : appState.collaborators (Map sérialisée en objet JSON par l'app/plugin) faisait planter Excalidraw 0.18 (collaborators.forEach is not a function) ; sanitizeAppState() reconvertit en Map et écarte la géométrie de viewport importée (width/height/offsetLeft/offsetTop). Vérifié Playwright sur l'instance de test (port 2020) : hauteur canvas 525 px, rectangle + losange affichés, UI stylisée, 0 pageerror. Tests : excalidraw-viewer.test.mjs 8/8 (dont 3 nouveaux), TestCspExcalidrawStylesheet (pytest), E2E (hauteur de canvas bornée). | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-064 (complément) | Correction | frontend/style.css, tests/frontend/excalidraw-viewer.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-064 (complément) : quand la barre de navigation gauche est masquée, le viewer Excalidraw restait borné à la colonne de lecture centrée de 1200 px. La règle .sidebar.hidden ~ .content-wrapper .content-area { max-width: 1200px } s'appliquait au viewer comme aux notes. Ajout de .content-area:has(iframe[src*="excalidraw-editor.html"]) en max-width: none; margin: 0 (même traitement que les viewers PDF/image, BUG-062). Vérifié Playwright (viewport 1400 px) : contenu 1115 → 1400 px, iframe 1035 → 1320 px, max-width calculé none. Test statique ajouté (excalidraw-viewer.test.mjs 9/9). | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | BUG-065, #78 (complément) | Correction + feature | frontend/js/excalidraw-viewer.js, frontend/js/utils.js, frontend/excalidraw-editor.html, tests/frontend/excalidraw-viewer.test.mjs, docs/features/excalidraw.md, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-065 : l'auto-save Excalidraw (débounce 2 s) déclenchait PUT save → SSE index_updated → reloadExternalWrite → openFile → recréation de l'iframe = refresh visible pendant le dessin. Auto-save retirée (excalidraw-viewer.js : plus de requestSave/saveTimer), sauvegarde explicite (bouton 💾 / Ctrl+S) ; reloadExternalWrite (utils.js) court-circuite le re-rendu si un iframe Excalidraw est ouvert sur ce fichier (attributs data-excalidraw-vault/data-excalidraw-path) ; le badge « Modified » suit désormais une signature des éléments (id:versionNonce) au lieu de tout onChange — resize/zoom/plein écran ne marquent plus le fichier modifié. #78 (complément) : bouton plein écran #btn-fullscreen dans la barre d'outils de l'éditeur (requestFullscreen sur le document de l'iframe) + iframe créée avec allow="fullscreen" allowfullscreen. Vérifié Playwright : bascule plein écran OK (document.fullscreenElement true→false), badge non modifié après bascule ; tests statiques excalidraw-viewer.test.mjs 12/12, validate-imports 38 modules, unit 9/9. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-17 | #78 (complément) | UI | frontend/excalidraw-editor.html, docs/features/excalidraw.md, CHANGELOG.md | #78 (complément) : la barre d'outils de l'éditeur Excalidraw passe en colonne d'icônes (34×34 px, SVG seuls), collée au bord droit (right: 0 ; top: 45% ; empilement vertical), avec title/aria-label. L'icône du bouton Save est remplacée par une coche pendant 1,2 s après une sauvegarde réussie. Badge « Modifié » réduit à une pastille. Vérifié Playwright : bord droit au bord de l'iframe, haut 45 %, 4 boutons empilés ; bascule plein écran OK, cycle d'icône Save + PUT save observés. | 🟢 livré (en attente vérif utilisateur) |
| 2026-09-18 | BUG-066 | Correction | frontend/locales/fr.json, frontend/locales/en.json, tests/frontend/unit.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-066 : la table des matières de la page de configuration n'affichait aucune icône pour « Fichiers cachés » et « Partages publics ». Les libellés du sommaire proviennent de clés i18n (config.section_hidden, config.section_shares) distinctes des titres de section qui, eux, portaient déjà l'icône. Alignement : 🗂️ / 📤 en FR et EN. Test de non-régression : unit.test.mjs vérifie que toutes les entrées .help-nav-link du sommaire portent une icône dans les deux langues (17/17). Vérifié : unit.test.mjs 10/10, validate-imports 38 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-18 | #105, BUG-067 | Documentation + correction | frontend/index.html, frontend/js/config.js, frontend/style.css, frontend/locales/{fr,en}.json, backend/guide_export.py, backend/main.py, tests/test_guide.py, docs/features/guide-coverage-105.md, CHANGELOG.md, docs/ROADMAP.md, docs/ISSUES_TODOLIST.md | #105 : audit complet de couverture du Guide d'utilisation — 8 nouvelles sections (Architecture + diagramme Mermaid, API & intégrations, Diagrammes Mermaid & Excalidraw, Hors-ligne & synchronisation, Collaboration temps réel, Application desktop, Bibliothèque & signets, Multilingue) et compléments (recherche sémantique, MFA/WebAuthn, notifications push, exports HTML/ePub/ZIP, PDF, vue multi-panneaux, admin). Téléchargement du guide en Markdown et PDF (GET /api/guide/download?format=md|pdf, FR/EN, rendu par le moteur d'export existant). Guide plus large en desktop. BUG-067 : ancre morte #help-mobile-editor → section dédiée créée. | 🟢 corrigé (en attente vérif utilisateur)
| 2026-09-18 | #105 (ajustements) | Amélioration | frontend/index.html, frontend/js/config.js, frontend/sw.js, frontend/locales/{fr,en}.json, backend/guide_export.py, backend/pdf_export.py, Dockerfile, scripts/build_guide_diagrams.py, scripts/render_guide_diagram.mjs, scripts/guide_content.py, backend/assets/guide_diagrams/df7366a40db6a5a2.png, tests/test_guide.py, docs/features/guide-coverage-105.md, CHANGELOG.md | #105 (retour utilisateur) : 1) boutons de téléchargement du guide passés en icônes seules (tooltips i18n conservés) ; 2) le diagramme Mermaid de la section Architecture est désormais rendu en vraie image dans le PDF (pipeline de pré-rendu PNG Chromium+mermaid v11, PNG commité sous backend/assets/guide_diagrams/<sha1>.png, résolu par diagram_png_for() ; le Markdown garde le fenced mermaid) ; 3) emoji du PDF rendus en couleur au lieu de rectangles : fonts-noto-color-emoji ajouté au Dockerfile + "Noto Color Emoji" en fin de pile de polices PDF. Vérifié : pytest 1218 (test_guide ×13), ruff/mypy 0, validate-imports 38, unit 10/10 ; PDF live conteneur 2020 : 24 pages, 0 glyphes tofu, diagramme 3568x1174 embarqué. | 🟢 livré
| 2026-09-22 | BUG-068 | Correction | backend/auth/router.py, backend/requirements.txt, frontend/js/auth.js, frontend/style.css, frontend/locales/{fr,en}.json, tests/test_mfa.py, tests/frontend/mfa-settings.test.mjs (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-068 : section « 🔒 Sécurité du compte » finalisée. (1) Boutons hors thème : config-btn-primary/config-btn-danger n'existaient pas en CSS → définis depuis les variables du thème (+ états disabled). (2) QR invisible : l'image tierce était bloquée par la CSP (img-src 'self' data: blob:) et exposait le secret TOTP → QR SVG data: généré en local par le backend (qr_data_url, segno) avec repli saisie manuelle. (3) Codes de récupération perdus à la 1re activation WebAuthn → _showRecoveryCodes(codes, targetId) avec repli webauthn-flow-area. (4) Carte « Mot de passe » ajoutée (endpoint change-password existant, jusque-là sans UI) + échappement des libellés de clés WebAuthn. Vérifié : pytest 1241 passed / 6 skipped, ruff 0, mypy 0 (78 fichiers), mfa-settings.test.mjs 9/9, unit 10/10, validate-imports 39 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-069 | Correction | frontend/js/auth.js, frontend/locales/{fr,en}.json, tests/frontend/mfa-settings.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-069 : login 2FA bloqué sans erreur — après user+pwd corrects, showMfaChallenge cherchait .login-box (inexistant dans index.html, marquage réel #login-screen > .login-card) et faisait un return silencieux : page de login figée, aucune erreur. Correctif : montage dans .login-card (repli #login-screen) + erreur visible mfa.challenge_unavailable (FR/EN) si le point de montage manque. Reproduit au navigateur (Playwright, instance Docker obsigate-test, compte jetable avec TOTP) : avant → challenge jamais affiché ; après → challenge affiché, code erroné → erreur, code valide (verify 200) → app. Tests : mfa-settings.test.mjs 11/11 (+2 ancrage DOM), unit 10/10, validate-imports 39 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-070 | Correction | backend/auth/webauthn_mfa.py, backend/auth/router.py, .env.example, tests/test_webauthn.py, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-070 : activation WebAuthn rejetée en 400. (1) Défauts localhost sans port → resolve_relying_party() dérive rp_id/origines de la requête (config explicite prioritaire, forwarded sous TRUST_PROXY), appliqué aux endpoints register + login. (2) Challenge single-use → 5 derniers conservés, vérification contre le challenge de la cérémonie en cours. Vérifié au navigateur (authentificateur virtuel CDP, instance Docker) : register 200, clé listée, clé de test retirée. Tests : test_webauthn.py 19/19 (+8), suite complète 1249 passed / 6 skipped, ruff/mypy 0. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-071 | Correction | frontend/index.html, frontend/js/config.js, frontend/js/i18n.js, frontend/style.css, frontend/locales/{fr,en}.json, tests/frontend/config-mobile.test.mjs (nouveau), .gitea/workflows/ci.yml, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-071 : page « Configurations » inutilisable en mobile. (1) #config-nav masquée sous 768px sans toggle → hamburger #config-hamburger ajouté à l'en-tête (.help-hamburger, libellé config.toc_toggle FR/EN). (2) Ancres brutes sans JS → interception en config.js (scroll doux, lien actif, repli auto mobile, reset à l'ouverture). (3) Débordements 360px → bloc CSS mobile #config-modal (sommaire haut 46vh, grilles 1fr, add-rows wrap + largeurs inline neutralisées, items wrap, cibles 44px). data-i18n-attr multi-paires (;). Vérifié : config-mobile.test.mjs 11/11 (nouveau, au CI), pytest 1249 passed / 6 skipped, ruff/mypy 0, validate-imports 39 modules, unit 10/10, JSDOM ai 93/93 + ai-sidebar 6/6 + sidebar-filters 8/8 + mobile-editor 35/35 + config-ai-keys 7/7. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-071 (complément E2E) | Test | tests/e2e/config-mobile.spec.js (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-071 (complément E2E) : spec Playwright mobile (convention mobile-editor.spec.js : test.skip hors viewport ≤768px, donc inactive sur le projet chromium-desktop du CI). Vérifié en local sur l'instance de test (port 2029, auth désactivée) : hamburger → sommaire, sélection → scroll + actif + repli, 0 débordement horizontal à 393px (3/3 chromium-mobile, 3 ignorés en desktop) ; suite mobile-editor.spec.js intacte (3/3). | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-072 | Correction | frontend/js/viewer.js, frontend/style.css, tests/frontend/image-viewer.test.mjs, tests/e2e/image-viewer.spec.js, scripts/run-e2e-local.ps1 (nouveau), package.json, AGENTS.md, README.md, README.fr.md, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-072 : dans la visionneuse d'images (#108-D), le plein écran (lightbox) et le panneau « Métadonnées » étaient perdus dès qu'on changeait d'image avec ←/→ (ou la pellicule), car openFile → renderFile recrée entièrement renderImageViewer. (1) Persistance : état module _imageViewerState { lightbox, meta } restauré à chaque rendu ; un drapeau _imageViewerNavPending posé par go() et le clic de vignette indique à renderFile que le rendu suivant est une navigation image→image (pas de réinitialisation) — toute autre ouverture repart à zéro. (2) Panneau latéral : .image-meta-panel déplacé dans un nouveau .image-viewer-body en flex row, à droite de .image-stage (border-left, width:280px; max-width:40%, défilement vertical) au lieu d'une bande sous la pellicule ; la règle lightbox ne masque plus que la pellicule. Boutons stables image-btn-lightbox/image-btn-metadata + aria-pressed, Escape resynchronise l'état. Tests statiques image-viewer.test.mjs (+2) et E2E Playwright (+1). Diagnostic E2E : npm run test:e2e bloquait car bash résout vers WSL (HS, Ubuntu Stopped, HCS_E_CONNECTION_TIMEOUT) et git-bash est bloqué par App Control → lanceur PowerShell ajouté. Vérifié : image-viewer.spec.js 4/4, suite chromium-desktop complète 103 passed / 6 skipped (10,3 min) via scripts/run-e2e-local.ps1, image-viewer.test.mjs 12/12, unit 10/10, validate-imports 40 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-23 | BUG-073 | Correction | frontend/style.css, tests/frontend/mobile-toolbar.test.mjs (nouveau), tests/e2e/mobile-toolbar.spec.js (nouveau), .gitea/workflows/ci.yml, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-073 : en mobile (≤768px), la barre fixe #mobile-toolbar (64px + safe-area) recouvrait le bas de tous les documents/pages — fin de contenu inaccessible. (1) Cause : la règle de clearance du bloc @media (max-width: 768px) ciblait .main-layout, classe absente de index.html (vrai conteneur : .main-body) → sélecteur mort, zéro dégagement ; règle sœur morte .editor-modal.active ~ .main-layout { padding-bottom: 0 } supprimée (overlay plein écran / nécessaire en édition inline). (2) Correctif : .main-body { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0)) }, reset body.reading-mode .main-body { padding-bottom: 0 } (barre masquée en mode lecture), body.np-active .content-area ramené de calc(64px+safe+76px) à 76px (dégagement dock seul — géométrie totale identique, pas de double comptage avec .main-body). Tests : mobile-toolbar.test.mjs (7 statiques, ajouté au CI), E2E mobile-toolbar.spec.js (géométrie + scroll fin de ANALYSE_REVIEW.md, skip hors viewport ≤768). Vérifié : mobile-toolbar 7/7, JSDOM 14 suites 0 échec, E2E chromium-mobile BUG-073 2/2 + régressions mobile-editor/config-mobile 6/6, suite chromium-desktop complète 106 passed / 9 skipped (11,4 min), pytest 1302 passed / 6 skipped, ruff/mypy 0, validate-imports 40 modules, unit 11/11. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-24 | #114 | Feature | frontend/index.html, frontend/js/config.js, frontend/style.css, frontend/locales/{fr,en}.json, tests/frontend/config-mobile.test.mjs, tests/e2e/config-mobile.spec.js, docs/features/settings-mobile-114.md (nouvelle), docs/ROADMAP.md, CHANGELOG.md, docs/ISSUES_TODOLIST.md | #114 — Configuration, refonte mobile-responsive (≤768px). (1) Drawer sommaire : #config-nav en panneau position: fixed (min(320px, 88vw), z-index 40) sous backdrop #config-modal.config-toc-open::before (z-index 35) ; bouton #config-toc-close ; backdrop/Échap ferment le drawer d'abord puis la modale ; _setConfigNav bascule la classe + conserve le display inline de reset. (2) Modale plein écran : 100dvh + padding: 0, border-radius: 0. (3) Tactile : boutons/liens ≥44px, inputs/selects 16px + min-height: 44px (anti-zoom iOS, scopé #config-modal), rangée .config-actions-row sticky column + safe-area, formulaires 1 colonne, MFA 1 colonne + code full-width, wrap webhook/token/share/diag/avatar/webauthn. (4) Dettes HTML/i18n : .config-actions-row replacée dans #cfg-backend-settings (</section> orphelin supprimé), id dupliqué cfg-partages-publics retiré du <h2>, #plugins-settings-container supprimé, doublons .config-btn-add + règle morte .mfa-recovery-input purgés, #mt-explorer → data-i18n="settings.explorer" ; i18n : clés mortes settings.{backend,backend_hint,restart_badge,save,plugins} supprimées, settings.explorer + config.toc_close ajoutées, settings.tabs FR = « Onglets ». Vérifié : config-mobile.test.mjs 27/27 (au CI), unit 11/11, validate-imports 40 modules, pytest 1302 passed / 6 skipped, ruff/mypy 0, E2E chromium-mobile 5/5. | ✅ livré (en attente vérif utilisateur) |
| 2026-09-24 | BUG-074 → BUG-077 | Correction | backend/agent/loop.py, backend/bookslm_routes.py, frontend/js/bookslm.js, frontend/style.css, frontend/index.html, frontend/locales/{fr,en}.json, frontend/sw.js, tests/test_agent_loop.py, tests/test_bookslm.py, tests/frontend/ai.test.mjs, tests/frontend/editor-inline.test.mjs, CHANGELOG.md, docs/ISSUES_TODOLIST.md | Lot assistant IA (mode agent) : (BUG-075) confirmation par lot — pending.actions regroupe toutes les mutations d'un tour LLM, un unique bouton « Tout approuver (N) » envoie confirm_all (ToolContext.confirmed) et n'interrompt plus à chaque action ; les lectures du lot s'exécutent aussitôt. (BUG-074) résumé du bloc d'étapes avec titre de la 1re action + compteur limité aux actions, reprise diffusée dans le même message (fini le « 1 étape » fragmenté). (BUG-076) refresh de l'arborescence débouncé sur les events tool mutateurs + _notifyFileWritten étendu aux documents (xlsx/docx/csv/pdf). (BUG-077) le bouton d'envoi devient « Stop » pendant le stream (abort SSE, tâche serveur annulée à la déconnexion, marqueur « Exécution arrêtée. »). Guide/i18n FR/EN + ai.stop/ai.stopped/ai.confirm_actions/ai.action_apply_all ; SW_VERSION v25. Vérifié : pytest 1304 passed / 6 skipped, ruff/mypy 0, validate-imports 40 modules, unit 11/11, ai 100/100, editor-inline 44/44, mobile-editor 35/35, ai-sidebar 6/6, forge 32/32, pane-manager 9/9, sw 8/8. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-24 | #115, #117, BUG-078 | Feature + correction | frontend/js/themes.js, frontend/js/ui.js, frontend/js/viewer.js, frontend/js/config.js, frontend/index.html, frontend/style.css, frontend/popout.html, frontend/locales/{fr,en}.json, frontend/icons/avatar/* (nouveau), tests/frontend/unit.test.mjs, tests/frontend/toolbar-order.test.mjs, tests/frontend/settings-order-avatar.test.mjs, docs/features/viewer-toolbar-highlight-avatars.md (nouvelle), docs/ROADMAP.md, CHANGELOG.md | #115 barre d'outils de lecture épinglée : viewer.js/popout.html sortent .file-actions de .file-header dans un .file-toolbar enfant direct de .content-area (position: sticky; top: 0), masqué en mode lecture. BUG-078 coloration syntaxique : le basculement des feuilles highlight.js suit le mode (themes.applyTheme + ui.initTheme/applyTheme lisent obsigate-theme-mode) au lieu de la clé de thème qui désactivait les deux feuilles. #117 avatars prédéfinis : galerie de 12 images (frontend/icons/avatar/) dans #cfg-profile, clic → recadrage 256 px (pipeline import) + PATCH /api/auth/me, avatars actifs surlignés (obsigate-avatar-preset), import personnalisé et suppression conservés. Vérifié : Playwright (coloration 5/5 déterministe, toolbar épinglée à barTop constant au défilement), unit.test.mjs 12/12, toolbar-order 13/13, settings-order-avatar 12/12, JSDOM editor-inline/pane-manager/mobile-editor/image-viewer/pdf-viewer/config-mobile/media-viewer/excalidraw verts, pytest 1304 passed / 6 skipped, ruff/mypy 0, validate-imports 40 modules. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-24 | BUG-079 | Correction | backend/main.py, tests/test_api_main.py, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-079 : GET /api/diagnostics renvoyait 500 « dictionary changed size during iteration ». Le handler itérait inv.word_index.values() et index.items() en direct alors que l'indexeur les modifiait depuis un autre thread (rebuild initial dans _search_executor, hooks incrémentaux add_document/remove_document) → RuntimeError dans le générateur. Correctif : snapshot avant itération (list(index.items()), inv.word_index.copy()) — copie C atomique sous le GIL, pas de verrou ajouté. Test de non-régression déterministe (RaceDict fait grossir le dict pendant l'itération ; échoue sans le correctif, passe avec). Vérifié : pytest 1305 passed / 6 skipped, ruff 0, mypy 0 (80 fichiers), validate-imports 40 modules, unit 12/12. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-27 | BUG-080, BUG-081 | Correction + enregistrement | scripts/run-e2e-local.ps1, scripts/run-e2e-local.sh, scripts/e2e-server.ps1, playwright.config.ts, tests/test_e2e_harness.py (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-080 : run E2E local pendu toute la nuit → harnais anti-blocage : npx --yes (plus de prompt interactif), install Chromium sautée si présent (E2E_INSTALL_BROWSERS=1), timeouts E2E_TIMEOUT_SEC (900)/E2E_BROWSER_INSTALL_TIMEOUT_SEC (600, exit 124), globalTimeout Playwright (15 min local / 30 min CI, E2E_GLOBAL_TIMEOUT_MS), pidfile resynchronisé sur le vrai owner du port + stop qui tue l'arbre complet (orphelins 81180/81936 nettoyés, port 2029 libéré). Diagnostic : double processus systématique (parent .venv parqué + enfant qui sert — environnemental, aussi sur flowdeck/3.13). BUG-081 (ouvert, non traité) : GET /api/auth/mfa/status → 500 auth désactivée (user None, router.py:827, reproduit live). Vérifié : test_e2e_harness.py 8/8, cycle start/stop live (pidfile cohérent, port libéré). | 🟢 corrigé (en attente vérif utilisateur) ; BUG-081 🔴 ouvert |
| 2026-09-27 | BUG-082 | Correction CI | .gitea/workflows/ci.yml, tests/test_ci_workflow.py, CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-082 : lint rouge (ERR_MODULE_NOT_FOUND: jsdom, rouge depuis 7bee4a2) — les fichiers de l'étape frontend racine à import statique jsdom (upload.test.mjs, puis config-ai-keys.test.mjs révélé par le CI après le 1er fix), alors que jsdom n'est installé que dans tests/frontend/node_modules (étape JSDOM). Les deux déplacés dans l'étape JSDOM (les deux branches) ; garde-fou test_ci_workflow.py généralisé (aucun fichier racine à import statique jsdom + suites verrouillées en JSDOM, contre-preuve OK). Vérifié : étape racine verte (11 suites) + upload et config-ai-keys verts depuis tests/frontend/. | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-09-27 | BUG-083 | Correction CI | .gitea/workflows/ci.yml, tests/test_ci_workflow.py (nouveau), CHANGELOG.md, docs/ISSUES_TODOLIST.md | BUG-083 : job security rouge — le runner Gitea Act tronque naïvement au premier # (même entre guillemets) : echo "... see #87)" devenait une citation non fermée (unexpected EOF while looking for matching '"'", /var/run/act/workflow/4 ligne 2). Seul run: du workflow avec un # (les # des noms d'étapes Bandit/Npm audit sont inoffensifs, ces étapes passent). Correctif : echo sans # (réf #87 en commentaire YAML). Garde-fou test_ci_workflow.py (aucun # dans le code des run:, upload.test.mjs verrouillé en étape JSDOM — BUG-082) + contre-preuve sur l'ancien ci.yml. Vérifié : 56 passed. | 🟢 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é :
- Exécuter les tests du projet :
- Backend :
python -m pytest tests/ -v(oupytest) - S'il existe des tests ciblés sur la zone modifiée, les lancer en priorité.
- Backend :
- Vérifier le lancement du module concerné (import sans erreur, endpoint répond).
- Relire sa propre diff pour éviter les régressions.
- Mettre la colonne « Correctif / Commit » à jour puis le statut en
🟢 corrigé. - Si un test échoue ou que la correction est incomplète, rester en
🟠 en courset 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 coursavant 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
- Guide de contribution : CONTRIBUTING.md (conventions de code)
- Structure du projet : README.fr.md
- Tests : répertoire
tests/— voir CONTRIBUTING - Analyse/décisions complémentaires : IMPLEMENTATION_PLAN.md, ROADMAP.md