Skip to content

Keep spaces after the caret out of a new list item - #7

Merged
gnapse merged 1 commit into
mainfrom
fix/list-enter-stray-space
Oct 26, 2026
Merged

gnapse merged 1 commit into
mainfrom
fix/list-enter-stray-space

Conversation

@gnapse

@gnapse gnapse commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Enter in a list item moved the spaces after the caret into the new item. The old editor trimmed them (asked for on 14 Oct). The move to CodeMirror lost that, and no test noticed: only 1 of the 8 old list tests was ported.

Before Enter Main This PR
1. one‸ two 1. one\n2. ‸ two 1. one\n2. ‸two
- one ‸ two - one\n- ‸ two - one\n- ‸two
1. a‸ 1. a\n2. ‸ 1. a\n2. ‸
> a‸ b > a\n> ‸ b > a\n> ‸b

Root cause

insertNewlineContinueMarkupCommand in @codemirror/lang-markdown 6.5.2 trims spaces before the caret, then replaces only from there to the caret (dist/index.js, near line 270). Spaces after the caret stay, and land at the start of the new item.

Fix

In enter (src/editor/keys.ts), for lists and quotes:

  • Delete the spaces after the caret first, then let CodeMirror run on that state. One transaction, one undo step.
  • Map layers through the two steps one at a time, and send them with setLayers. A single replacement would leave a ghost that starts with a space (common for Lab cuts) on the old line, covering the line break.
  • The empty-item rule looks past spaces after the caret, so 1. a\n2. ‸ ends the list like 1. a\n2. ‸.
  • A caret right after a marker (1.‸ a, - [ ]‸ a, >‸ a) keeps the space that ends the marker. These behave as on main.

.claude/skills/fix-bug/SKILL.md no longer calls this bug open.

Tests

src/editor/keys.test.ts gets an "Enter in a list" block. It ports the old list tests, except the two renumbering cases below. The old "drops an empty item in the middle" case was already in "continues a list and ends it on an empty item". New cases cover quotes, tabs, a ghost at the caret (with undo and redo, checking the saved Markdown), undo after typing, a selection, and a caret right after a marker.

Verify

Ran: pnpm verify (passed, recorded 8db07409d693); keys.test.ts against main's keys.ts; 9 mutations of the fix; browser preview
Showed: main's keys.ts fails 8 of the new tests (the split cases, undo, the ghost, the empty item).
  Each mutation fails at least one test: returning true when CodeMirror declines, plain dispatch,
  spaces only (no tabs), no userEvent, one-step layer mapping, the old empty-item check,
  no marker check, and the marker pattern without checkboxes or without quotes.
  Preview: "- one ‸  two", Enter, type X → "- Xtwo" on the new line, no console errors.
Safe because: layers change only through setLayers in the same transaction, and undo and redo
  restore the saved Markdown (ran, the ghost test)
Not verified: the desktop app (WebKit). The change is in editor state, not caret drawing.

Review

review-changes ran a task review, an editor-check review, and a tests-check review. A fresh review then checked the final diff. Fixed from their findings: the ghost moving across the line break, the empty item with a trailing space, the caret after a marker, and five test gaps. Not changed:

  • A ghost made only of the deleted spaces disappears. The old editor deleted the same text. Undo restores it.
  • A Lab cut that starts with one of those spaces is dropped. The Lab already drops a cut when an edit touches it. Plain-paragraph Enter does the same.

Open questions for the owner

  1. Renumbering past an indent change or a lazy line. Not decided yet, so these two old cases are not in the tests:

    • 1. a‸\n 2. b\n3. c gives 4. c. The old editor kept 3. c.
    • 1. a‸\n - x\n2. b\nText\n3. c gives 4. c. The old editor stopped at Text.

    This PR doesn't change either one. Recommendation: keep CommonMark. The preview's parser puts 3. c in the same list in both cases. Renumbering keeps the numbers in the text consistent with how the list renders. The old stop came from the old editor reading lists line by line.

  2. Enter with a selection in a list item falls through to the default Enter, as on main. - one «two» three gives - one \nthree: no new item, and a trailing space. The old editor gave - one\n- three. The test "leaves a selection to the default Enter" keeps main's behavior until you decide.

  3. CLAUDE.md says commands return a TransactionSpec. Every command in keys.ts dispatches instead, this one included. The rule or the file should change.

Already on main, not changed here

  • A ghost that ends in a space before the caret takes the line break: - This{- really -}‸good. saves as - This{- really\n- -}good.
  • A caret after a marker and an extra space leaves an empty item: - ‸ two gives -\n- ‸two.

Enter in a list item moved the spaces after the caret into the new
item: "1. one‸ two" gave "2. ‸ two". The old editor trimmed them. The
move to CodeMirror lost that, and no test noticed.

CodeMirror's insertNewlineContinueMarkup drops the spaces before the
caret, but not those after it. Enter now deletes those first, then
lets CodeMirror continue the list or quote. Layers map through the two
steps one at a time. So a ghost that began with a space moves into
the new item, not over the line break.

- An empty item with spaces after the caret now ends the list, as in
  the old editor.
- A caret right after a marker, as in "1.‸ a", keeps the space that
  ends the marker.
- keys.test.ts has every old list test, plus cases for quotes, tabs,
  a ghost at the caret, undo, and a selection.
- The fix-bug skill no longer calls this bug open.

Two old renumbering cases stay out of the tests for now. Today's
editor follows CommonMark there and renumbers further than the old
one did. That choice is open.
@gnapse
gnapse merged commit 9a297dc into main Oct 26, 2026
1 of 2 checks passed
@gnapse
gnapse deleted the fix/list-enter-stray-space branch October 26, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant