feat(ai): ajouter une section de la reponse et supporter l'editeur Forge (BUG-057, #102)
CI / lint (push) Successful in 1m34s
CI / security (push) Successful in 1m1s
CI / test (push) Successful in 2m44s
CI / build (push) Successful in 1m1s
CI / e2e (push) Successful in 10m51s

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.
This commit is contained in:
2026-09-17 09:39:35 -04:00
parent 94ea5909f4
commit c0eea526de
19 changed files with 239 additions and 29 deletions
+67
View File
@@ -404,6 +404,73 @@ async function main() {
panel.remove();
});
await test("add action delegates to the Forge iframe when no CodeMirror is open (BUG-057)", () => {
const b = new BooksLM();
const panel = b._render();
b._panel = panel;
document.body.appendChild(panel);
b._messages = [{ role: "assistant", content: "Réponse Forge" }];
b._renderMessages();
state.editorView = null;
const iframe = document.createElement("iframe");
iframe.id = "forge-iframe";
document.body.appendChild(iframe);
const posted = [];
iframe.contentWindow.postMessage = (msg) => posted.push(msg);
panel.querySelector(".bookslm-msg.assistant .bookslm-msg-action-insert").click();
assert.equal(posted.length, 1, "one postMessage to the Forge iframe");
assert.equal(posted[0].type, "parent-insert");
assert.ok(posted[0].text.includes("Réponse Forge"), "answer forwarded");
iframe.remove();
panel.remove();
});
await test("add action falls back to the plain textarea editor", () => {
const b = new BooksLM();
const panel = b._render();
b._panel = panel;
document.body.appendChild(panel);
b._messages = [{ role: "assistant", content: "Réponse textarea" }];
b._renderMessages();
state.editorView = null;
const ta = document.createElement("textarea");
ta.value = "Ligne existante";
document.body.appendChild(ta);
ta.setSelectionRange(ta.value.length, ta.value.length);
state.fallbackEditorEl = ta;
panel.querySelector(".bookslm-msg.assistant .bookslm-msg-action-insert").click();
assert.ok(ta.value.includes("Réponse textarea"), "answer appended to the textarea");
state.fallbackEditorEl = null;
ta.remove();
panel.remove();
});
await test("code block exposes an add-section button inserting only the block (#102)", () => {
const b = new BooksLM();
const panel = b._render();
b._panel = panel;
document.body.appendChild(panel);
b._messages = [{
role: "assistant",
content: "Voici la section :\n\n```markdown\n## Titre\n\nContenu\n```\n\nFin.",
}];
b._renderMessages();
const btn = panel.querySelector(".bookslm-msg.assistant .bookslm-code-insert");
assert.ok(btn, "per-block add button rendered");
const dispatched = [];
state.editorView = {
state: { selection: { main: { to: 0 } } },
dispatch: (spec) => dispatched.push(spec),
focus: () => {},
};
btn.click();
assert.equal(dispatched.length, 1, "editor dispatch called");
assert.ok(dispatched[0].changes.insert.includes("## Titre"), "block content inserted");
assert.ok(!dispatched[0].changes.insert.includes("```"), "code fences stripped");
state.editorView = null;
panel.remove();
});
await test("manual wheel scroll releases the top-pinning", async () => {
const b = new BooksLM();
const panel = b._render();
+7
View File
@@ -347,6 +347,13 @@ test("editor-poc.html reloads Forge buffer on parent-reload", () => {
assert.match(handler[1], /loadFile\(\);/);
});
test("editor-poc.html inserts assistant text on parent-insert (BUG-057)", () => {
assert.match(forgeSrc, /if \(e\.data\.type === 'parent-insert' && typeof e\.data\.text === 'string'\) \{/);
const handler = forgeSrc.match(/if \(e\.data\.type === 'parent-insert' && typeof e\.data\.text === 'string'\) \{([\s\S]*?)\n \}/);
assert.ok(handler, "parent-insert handler not found");
assert.match(handler[1], /insertAtCursor\(/);
});
test("style.css lets the inline editor fill the content area", () => {
assert.match(cssSrc, /\.editor-modal\.editor-inline-mode \{/);
assert.match(cssSrc, /\.editor-modal\.editor-inline-mode \{[\s\S]*?pointer-events: none;/);