L'assistant IA peut desormais inserer sa reponse dans l'editeur Forge (postMessage parent-insert) en plus d'Editer, et chaque bloc de code propose un bouton « Ajouter la section » pour n'inserer que ce bloc.
Une session d'edition inline (Editer/Forge) qui possede la zone de contenu etait detruite par deux chemins qui vident cette zone : activation d'onglet (TabManager.activate, PaneTabManager.activate) -> detachInlineEditor() avant le placeholder ; evenement SSE index_updated -> reloadExternalWrite() recharge le tampon de l'editeur au lieu de re-rendre la vue lecture. Nouveau helper queryEditor() ; closeEditor() remet le conteneur dans la modale avant de restaurer en-tete/pied/marque. docs/CONTRIBUTING.md documente scripts/install-hooks.sh.
BUG-046: 'Ajouter du texte au document courant' échouait avec 'Échec de
l'action: [object Object]'. Trois causes corrigées:
- continuation de confirmation sans payload -> second Appliquer POSTait
sans 'message' (422); le payload d'origine est désormais transmis
- detail 422 FastAPI (tableau d'objets) stringifié tel quel -> nouvelle
_responseError() qui aplatit en message lisible
- prompt documents/dossier muet sur le vault -> le modèle inventait
'vault: test' et append_to_file échouait en silence; build_system_prompt
nomme le vault et enjoint les outils d'écriture de l'utiliser exactement
BUG-045: double barre de défilement dans l'éditeur Editer (fichiers
longs): #editor-body overflow:auto + cm-editor height:100% sous la barre
IA -> corps et scroller CodeMirror défilant tous les deux. Flex column,
seul le scroller défile; override global .cm-scroller retiré. SW v20.
Vérifié: pytest 1038, ruff backend 0, tests frontend AI 84/84,
editor-inline 25/25, validate-imports 38 modules; Playwright sur
l'instance de test (un seul conteneur scrollable) et flux agent complet
append_to_file ok=true sur fichier réel
Le conteneur d'edition (#editor-container, CodeMirror ou iframe Forge) est deplace dans la zone de contenu du document (#content-area ou pane active) au lieu de l'overlay plein ecran : le mode edition remplace la vue lecture. L'overlay reste monte (transparent, pointer-events: none) car le ruban d'edition mobile y est ancre ; il ne sert plus que de repli quand la cible n'est pas le document affiche. Garde-fou dans renderFile() pour liberer proprement la session (destroy CodeMirror/Yjs, retrait de l'iframe Forge) ; forge-close passe par closeEditor() ; cache d'onglet invalide au retour en lecture.
Assistant IA : app_context.editing annonce le document en cours d'edition (frontend bookslm.js + backend bookslm.py), et chaque ecriture d'outil (edit_file, append_to_file, create_file, restore_backup) recharge le document affiche (obsigate:file-written) — tampon CodeMirror remplace avec auto-save neutralisee, parent-reload pour l'iframe Forge, re-rendu de la vue lecture sinon.
Tests : tests/frontend/editor-inline.test.mjs (19), tests/test_bookslm.py::TestGeneralPrompt (3 nouveaux). Fiche : docs/features/editeur-inline.md.
[pop-out][Bookmark] | [Editer][Source][.ext][Pretty] | [Copier] | [Partager]
- TOC ancre uniquement au markdown (plus de bouton TOC sur .sh/.py/...)
- PDF/Export deja ancres au markdown: groupe export = [Copier] seul sur texte
- tests toolbar-order mis a jour (conditions is_markdown sur nav/export)
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 : ajouter ou
supprimer une clé API dans la configuration du projet laissait la liste des
fournisseurs figée jusqu'à un rechargement de page.
- ai.js : nouveau refreshAIPickers() qui reconstruit chaque picker monté dans
son emplacement .ai-picker-slot (constante PICKER_SLOT_CLASS) ; le slot est
conservé même sans fournisseur configuré, donc le premier fournisseur ajouté
s'y monte aussi ; une sélection persistée dont le fournisseur n'est plus
configuré est purgée de obsigate_ai_picker (retour au défaut, plus de modèle
fantôme dans le déclencheur).
- bookslm.js : l'emplacement .bookslm-picker-host porte la classe
ai-picker-slot et reste dans la barre (replaceChildren au lieu de replaceWith).
- config.js : refreshAIPickers() après saveAIKeys() et deleteAIKey().
- tests : +4 tests JSDOM (ajout/retrait dans la barre, premier montage dans un
slot vide, purge de la sélection orpheline, câblage save/delete).
Vérifié : tests frontend 66/66 (IA) + 9 suites JSDOM, validate-imports 36
modules, pytest 963 passed / 6 skipped, ruff 0, et contrôle navigateur
(Playwright) sur l'instance de test — ajout de nvidia visible sans rechargement,
retrait effectif + sélection réinitialisée.
- accents : classes de caracteres Unicode (\p{L}\p{N}\p{M}) pour les motifs
de liens et _looksLikePath (PATH_NAME_RE) -> les chemins/fichiers accentues
produisent enfin un lien cliquable ; comparaison de chemins normalisee NFC
(_normKey), donc une mention decomposee (e + accent combinant, style macOS)
correspond a une entree d'index precomposee, et inversement.
- prefixe de vault : _splitVaultPrefix() retire un premier segment egal au nom
d'un vault connu (TestVault/Recettes/Pizza Maison.md) ; _fetchPathsForVault()
interroge l'index de CE vault sans ecraser le cache du vault actif ;
_openFileLink/_revealPath recoivent le vault cible ; repli 'retirer le
premier segment' si le prefixe ne correspond a aucun vault connu.
Tests : frontend IA 62/62 (+5), 9 suites JSDOM, validate-imports 36 modules,
pytest 963 passed / 6 skipped. Verifie aussi contre l'instance live sur donnees
reelles accentuees/espaces (Recettes/Preparation.md, Recettes/Pat... ) : 8/8.
Les noms/paths de l'assistant peuvent contenir des espaces ; la detection et
la resolution les prennent desormais en charge de bout en bout :
- liens markdown : cible avec espaces, encadree par <...> ou avec un "titre",
et URL percent-encodees (%20) decodees ;
- code inline : `Ma note.md` / `Mon dossier/Ma note.md` reconnus
(_looksLikePath accepte les espaces, jeu de caracteres strict pour rejeter
extraits de code et commandes shell) ;
- mentions brutes : liees uniquement si presentes dans l'index du vault
(_linkifySpacePaths + _confirmPathInCache), avec acceptation du plus long
suffixe aligne sur un mot -> le mot de prose precedent n'est pas avale ;
- _normalizeLinkPath : trim ; _renderMdLink : decodage %20.
Tests : frontend IA 57/57 (+5), 9 suites JSDOM, validate-imports 36 modules,
pytest 963 passed / 6 skipped.
- BUG-041 : le contexte d'un dossier vide ne renvoie plus 404 ; il degrade
vers le prompt General enrichi d'un bloc « Dossier vide ».
- BUG-042 : liens de fichiers deterministes (nom -> presse-papiers,
dossier -> arborescence, chemin -> viewer) et resolution du chemin contre
l'index du vault (exact -> suffixe -> basename) avant ouverture, fin des
'File not found'.
- #88 : app_context (documents ouverts, repertoire/vault courants, recherche
+ resultats affiches) et fichiers recemment modifies injectes dans le
prompt General.
Tests : pytest 963 passed / 6 skipped, ruff 0, mypy 0, frontend 52/52 +
validate-imports 36 modules.
Nouvel endpoint GET /api/vault/{vault}/paths + prechargement et filtrage client du menu @ (affichage instantane des fichiers/repertoires). Selecteurs Fournisseur/Modele agrandis (0,8rem / 34px) pour s'aligner sur le reste du site. Ajout de BooksLM au menu contextuel de la racine des vaults.
BUG-015: l'etat actif des menus / et @ (et de la liste de modeles) utilise --bg-hover + barre d'accent a gauche, car --surface2 est identique a --bg-primary en theme sombre. Retrait de l'indice 'Entree pour envoyer...' sous la zone de saisie.
BUG-013: _openFileLink/_revealPath utilisent _activeVault() (contexte -> selection -> premier vault). BUG-014: navigation up/down geree au niveau du panneau en phase de capture, independante du focus. Caches: SW_VERSION v6 + migration qui purge tous les caches obsigate-*.