From 9af948dc53468aa4a27e8d7e3ebac96a9f4d0ad3 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 29 Aug 2026 14:08:31 +0530 Subject: [PATCH 1/3] fix(playground): insert one newline per Enter --- web/main.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/web/main.js b/web/main.js index 83464e9..1ade89e 100644 --- a/web/main.js +++ b/web/main.js @@ -212,6 +212,14 @@ function getCaretOffset() { return count; } +function sourceOffsetAt(node, offset) { + if (node !== editor && !editor.contains(node)) return null; + const range = document.createRange(); + range.setStart(editor, 0); + range.setEnd(node, offset); + return editorSource(range.cloneContents()).length; +} + function setCaretOffset(offset) { const boundaries = []; // {at, make} const walk = (node, base) => { @@ -619,6 +627,24 @@ editor.addEventListener("keydown", (e) => { return; } + if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); + const sel = getSelection(); + if (!sel.rangeCount) return; + const range = sel.getRangeAt(0); + const start = sourceOffsetAt(range.startContainer, range.startOffset); + const end = sourceOffsetAt(range.endContainer, range.endOffset); + if (start === null || end === null) return; + const src = editorSource(editor); + const next = src.slice(0, start) + "\n" + src.slice(end); + editor.replaceChildren(buildEditorFragment(next)); + setCaretOffset(start + 1); + renderGutter(next); + scheduleUndoCommit(); + updateAutocomplete(); + return; + } + if (e.key === "Tab") { // No popup yet: complete directly when there is exactly one // candidate, otherwise open the popup for the word at the caret. From bcdda29db43786eb73c477b69e2a117e834071df Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 29 Aug 2026 14:09:20 +0530 Subject: [PATCH 2/3] fix(playground): allow clearing the empty editor --- web/main.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/main.js b/web/main.js index 1ade89e..899687f 100644 --- a/web/main.js +++ b/web/main.js @@ -158,6 +158,15 @@ function makeEmoteImg(name) { // Plain-text source; images contribute their literal names. function editorSource(root = editor) { + if (root === editor && root.childNodes.length === 1) { + const only = root.firstChild; + if (only.nodeName === "BR" || + (only.nodeName === "DIV" && only.childNodes.length === 1 && + only.firstChild.nodeName === "BR")) { + return ""; + } + } + let src = ""; for (const node of root.childNodes) { if (node.nodeType === Node.TEXT_NODE) { From 7d531495530228225e4a549bde6819c4a1db8ec0 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sat, 29 Aug 2026 14:17:59 +0530 Subject: [PATCH 3/3] fix(playground): respect IME composition in Enter handler --- web/main.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/main.js b/web/main.js index 899687f..d3b6e32 100644 --- a/web/main.js +++ b/web/main.js @@ -158,6 +158,9 @@ function makeEmoteImg(name) { // Plain-text source; images contribute their literal names. function editorSource(root = editor) { + // A bare contenteditable can leave a single
or

+ // as a placeholder. Treat that as empty source so the user can delete + // back to an empty editor. if (root === editor && root.childNodes.length === 1) { const only = root.firstChild; if (only.nodeName === "BR" || @@ -637,6 +640,7 @@ editor.addEventListener("keydown", (e) => { } if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) { + if (e.isComposing) return; // let IME confirm composition e.preventDefault(); const sel = getSelection(); if (!sel.rangeCount) return;