--- a/app/javascript/controllers/list_autofill_controller.js
+++ b/app/javascript/controllers/list_autofill_controller.js
@@ -33,16 +33,30 @@
     switch (result.action) {
       case 'remove':
         event.preventDefault()
-        this.input.setRangeText('', lineStartPos, selectionStart, 'start')
+        // Use execCommand so the edit participates in the browser's undo
+        // stack (setRangeText and value assignments are not undoable).
+        this.input.setSelectionRange(lineStartPos, selectionStart)
+        if (!document.execCommand('delete')) {
+          this.input.setRangeText('', lineStartPos, selectionStart, 'start')
+        }
         break
-      case 'insert':
-        event.preventDefault()
-        const insertText = "\n" + result.text
-        const newValue = value.slice(0, selectionStart) + insertText + value.slice(selectionStart)
-        const newCursor = selectionStart + insertText.length
-        this.input.value = newValue
-        this.input.setSelectionRange(newCursor, newCursor)
+      case 'insert': {
+        // Let the native line break through (it creates an undo entry and
+        // scrolls the caret into view), then append the list marker at the
+        // caret. Replacing input.value here instead would discard the undo
+        // history on every Enter and leave the caret off-screen in an
+        // overflowing textarea.
+        const text = result.text
+        this.input.addEventListener('input', (e) => {
+          if (e.inputType !== 'insertLineBreak') return
+          if (!document.execCommand('insertText', false, text)) {
+            const el = e.currentTarget
+            const pos = el.selectionStart
+            el.setRangeText(text, pos, pos, 'end')
+          }
+        }, { once: true })
         break
+      }
       default:
         return
     }
