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.
Fix final pour atteindre 'All checks passed!' sur ruff check backend/.
Le commit précédent avait raté ce fichier (modifié par --unsafe-fixes
mais non inclus dans le commit).
- bookslm.py: PERF402 — list(_cache) au lieu de la boucle d'append
- bookslm_routes.py: SIM102 — combine les conditions imbriquées (via --unsafe-fixes)
- export.py: DTZ005 — datetime.now(tz=timezone.utc) au lieu de naive now()
- watcher.py: SIM102 — combine les conditions imbriquées
Résultat : ruff check backend/ → All checks passed!
Vérifié : pytest full suite reste 492 passed, 5 skipped, 0 failed.
Réduit les erreurs ruff de 11 à 5 (toutes pré-existantes non auto-fixables) :
- F401 imports inutilisés dans auth/mfa.py
- I001 blocs d'imports non triés dans auth/router.py + main.py + pdf_reader.py
Les 5 restantes sont dans bookslm/export/watcher (code pré-existant, hors scope).
Vérifié : pytest full suite reste 492 passed, 5 skipped, 0 failed.
- tests/test_pdf.py : 13 tests (100% verts) couvrant :
- extract_pdf_text/metadata/toc avec edge cases (corrupt, missing, truncation)
- .pdf dans SUPPORTED_EXTENSIONS
- _scan_vault() extrait le texte des PDFs (vérifié avec fixture reportlab)
- parseur du filtre ext:pdf
- backend/pdf_reader.py : fix NameError quand pymupdf est installé
(PdfReader n'était déclaré que dans la branche except ImportError)
- backend/requirements-test.txt : reportlab pour générer des PDFs de test (devDep only)
- README.md + README.fr.md : section 'PDF support' documentée
- docs/ROADMAP.md : #74 marqué 'pratiquement complet' avec détail honnête des items livrés vs non
- CHANGELOG.md : entrées pour #71 admin (déjà dans commit précédent), #75 I2 et #74
backend/VERSION est gitignore (genere par build.sh/CI) donc le COPY
echouait sur un clone propre. Remplace par le COPY backend/ existant +
ARG VERSION avec RUN fallback ; docker-compose.yml injecte VERSION via
build args. docker compose build --no-cache fonctionne desormais sans
build.sh.
inotify ne voit pas les ecritures faites depuis d'autres clients NFS
(Obsidian sur Windows), donc index_updated n'etait jamais emis et la
section Recent du sidebar ne se rafraichissait pas. Detection du fstype
par vault via /proc/mounts -> PollingObserver (stat-based) sur mounts
reseau, Observer natif conserve sur les mounts locaux.
L'input de la palette porte la classe .cp-input et non l'id #cp-input. Le fallback de dispatch manuel du raccourci est supprime : la cause racine est traitee.
- search.js : suppression par flag (jusqu a la prochaine frappe) au lieu d un timeout 500ms qui expirait avant le clic sur le bouton Forge
- split-view.spec.js : le test reset panes attend l apparition de la liste de commandes (debounce 150ms du palette) avant Entree
- la copie de l onglet actif etait dedupe (fichier deja ouvert dans le pane source) et le transfert clavier (200ms) vidait le pane source -> auto-collapse -> grid disparu
- comportement coherent avec le menu contextuel : pane vide, l utilisateur y glisse ses fichiers
- search.js : le dropdown d autocomplete ne se rouvre plus 500ms apres un hide (la recherche le rouvrait et bloquait les clics toolbar - test Forge)
- ui.js + pane-manager.js : le split via menu contextuel ne transfert plus l onglet en synchrone (vidait le pane source -> auto-collapse -> grid disparu)
- split-view.spec.js : navigation clavier attend la copie asynchrone (setTimeout 150ms qui reactivait pane 0)
- split-view.spec.js : drag depuis l arbre via dragTo (drag natif HTML5)
- un goto() entre deux openFile perd les onglets du singleton (PaneManager.init ne restaure que les splits >= 2 panes)
- expansion du vault seulement si le fichier n est pas deja visible
- le clic simple ouvre un preview qui remplace l onglet precedent (openPreview ferme l ancien preview)
- dblclick -> openPersistent : onglets cumulables (requis pour les tests multi-onglets)
- obsigate.spec.ts : searchFor appuie sur Entree (la recherche se declenche au keydown Enter, l autocomplete s affiche pendant la frappe)
- test_vault : ajout note1.md et note2.md (requis par les tests split-view)
- viewer.js : initialise la barre de filtres saved searches meme quand la liste est vide (sinon clics sans effet)
- ci.yml e2e : les bind mounts C:\dev\git\python\ObsiGate sont vides en docker-in-docker (chemin du job container inexistant sur l hote) -> docker create + docker cp des fixtures + docker start
- obsigate.spec.ts : placeholder de recherche attendu mis a jour (Rechercher dans toutes les vaults...)
- obsigate.spec.ts : theme par defaut accepte defaut-obsigate (moteur themes.js) en plus de dark (ui.js legacy)
- test_vault/ et test_dir/ : fixtures markdown pour les tests E2E Playwright (22 fichiers, .obsigate-backup ignore)
- ci.yml e2e : health check et BASE_URL via IP passerelle docker (localhost du job container ne voit pas le port publie sur l hote)
- ci.yml e2e : l etape Start ObsiGate echoue explicitement si l app n est pas joignable
- backend/VERSION est gitignore et genere par build.sh, mais le job build du CI ne le generait pas : docker build echouait avec /backend/VERSION: not found
fix(ci): job e2e - reparation de l etape Start ObsiGate (YAML imbrique casse, port 2029, nettoyage du container stale)
- frontend/index.html : splash #boot-splash 100% inline (CSS+JS), scripts CDN en defer pour ne pas bloquer le premier paint
- frontend/js/app.js : signale le boot terminé via __OBSIGATE_BOOTED (try/finally)
- desktop/src/main.rs : met à jour le statut du splash avant la redirection vers le backend
- ci: stage backend/ + frontend/ sous desktop/ avant tauri build (resources backend/** et frontend/**) dans desktop-build.yml et build-linux.sh
- desktop/gen/schemas/windows-schema.json : régénéré (permissions store/updater)
- docs: README, README.fr, ROADMAP
- Ajoute la reconnaissance de la syntaxe Obsidian [[#Titre|Texte]] pour
créer des liens d'ancre vers les titres du document courant.
- Corrige _heading_slugify pour ignorer les balises HTML et décoder les
entités HTML (ex. &) avant de générer l'ID, évitant ainsi la
pollution par les wikilinks placés dans les titres.
- Ajoute le style .wikilink-anchor et des tests couvrant ces cas.
Fixe le rendu du document TestDir/Agents IA - Panorama Complet 2026.md.
Les ancres des tables des matières utilisaient des caractères accentués
qui ne correspondaient pas aux identifiants de titres générés par
ObsiGate (suppression des accents et normalisation des slugs).
Fichiers concernés :
- README.md
- docs/ANALYSE_REVIEW.md
- desktop/README.md
CSP directive 'font-src' only allowed self, data:, and fonts.gstatic.com.
Excalidraw loads its handwriting fonts (*.woff2) from esm.sh via
EXCALIDRAW_ASSET_PATH. All ~12 font files were blocked (blocked:csp)
→ Excalidraw fails to initialize → infinite 'Loading...' spinner.
Fix: added https://esm.sh to font-src directive.