BUG-003: annotations de types, gardes None sur get_user(), PdfReader: Any et import PROVIDERS manquant (bug latent main.py:4523). Etape mypy du CI rendue bloquante (etait advisory).
BUG-004: lien README.md -> docs/CONTRIBUTING.md corrige (+ DELIVERY_WORKFLOW.md), arbre projet mis a jour, parite README.fr.md.
Verifie: mypy 0 erreur, ruff OK, pytest 728 passed / 5 skipped, frontend OK, liens md OK.
- ROADMAP: ne garde que le travail a venir + index compact du complete (995 -> ~155 lignes); detail deplace vers docs/features/ et docs/archive/
- docs/features/: fiches detaillees #74, #75, #76, #77, #78, #79
- docs/archive/COMPLETED_v1-v2.md: detail des items courts livres
- CHANGELOG: alignement sur les tags (2.0.0 date, 2.2.0/2.2.1 ajoutes, Unreleased = travail #79 post-2.2.1)
- AGENTS.md + docs/DELIVERY_WORKFLOW.md: methode de livraison unique (Definition of Done) referencee par ROADMAP, CONTRIBUTING, ISSUES_TODOLIST
Le .gitignore exclut _*.py, donc backend/tools/__init__.py n'etait pas versionne et
la CI echouait a l'import (unknown location). Remplacement par une facade api.py
qui enregistre les outils et reexporte l'API publique. Tests adaptes.
- Persistance ai_default_provider / ai_default_models dans data/config.json
- Lecture + rechargement a chaud dans backend/ai.py (get_default_provider, reload_ai_config)
- UI: selecteurs Fournisseur/Modele par defaut dans la section Cles API IA (i18n FR/EN)
- docs/AI_ARCHITECTURE_GUIDE.md: architecture cible, catalogue d'outils, decisions MCP
Sur mobile, le textarea et le bouton envoyer de l'assistant AI etaient masques par la barre de navigation fixe en bas. Ajout d'un padding-bottom au panneau bookslm pour degager la barre.
Sur mobile, la pastille flottante etait masquee par la barre d'outils.
- Masque .ai-fab sur mobile
- Ajoute un bouton rond sureleve #mt-ai au centre de la barre (5 items)
- Extrait openAssistant() dans ai-fab.js (reutilise par la FAB et le bouton mobile)
- mobile-toolbar.js : branche le bouton sur openAssistant()
Les 3 affichages (badge header, A propos, pied de l'aide) lisaient la version bakee dans l'image Docker, qui retombait sur l'ARG VERSION=2.1.0 car build.sh/build.ps1 ne transmettaient pas le tag git.
- Dockerfile / docker-compose.yml : defaut porte a 2.2.0
- build.sh : export VERSION pour substitution par docker compose
- build.ps1 : version derivee de git describe puis exportee
- CHANGELOG / ROADMAP alignes sur 2.2.0
Contexte:
- Repertoire (mode historique BooksLM, via menu contextuel dossier)
- Documents (bouton flottant quand des documents sont ouverts)
- General (bouton flottant sans document: aide app + creation de fichiers)
- Le contexte est affiche dans le header; suggestions adaptees au mode
- Blocs obsigate-action rendus en carte avec bouton Appliquer (pas d'execution auto)
Backend:
- /api/ai/bookslm/context et /chat acceptent mode + context_files
- collect_files_context(), empty_context(), build_general_system_prompt()
- mode documents degrade vers general si aucun fichier lisible
Corrige:
- bouton flottant passait le chemin d'un fichier comme repertoire -> erreur
'Aucun fichier markdown trouve dans ce dossier'
- panneau precedent masque (localStorage) ne se rouvrait plus
- selecteur fournisseur/modele deplace sur une ligne dediee (header actions visibles)
- Entree envoie, Ctrl+Entree insere un saut de ligne
Tests: test_bookslm.py (+13) et tests/frontend/ai.test.mjs (+8)
Les fixtures copiees via docker cp appartiennent a root ; l'app tourne en UID 1000 et os.access(W_OK) renvoyait False -> POST /api/file 403 'Vault is read-only' -> le test E2E de creation .excalidraw (C8) echouait. Ajout d'un chmod -R a+rwX apres le health check.
BUG-001 (PDF stream 500) : le nom Unicode du fichier etait injecte brut
dans Content-Disposition -> en-tete HTTP invalide -> 500. Nouveau helper
_content_disposition (RFC 5987 : filename ASCII + filename*=UTF-8'').
Applique aux 2 branches /pdf/stream. Test: tests/test_pdf_stream.py.
BUG-002 (Excalidraw 'Loading...' infini) : deux causes.
1) L'import esm.sh avec ?alias=react:... etait propage a chaque dep
transitive; esm.sh renvoie 408 sur les builds a gamme semver (jotai@>=2.9.2)
-> import entier echoue. On retire l'alias et on utilise React 19 coherent
(spec a gamme + target identique a celle d'Excalidraw) -> plus de 408.
2) L'editeur passait la prop legacy 'excalidrawRef', inoperante en 0.18
(la prop reelle est 'excalidrawAPI') -> l'API n'etait jamais recue,
le 'Loading' ne se masquait pas, save/export morts.
Verifie : 534 tests backend verts (dont 3 nouveaux PDF) + E2E navigateur
(loading masque + cycle requestSave->save OK).
Corrige l'incoherence d'affichage (page principale 0.0.0-dev vs a propos
v2.0.0-dev) et unifie la gestion de version sur une seule source de verite.
Backend:
- version.py: get_version() retourne toujours le tag x.y.z propre du dernier
tag (plus de 0.0.0-dev / -N-gHASH dans l'UI). Ajout get_git_describe() et
get_git_commit() pour le detail.
- main.py: /api/health expose git_describe + git_commit (nouveaux champs).
Frontend:
- modale A propos (populateAbout): version + commit lus depuis /api/health,
suppression du hardcode v2.0.0-dev dans index.html.
- section config A propos (loadAbout): lit health.version, plus de state.
APP_VERSION obsolete.
- badge header: fetch /api/health (fallback neutre '...').
- state.js: suppression de APP_VERSION (1.5.0) devenu mort; imports nettoyes.
Script:
- scripts/bump_version.sh: incrementation SemVer selon les commits
(breaking->major, feat->minor, sinon patch), creer/pousser le tag vX.Y.Z.
Tests: 507 passed, frontend validators OK.
Navigation/ergonomie:
- barre sticky de pilules (Stats, Audit, Backups, Utilisateurs) sous le header
- scrollspy (IntersectionObserver) + scroll fluide au clic dans admin.js
- ancres + scroll-margin-top sur chaque section, layout responsive <600px
Themes:
- admin.html importe et appelle initThemes() (themes.js) au boot: la page
reprend le theme/mode sauvegarde dans l'app (localStorage meme-origine)
- script anti-FOUC en <head> applique vite le mode avant le CSS
admin.html charge /static/style.css dont body{overflow:hidden;height:100vh}
(congu pour la SPA avec scroll interne). Sur la page admin autonome, ce bloc
desactivait le scroll de la page.
- frontend/admin.html: body.admin-page force overflow:auto + height:auto pour
surcharger le CSS global (specificite body.admin-page > body).
Le menu Admin redirigeait vers /admin.html mais aucune route backend ne le
servait : le catch-all SPA /{full_path:path} renvoyait index.html, d'où le
retour à la page principale.
- backend/main.py: route GET /admin.html (gate require_admin) déclarée avant
le catch-all SPA + correction des imports JS /frontend/js -> /static/js
- frontend/admin.html: chemins d'assets alignés sur le mount /static
- tests/test_admin.py: 3 tests de régression (page servie admin, refus 401/403)
- backend: ajout DELETE /api/config/ai-keys/{provider_env} pour supprimer une clé
- frontend: badge ✓ Configuré / Non configuré sur chaque ligne provider
- frontend: bouton × Supprimer avec confirmation sur les providers configurés
- testAIKeys met à jour les badges selon les résultats du test
1. **fix(xiaomi): la clé Xiaomi échouait avec 'Name or service not known'**
- URL précédente api.xiaomi.com n'existe pas (DNS fail)
- Vraie URL: https://api.xiaomimimo.com/v1/models
- Xiaomi MiMo utilise un header custom 'api-key:' (PAS 'Authorization: Bearer')
- Modèles par défaut mis à jour: mimo-v2.5-pro, mimo-v2.5, mimo-v2.5-asr, etc.
- Le chat dans ai.py supporte maintenant un header custom via auth_header_name
2. **fix(admin): EventSource 503 → fallback polling**
- Ajout d'un fallback: si EventSource ne reçoit jamais le premier event
(cookie expiré, réseau bloqué, proxy timeout), on bascule sur du polling
/api/admin/stats toutes les 5s. Le UI continue de se mettre à jour.
- Cleanup correct du timer dans disconnectSSE
3. **fix(test_ai_models)**: 2 nouveaux tests de régression
- test_xiaomi_uses_api_key_header_not_bearer: vérifie URL + header
- test_xiaomi_test_endpoint_uses_real_url: vérifie /api/config/ai-keys/test
- Patche backend.ai ET backend.main (import local)
- Header case-insensitive (urllib normalise à 'Api-key', HTTP est insensible)
Vérifié :
- pytest : 504 passed (502 + 2 nouveaux Xiaomi)
- frontend unit : 7 passed
- validate-imports : 30 modules / 204 exports
- pane-manager JSDOM : 9/9
- ruff check backend/ : All checks passed
Trois bugs corrigés + une amélioration demandée :
1. **fix(admin): /admin.html redirigeait toujours vers /**
- admin.js _gateAdmin() lisait /api/auth/status qui ne contient PAS le rôle user
- Remplacé par /api/auth/me (retourne username, role, vaults)
- Le code distingue maintenant le cas 'auth désactivé' (admin anonyme) du
cas 'auth requise non admin' (affiche écran forbidden)
2. **fix(ai): les modèles Nvidia/Xiaomi ne se chargeaient pas dans les dropdowns**
- Xiaomi : l'endpoint public /v1/models est instable, échec réseau fréquent
- Toutes les erreurs réseau/d'API renvoyaient models=[] → dropdown vide
- Ajout d'un fallback curé : _FALLBACK_MODELS dict avec 4-8 modèles populaires
par provider, TOUJOURS retourné si la clé manque OU si l'API distante échoue
- Le frontend voit désormais 'fallback' vs 'live' comme hint pour l'utilisateur
- Gemini parsing : strip du préfixe 'models/' retourné par l'API Gemini
- 7 nouveaux tests pytest pour _FALLBACK_MODELS + endpoint /api/config/ai-models
3. **feat(ai): picker provider/model dans la toolbar AI + BooksLM**
- Plusieurs providers peuvent maintenant être activés simultanément
- Sélection provider+model par section (Forge, BooksLM, etc.) via dropdown
- État persisté en localStorage (le choix suit l'utilisateur entre sections)
- Chaque appel AI passe maintenant {provider, model} au backend
- ai.js : aiAction() lit le picker et l'injecte dans le body
- bookslm.js : envoie provider+model à /api/ai/bookslm/chat
- ai_routes.py + bookslm_routes.py : AIRequest et BooksLMChatRequest
acceptent provider+model, avec save/restore du modèle original
pour ne pas affecter les autres requêtes concurrentes
- 7 nouvelles clés i18n (ai.provider, ai.model, ai.model_loading, etc.)
- 1 nouveau test bookslm vérifie que le schema accepte provider+model
- validate-imports.mjs : fix faux positif sur 'export { X as Y }'
Vérifié :
- pytest : 502 passed, 5 skipped (494 baseline + 7 AI models + 1 BooksLM)
- frontend unit : 7 passed
- validate-imports : 30 modules / 204 exports / 0 erreur
- pane-manager JSDOM : 9/9
- ruff check backend/ : All checks passed
Le job 'CI / build' échouait avec :
failed to fetch anonymous token: dial tcp: lookup auth.docker.io on
127.0.0.11:53: server misbehaving
Le résolveur DNS embarqué du daemon Docker sur le runner GitHub Actions
est bogué de manière intermittente. Cette PR ajoute :
1. Étape 'Configure DNS' qui configure /etc/docker/daemon.json avec
les DNS publics Google/Cloudflare/Quad9 (8.8.8.8, 1.1.1.1, 9.9.9.9)
puis redémarre le daemon Docker
2. Retry jusqu'à 3 fois (10s, 20s d'attente) sur la commande
docker build pour absorber les flakes réseau ponctuels
Aucun changement au Dockerfile lui-même — il fonctionne en local
et le bug est purement infrastructure.