From 1fbddff39a453cb39d78e7645f65ef5e53d9f675 Mon Sep 17 00:00:00 2001 From: Isulion Date: Tue, 20 Oct 2026 20:43:57 +0200 Subject: [PATCH] feat(megaprompt-v4): on-screen custom text input and frozen prompt editor Co-Authored-By: Claude Opus 5.5 --- tests/js/test_mega_prompt_v4.mjs | 30 ++++++++++++++ web/mega_prompt_v4.js | 68 ++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/tests/js/test_mega_prompt_v4.mjs b/tests/js/test_mega_prompt_v4.mjs index 3f17dfe..3c4f07d 100644 --- a/tests/js/test_mega_prompt_v4.mjs +++ b/tests/js/test_mega_prompt_v4.mjs @@ -114,6 +114,36 @@ test("removing the node twice is safe and leaves nothing running", async () => { const mine = fake.observers.slice(obsBefore); assert.ok(mine.length >= 1 && mine.every((o) => o.disconnected)); }); +test("inline edit pins a custom subject and rerolls that reel", async () => { + const node = await createNode(); api.responders["/isulion/megaprompt_v4/roll"] = payload(); + const j = node._jackpot; click(j.ui.reels.subject.btns.edit); + assert.equal(j.ui.editor.panel.hidden, false); j.ui.editor.input.value = " my elf queen "; + j.ui.editor.input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" })); await tick(); + assert.equal(lastRoll().body.state.reels.subject.custom, "my elf queen"); assert.deepEqual(lastRoll().body.reels, ["subject"]); + assert.equal(j.ui.editor.panel.hidden, true); +}); +test("escape cancels the inline edit", async () => { + const node = await createNode(); const before = api.calls.length; const j = node._jackpot; + click(j.ui.reels.location.btns.edit); j.ui.editor.input.value = "x"; + j.ui.editor.input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" })); + assert.equal(j.state.reels.location.custom, ""); assert.equal(api.calls.length, before); assert.equal(j.ui.editor.panel.hidden, true); +}); +test("clear button removes the custom text and rerolls", async () => { + const node = await createNode(); api.responders["/isulion/megaprompt_v4/roll"] = payload(); const j = node._jackpot; + j.state.reels.location.custom = "my castle"; click(j.ui.reels.location.btns.edit); + assert.equal(j.ui.editor.input.value, "my castle"); click(j.ui.editor.clear); await tick(); + assert.equal(lastRoll().body.state.reels.location.custom, ""); assert.deepEqual(lastRoll().body.reels, ["location"]); +}); +test("edit prompt freezes it and the lever unfreezes", async () => { + const node = await createNode(); api.responders["/isulion/megaprompt_v4/roll"] = payload({ rolled: [], segments: [{ reel: "frozen", text: "fixed text" }], prompt: "fixed text", state: { ...readState(null), frozen_prompt: "fixed text" } }); + const j = node._jackpot; click(j.ui.editBtn); j.ui.editor.textarea.value = "fixed text"; + click(j.ui.editor.ok); await tick(); + assert.equal(lastRoll().body.state.frozen_prompt, "fixed text"); assert.deepEqual(lastRoll().body.reels, []); await tick(); + assert.equal(j.ui.frozenBadge.hidden, false); + click(j.ui.lever); await tick(); + assert.equal(lastRoll().body.state.frozen_prompt, ""); assert.deepEqual(lastRoll().body.reels, REELS); +}); + // --- runner (keep last) --------------------------------------------------- let failures = 0; for (const [name, fn] of tests) { diff --git a/web/mega_prompt_v4.js b/web/mega_prompt_v4.js index 388b7e7..d405e09 100644 --- a/web/mega_prompt_v4.js +++ b/web/mega_prompt_v4.js @@ -278,6 +278,17 @@ class PromptJackpot { const ok = el("button"); row.append(clear, cancel, ok); panel.append(input, textarea, row); + const onKey = (e) => { + e.stopPropagation(); // Delete/Backspace must not reach LiteGraph while typing + if (e.key === "Escape") this.closeEditor(); + if (e.key === "Enter" && e.target === input) this.confirmEditor(); + }; + input.addEventListener("keydown", onKey); + textarea.addEventListener("keydown", onKey); + ok.addEventListener("click", () => this.confirmEditor()); + cancel.addEventListener("click", () => this.closeEditor()); + clear.addEventListener("click", () => this.clearCustom()); + panel.addEventListener("pointerdown", (e) => { if (e.target === panel) this.closeEditor(); }); this.ui.editor = { panel, input, textarea, clear, cancel, ok }; return panel; } @@ -481,9 +492,60 @@ class PromptJackpot { this.ui.search.list.replaceChildren(...rows); } - // inline editors (Tâche 9) - openInlineEdit() {} - openPromptEditor() {} + // on-screen editors: custom subject/location text, or the whole frozen prompt + openInlineEdit(reel) { + const { panel, input, textarea, clear } = this.ui.editor; + this.editing = { reel }; + input.hidden = false; + clear.hidden = false; + textarea.hidden = true; + input.value = this.state.reels[reel].custom; + panel.hidden = false; + input.focus(); + } + + openPromptEditor() { + const { panel, input, textarea, clear } = this.ui.editor; + this.editing = { prompt: true }; + input.hidden = true; + clear.hidden = true; + textarea.hidden = false; + textarea.value = this.state.frozen_prompt || (this.view.last?.prompt ?? ""); + panel.hidden = false; + textarea.focus(); + } + + closeEditor() { + this.editing = null; + this.ui.editor.panel.hidden = true; + } + + confirmEditor() { + const editing = this.editing; + if (!editing) return; + this.closeEditor(); + if (editing.prompt) { + this.state.frozen_prompt = this.ui.editor.textarea.value; + this.commit(); + this.roll([]); + } else { + this.setCustom(editing.reel, this.ui.editor.input.value.trim()); + } + } + + clearCustom() { + const editing = this.editing; + if (!editing?.reel) return; + this.closeEditor(); + this.setCustom(editing.reel, ""); + } + + setCustom(reel, text) { + this.state.reels[reel].custom = text; + this.commit(); + this.renderReel(reel); + this.roll([reel]); + } // history (Tâche 10) pushHistory() {}