fix(open-knowledge): Backspace/Delete at a list-item boundary orphani…#613
Open
blokboy wants to merge 3 commits into
Open
fix(open-knowledge): Backspace/Delete at a list-item boundary orphani…#613blokboy wants to merge 3 commits into
blokboy wants to merge 3 commits into
Conversation
…ng rows Merging list items via Backspace (start of item) or Delete (end of item) routed through StarterKit's ListKeymap sub-extension, which assumes the fragmented BulletList/OrderedList/TaskList/TaskItem schema this repo replaced with a unified listItem node. Two of its branches misfire against that schema: Backspace into a previous item that has a nested sublist lifts the merged item clean out of the list into a bare, unmarked paragraph, and Delete across a nested/top-level depth change re-nests the merged item at the wrong depth and drops its checked attr — both read by users as "orphan" list rows (inkeep#609). ListItemNode now binds Backspace/Delete itself (priority 101, ahead of ListKeymap's default 100) using prosemirror-commands' joinTextblockBackward/ joinTextblockForward, which descend through container nodes to the actual textblocks and merge only those, falling back to joinBackward/joinForward when there's no adjacent item to join into.
Two new list-keymap.e2e.ts cases exercise the exact orphan scenarios via real Playwright keystrokes: Backspace merging into an item whose preceding sibling has a nested sublist, and Delete merging across a nested/top-level depth boundary. Testing locally also appears issue is resolved.
blokboy
force-pushed
the
fix/609-list-backspace-delete-orphan
branch
from
July 13, 2026 20:47
7c4a03d to
e4c01c3
Compare
inkeep-oss-sync Bot
pushed a commit
that referenced
this pull request
Jul 14, 2026
…(#2630) * test(open-knowledge): pin nested list-item boundary merge defects RED Reproduces issue #609's remaining orphan source on a mounted TipTap editor via real keydown dispatch: Backspace at the start of an item after a sibling with a nested sublist lifts it to a bare paragraph (orphan), and Delete at the end of a nested item before a shallower task item re-nests it at the wrong depth and drops its checked attr. Covers Mod-Backspace and Mod-Delete, ordered lists, and multi-paragraph items, plus controls pinning flat merges, stock first-item lift, undoInputRule, trailing-paragraph rejoin, empty-nested-item removal, ranged selections, and suggestion-layer Enter/Tab precedence. Test scenarios adapted from #613. * fix(open-knowledge): merge nested list-item boundaries on Backspace/Delete Upstream ListKeymap (StarterKit's @tiptap/extension-list sub-extension) misfires in two branches at nested item boundaries, by upstream design rather than schema wiring: Backspace at the start of an item whose previous sibling has a nested sublist takes the liftListItem fallthrough and mints a bare orphan paragraph, and Delete at the end of a nested item before a shallower item takes the nextListIsHigher branch, re-nesting the next item at the wrong depth and dropping its checked attr. The new ListBoundaryMerge extension preempts exactly those two configurations across all four bindings ListKeymap holds (Backspace, Delete, and their Mod variants) using joinTextblockBackward and joinTextblockForward, which merge only the adjacent textblocks and preserve the surviving item's marker, depth, and checked attr. Every other configuration falls through, so ListKeymap's good branches (flat joins, first-item lift, undoInputRule, trailing-paragraph rejoin, empty nested-item removal) are untouched. Preemption relies on registration order after StarterKit at the default priority, so suggestion-layer Enter/Tab precedence (precedent #48) is unaffected. Schema snapshot regenerated: additive extensionOrder entry only. Fixes #609. Supersedes and credits #613 by blokboy (defect localization, the joinTextblock repair mechanism, and the test scenarios are theirs). * test(open-knowledge): pin nested boundary merges in e2e and add changeset Extends list-keymap.e2e.ts with the two nested-boundary scenarios, asserting persisted Y.Text bytes through the full CRDT bridge, including the checkbox-preservation assertion strong enough to catch the pre-fix indented plain-bullet corruption. Changeset marks a patch release. Test scenarios adapted from #613. * chore(open-knowledge): drop decision markers from test docblock md-audit comment-discipline flags spec decision markers in source comments; the docblock now names the two failure shapes in prose. * chore(open-knowledge): refresh ng-anchors catalog test file count The catalog freshness gate deep-equals a fresh enumeration, which counts test files; the new list boundary merge unit suite changed the count. * test(open-knowledge): cover 3-level nesting and explain recursive scan Review follow-up: adds Backspace/Delete merge tests at three nesting levels so the depth-traversal loops are exercised beyond one iteration, and documents why itemContainsSublist scans descendants rather than direct children (it must mirror upstream ListKeymap's descendants-based listItemHasSubList so the guard fires exactly when the upstream branch misfires, including listItem > blockquote > list shapes). * test(open-knowledge): anchor merge-target checkbox and ranged control Review follow-up: the Backspace merge tests now pin the merge target's checkbox, marker, and nested indent with anchored regexes matching the Delete-side rigor, and the ranged-selection control also asserts the trailing item survives without orphaning. * test(open-knowledge): pin depth in the 3-level Delete merge test Review follow-up: the 3-level Delete test now anchors the merged item at level-3 indentation and guards against the one-level-too-shallow form, matching the 2-level depth rigor. * test(open-knowledge): pin 3-level Backspace depth and edge merge targets Review follow-up. The 3-level Backspace test now anchors the merged item at level-3 indentation. Two new pins cover the owned configuration's unusual targets: a code block as the deepest preceding textblock (the merge lands inside it, no fallthrough to the upstream lift) and a fresh autoformatted empty item at the nested boundary (dissolves into the list rather than minting an empty orphan paragraph; the undoInputRule-first call in the handler keeps upstream ordering whenever the undoable state is live, which the app's trailing appended transaction typically consumes). --------- GitOrigin-RevId: 465e670a9205ab14c8d24cae3a4f69029c73bbce
inkeep-oss-sync Bot
pushed a commit
that referenced
this pull request
Jul 14, 2026
…(#2630) * test(open-knowledge): pin nested list-item boundary merge defects RED Reproduces issue #609's remaining orphan source on a mounted TipTap editor via real keydown dispatch: Backspace at the start of an item after a sibling with a nested sublist lifts it to a bare paragraph (orphan), and Delete at the end of a nested item before a shallower task item re-nests it at the wrong depth and drops its checked attr. Covers Mod-Backspace and Mod-Delete, ordered lists, and multi-paragraph items, plus controls pinning flat merges, stock first-item lift, undoInputRule, trailing-paragraph rejoin, empty-nested-item removal, ranged selections, and suggestion-layer Enter/Tab precedence. Test scenarios adapted from #613. * fix(open-knowledge): merge nested list-item boundaries on Backspace/Delete Upstream ListKeymap (StarterKit's @tiptap/extension-list sub-extension) misfires in two branches at nested item boundaries, by upstream design rather than schema wiring: Backspace at the start of an item whose previous sibling has a nested sublist takes the liftListItem fallthrough and mints a bare orphan paragraph, and Delete at the end of a nested item before a shallower item takes the nextListIsHigher branch, re-nesting the next item at the wrong depth and dropping its checked attr. The new ListBoundaryMerge extension preempts exactly those two configurations across all four bindings ListKeymap holds (Backspace, Delete, and their Mod variants) using joinTextblockBackward and joinTextblockForward, which merge only the adjacent textblocks and preserve the surviving item's marker, depth, and checked attr. Every other configuration falls through, so ListKeymap's good branches (flat joins, first-item lift, undoInputRule, trailing-paragraph rejoin, empty nested-item removal) are untouched. Preemption relies on registration order after StarterKit at the default priority, so suggestion-layer Enter/Tab precedence (precedent #48) is unaffected. Schema snapshot regenerated: additive extensionOrder entry only. Fixes #609. Supersedes and credits #613 by blokboy (defect localization, the joinTextblock repair mechanism, and the test scenarios are theirs). * test(open-knowledge): pin nested boundary merges in e2e and add changeset Extends list-keymap.e2e.ts with the two nested-boundary scenarios, asserting persisted Y.Text bytes through the full CRDT bridge, including the checkbox-preservation assertion strong enough to catch the pre-fix indented plain-bullet corruption. Changeset marks a patch release. Test scenarios adapted from #613. * chore(open-knowledge): drop decision markers from test docblock md-audit comment-discipline flags spec decision markers in source comments; the docblock now names the two failure shapes in prose. * chore(open-knowledge): refresh ng-anchors catalog test file count The catalog freshness gate deep-equals a fresh enumeration, which counts test files; the new list boundary merge unit suite changed the count. * test(open-knowledge): cover 3-level nesting and explain recursive scan Review follow-up: adds Backspace/Delete merge tests at three nesting levels so the depth-traversal loops are exercised beyond one iteration, and documents why itemContainsSublist scans descendants rather than direct children (it must mirror upstream ListKeymap's descendants-based listItemHasSubList so the guard fires exactly when the upstream branch misfires, including listItem > blockquote > list shapes). * test(open-knowledge): anchor merge-target checkbox and ranged control Review follow-up: the Backspace merge tests now pin the merge target's checkbox, marker, and nested indent with anchored regexes matching the Delete-side rigor, and the ranged-selection control also asserts the trailing item survives without orphaning. * test(open-knowledge): pin depth in the 3-level Delete merge test Review follow-up: the 3-level Delete test now anchors the merged item at level-3 indentation and guards against the one-level-too-shallow form, matching the 2-level depth rigor. * test(open-knowledge): pin 3-level Backspace depth and edge merge targets Review follow-up. The 3-level Backspace test now anchors the merged item at level-3 indentation. Two new pins cover the owned configuration's unusual targets: a code block as the deepest preceding textblock (the merge lands inside it, no fallthrough to the upstream lift) and a fresh autoformatted empty item at the nested boundary (dissolves into the list rather than minting an empty orphan paragraph; the undoInputRule-first call in the handler keeps upstream ordering whenever the undoable state is live, which the app's trailing appended transaction typically consumes). --------- GitOrigin-RevId: 465e670a9205ab14c8d24cae3a4f69029c73bbce
inkeep-oss-sync Bot
pushed a commit
that referenced
this pull request
Jul 14, 2026
…(#2630) * test(open-knowledge): pin nested list-item boundary merge defects RED Reproduces issue #609's remaining orphan source on a mounted TipTap editor via real keydown dispatch: Backspace at the start of an item after a sibling with a nested sublist lifts it to a bare paragraph (orphan), and Delete at the end of a nested item before a shallower task item re-nests it at the wrong depth and drops its checked attr. Covers Mod-Backspace and Mod-Delete, ordered lists, and multi-paragraph items, plus controls pinning flat merges, stock first-item lift, undoInputRule, trailing-paragraph rejoin, empty-nested-item removal, ranged selections, and suggestion-layer Enter/Tab precedence. Test scenarios adapted from #613. * fix(open-knowledge): merge nested list-item boundaries on Backspace/Delete Upstream ListKeymap (StarterKit's @tiptap/extension-list sub-extension) misfires in two branches at nested item boundaries, by upstream design rather than schema wiring: Backspace at the start of an item whose previous sibling has a nested sublist takes the liftListItem fallthrough and mints a bare orphan paragraph, and Delete at the end of a nested item before a shallower item takes the nextListIsHigher branch, re-nesting the next item at the wrong depth and dropping its checked attr. The new ListBoundaryMerge extension preempts exactly those two configurations across all four bindings ListKeymap holds (Backspace, Delete, and their Mod variants) using joinTextblockBackward and joinTextblockForward, which merge only the adjacent textblocks and preserve the surviving item's marker, depth, and checked attr. Every other configuration falls through, so ListKeymap's good branches (flat joins, first-item lift, undoInputRule, trailing-paragraph rejoin, empty nested-item removal) are untouched. Preemption relies on registration order after StarterKit at the default priority, so suggestion-layer Enter/Tab precedence (precedent #48) is unaffected. Schema snapshot regenerated: additive extensionOrder entry only. Fixes #609. Supersedes and credits #613 by blokboy (defect localization, the joinTextblock repair mechanism, and the test scenarios are theirs). * test(open-knowledge): pin nested boundary merges in e2e and add changeset Extends list-keymap.e2e.ts with the two nested-boundary scenarios, asserting persisted Y.Text bytes through the full CRDT bridge, including the checkbox-preservation assertion strong enough to catch the pre-fix indented plain-bullet corruption. Changeset marks a patch release. Test scenarios adapted from #613. * chore(open-knowledge): drop decision markers from test docblock md-audit comment-discipline flags spec decision markers in source comments; the docblock now names the two failure shapes in prose. * chore(open-knowledge): refresh ng-anchors catalog test file count The catalog freshness gate deep-equals a fresh enumeration, which counts test files; the new list boundary merge unit suite changed the count. * test(open-knowledge): cover 3-level nesting and explain recursive scan Review follow-up: adds Backspace/Delete merge tests at three nesting levels so the depth-traversal loops are exercised beyond one iteration, and documents why itemContainsSublist scans descendants rather than direct children (it must mirror upstream ListKeymap's descendants-based listItemHasSubList so the guard fires exactly when the upstream branch misfires, including listItem > blockquote > list shapes). * test(open-knowledge): anchor merge-target checkbox and ranged control Review follow-up: the Backspace merge tests now pin the merge target's checkbox, marker, and nested indent with anchored regexes matching the Delete-side rigor, and the ranged-selection control also asserts the trailing item survives without orphaning. * test(open-knowledge): pin depth in the 3-level Delete merge test Review follow-up: the 3-level Delete test now anchors the merged item at level-3 indentation and guards against the one-level-too-shallow form, matching the 2-level depth rigor. * test(open-knowledge): pin 3-level Backspace depth and edge merge targets Review follow-up. The 3-level Backspace test now anchors the merged item at level-3 indentation. Two new pins cover the owned configuration's unusual targets: a code block as the deepest preceding textblock (the merge lands inside it, no fallthrough to the upstream lift) and a fresh autoformatted empty item at the nested boundary (dissolves into the list rather than minting an empty orphan paragraph; the undoInputRule-first call in the handler keeps upstream ordering whenever the undoable state is live, which the app's trailing appended transaction typically consumes). --------- GitOrigin-RevId: 465e670a9205ab14c8d24cae3a4f69029c73bbce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Addressing Issue #609
How this was verified
bun run checkfollowed by a manual evaluation of the behavior locally.Checklist
bun run check(lint, typecheck, tests) locallybun run changeset) if this changes behaviorAfter you open this PR
See CONTRIBUTING.md for details.