feat(editeur): #93 edition inline — Editer/Forge remplacent la vue lecture
CI / lint (push) Successful in 1m26s
CI / security (push) Successful in 1m0s
CI / test (push) Successful in 3m17s
CI / build (push) Successful in 56s
CI / e2e (push) Successful in 10m47s

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.
This commit is contained in:
2026-09-15 23:40:10 -04:00
parent f630771150
commit e3df4bbf00
16 changed files with 920 additions and 33 deletions
+45
View File
@@ -781,6 +781,12 @@ class BooksLM {
ctx.open_documents = docs.map((d) => ({ vault: d.vault, path: d.path }));
}
// #93 — Live edition session: the document currently replaced by the
// editor/Forge surface. Knowing it lets the assistant target "le document
// que je suis en train d'éditer" and update it in place.
const editing = this._editingDocument();
if (editing) ctx.editing = editing;
const query = state.advancedSearchLastQuery;
if (query) {
ctx.search_query = query;
@@ -803,6 +809,43 @@ class BooksLM {
return ctx;
}
/**
* #93 — The document currently being edited, i.e. the one the editor/Forge
* surface replaced the read view with, or null. Sent as `app_context.editing`
* so the assistant can target "le document que je suis en train d'éditer".
*/
_editingDocument() {
try {
const modal = document.getElementById('editor-modal');
if (!modal || !modal.classList.contains('active')) return null;
const vault = state.editorVault;
const path = state.editorPath;
if (!vault || !path) return null;
return { vault, path, surface: document.getElementById('forge-iframe') ? 'forge' : 'editor' };
} catch (e) {
return null;
}
}
/**
* #93 — A write tool just modified a vault file. Notify the UI so the
* displayed document is reloaded from disk: otherwise the read view keeps the
* stale content, and an open editor buffer autosaves the old text back over
* the assistant's change (see utils.reloadExternalWrite).
*/
_notifyFileWritten(data) {
if (!data || data.ok === false) return;
const WRITE_TOOLS = ['edit_file', 'append_to_file', 'create_file', 'restore_backup'];
if (WRITE_TOOLS.indexOf(data.name) === -1) return;
const args = data.arguments || {};
if (!args.vault || !args.path) return;
window.dispatchEvent(
new CustomEvent('obsigate:file-written', {
detail: { vault: args.vault, path: args.path, tool: data.name },
}),
);
}
// ── Rendering ───────────────────────────────────────────────────────
_render() {
@@ -2575,6 +2618,8 @@ class BooksLM {
step: data.step || null,
sources: Array.isArray(data.sources) ? data.sources : [],
});
// #93 — a vault write must be reflected in the displayed document.
this._notifyFileWritten(data);
this._setActivity('working', t('ai.activity_tool', { name: data.name }));
return;
}