Skip to content

fix: Multi-column errors when moving the only block out of a column#2678

Open
Tokonigeorge wants to merge 1 commit intoTypeCellOS:mainfrom
Tokonigeorge:fix/multi-column-move-blocks-up
Open

fix: Multi-column errors when moving the only block out of a column#2678
Tokonigeorge wants to merge 1 commit intoTypeCellOS:mainfrom
Tokonigeorge:fix/multi-column-move-blocks-up

Conversation

@Tokonigeorge
Copy link
Copy Markdown

@Tokonigeorge Tokonigeorge commented Apr 26, 2026

Summary

Fixes the Uncaught Error: Block with ID … not found thrown by moveBlocksUp/moveBlocksDown when the block being moved is the only block in its column. The throw happens inside editor.transact, so the transaction is rolled back and the block does not move.

Closes #2594

Rationale

When a column contains only a single block, removing that block from the column makes the column empty. removeAndInsertBlocks then runs fixColumnList, which removes the empty column. ProseMirror's schema requires at least two columns, so it auto-fills an empty one, and fixColumnList then replaces the entire columnList with the contents of the remaining non-empty column. The columnList's ID no longer exists in the document.

moveSelectedBlocksAndSelection was caching referenceBlock (the columnList itself, in the move-out-of-column case) and calling editor.insertBlocks(..., referenceBlock, placement) after removeBlocks. The subsequent getNodeById lookup failed and threw.

Changes

  • packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts: in moveSelectedBlocksAndSelection, resolve the destination to a ProseMirror position before removeBlocks, map it through any steps the removal adds, and insert at the mapped position via a direct ReplaceStep instead of re-resolving the referenceBlock ID.
  • packages/xl-multi-column/src/test/commands/moveBlocks.test.ts: two regression tests that fail without the fix and pass with it. Covers both the case from the issue (columnList followed by a paragraph) and the variant where the columnList is preceded by a sibling.

Impact

No behavioral change for existing scenarios. All existing moveBlocks snapshot tests still pass. Removing the ID-based re-lookup also slightly hardens the function against any future side effect that might destroy the destination block.

Testing

  • New regression tests in packages/xl-multi-column/src/test/commands/moveBlocks.test.ts.
  • Manually verified in the playground at /basic/multi-column/: add a multi-column block, type text under it, Cmd+Shift+Up. No console error; the block moves out cleanly.

Screenshots/Video

column-console-bug.mov

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

(No documentation change applicable; this is an internal bug fix.)

Additional Notes

N/A

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced block movement operations to improve reliability and provide faster error detection when moving blocks to invalid destinations.
  • Tests

    • Added regression tests to ensure block movement works correctly in complex document layouts.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 26, 2026

@Tokonigeorge is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

Refactors the block move operation to resolve destination positions upfront, remap insertion positions through transaction steps following block removal, and use direct ProseMirror ReplaceStep operations instead of higher-level API functions for more accurate positional handling.

Changes

Cohort / File(s) Summary
Core Block Move Logic
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
Refactored move operation to resolve destination block upfront, fail fast on invalid references, remap position after removal through transaction steps, and insert blocks via ReplaceStep with converted ProseMirror nodes instead of editor.insertBlocks.
Move Blocks Test Coverage
packages/xl-multi-column/src/test/commands/moveBlocks.test.ts
Added two regression tests validating moveBlocksUp() behavior when moving a paragraph out of a single-block column within a columnList, covering both first-level and preceded-by-sibling scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • nperez0111
  • matthewlipski

Poem

🐰 Hoppy times ahead!
Columns no more cause a fright,
Blocks now move with proper might,
Position remapped through every step,
Multi-column moves—no more missteps! 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main fix: resolving multi-column errors when moving the only block out of a column.
Linked Issues check ✅ Passed The changes directly address issue #2594 by fixing the uncaught error when moving the only block out of a column and ensuring no errors occur during the operation.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the multi-column move blocks error: updates to moveBlocks.ts logic and addition of regression tests; no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description comprehensively covers all required template sections with clear, detailed content for Summary, Rationale, Changes, Impact, Testing, and Checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/xl-multi-column/src/test/commands/moveBlocks.test.ts (2)

63-139: Add parallel moveBlocksDown regression coverage.

The PR description and the production fix apply equally to moveBlocksDown, but the new describe block only exercises moveBlocksUp. The "after" placement branch (targetPos = posBeforeNode + nodeSize) is not covered by these regression tests; a parallel moveBlocksDown case for moving the only block in the last column out of the columnList would close that gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/xl-multi-column/src/test/commands/moveBlocks.test.ts` around lines
63 - 139, Add a parallel test case exercising moveBlocksDown to hit the "after"
placement branch (targetPos = posBeforeNode + nodeSize): duplicate the existing
single-block-column tests that call editor.moveBlocksUp but instead set the text
cursor in the last column's only paragraph (e.g., "only-paragraph-b" /
"only-paragraph-d") and invoke editor.moveBlocksDown(), asserting it does not
throw; ensure one test covers the columnList preceded by a sibling (same setup
as "column-list-single-2" block) so the branch for moving the only block out of
the columnList into the following position is covered.

99-99: Strengthen regression assertions beyond "does not throw".

The fix's risk surface is mostly silent: an off-by-one in the position remap, or a future change to fixColumnList's step shape, would not throw but would land the moved block in the wrong place (e.g., after only-paragraph-b rather than before the collapsed columnList). Consider asserting the resulting document via toMatchSnapshot() or an explicit shape check so a positional regression is caught.

🧪 Suggested addition
-    expect(() => editor.moveBlocksUp()).not.toThrow();
+    expect(() => editor.moveBlocksUp()).not.toThrow();
+    expect(editor.document).toMatchSnapshot();

Also applies to: 137-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/xl-multi-column/src/test/commands/moveBlocks.test.ts` at line 99,
The test currently only asserts editor.moveBlocksUp() does not throw, which
misses positional regressions; after invoking editor.moveBlocksUp() (and the
related call at the other occurrence), capture the editor document/state (e.g.,
via the test helper you use to inspect the doc, or a serializer like
getDocument()/toJSON()/state.doc) and add an assertion that checks the resulting
structure — either expect(serializedDoc).toMatchSnapshot() or explicit
expectations that the moved block now appears before the collapsed columnList
(and not after only-paragraph-b). Also update both occurrences (the
expect(...not.toThrow()) at the two spots noted) to include the new structural
assertion instead of relying solely on not.toThrow().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/xl-multi-column/src/test/commands/moveBlocks.test.ts`:
- Around line 63-139: Add a parallel test case exercising moveBlocksDown to hit
the "after" placement branch (targetPos = posBeforeNode + nodeSize): duplicate
the existing single-block-column tests that call editor.moveBlocksUp but instead
set the text cursor in the last column's only paragraph (e.g.,
"only-paragraph-b" / "only-paragraph-d") and invoke editor.moveBlocksDown(),
asserting it does not throw; ensure one test covers the columnList preceded by a
sibling (same setup as "column-list-single-2" block) so the branch for moving
the only block out of the columnList into the following position is covered.
- Line 99: The test currently only asserts editor.moveBlocksUp() does not throw,
which misses positional regressions; after invoking editor.moveBlocksUp() (and
the related call at the other occurrence), capture the editor document/state
(e.g., via the test helper you use to inspect the doc, or a serializer like
getDocument()/toJSON()/state.doc) and add an assertion that checks the resulting
structure — either expect(serializedDoc).toMatchSnapshot() or explicit
expectations that the moved block now appears before the collapsed columnList
(and not after only-paragraph-b). Also update both occurrences (the
expect(...not.toThrow()) at the two spots noted) to include the new structural
assertion instead of relying solely on not.toThrow().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e4d1ee46-a720-40c7-872b-fa8c26d6de2b

📥 Commits

Reviewing files that changed from the base of the PR and between 210b499 and 4b98e27.

📒 Files selected for processing (2)
  • packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
  • packages/xl-multi-column/src/test/commands/moveBlocks.test.ts

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.

Multi-columns errors when moving blocks

1 participant