Skip to content

fix: keep same-key siblings indexed when a duplicate _key is renamed - #3364

Open
christianhg wants to merge 1 commit into
mainfrom
fix/block-index-map-duplicate-key-rename
Open

christianhg wants to merge 1 commit into
mainfrom
fix/block-index-map-duplicate-key-rename

Conversation

@christianhg

@christianhg christianhg commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Pressing Backspace after the editor receives a new value that removes a block above the caret no longer deletes everything from the start of the document to the caret. For example, with the blocks "foo", "bar", "baz" and "qux", syncing a value without "bar" and then pressing Backspace at the end of "baz" now leaves "foo", "ba" and "qux". Previously it left a single empty block followed by "qux".

The value sync replaces blocks by position, so removing "bar" briefly gives two blocks the same _key until the normalizer renames one. That rename dropped the other block's entries from the block index map, and with the caret's block missing from the map, a collapsed delete couldn't find the point before the caret and fell back to the document start. The rename now rebuilds the entries of every sibling sharing the old or new key, first occurrence wins, matching a fresh build. Other operations on duplicate-keyed siblings (insert, unset, full replace) are unchanged and still diverge from a fresh build, as they do on main.


Note

Medium Risk
Touches incremental block-index maintenance used for selection and delete resolution; scope is limited to _key rename handling but incorrect maps can still corrupt edits.

Overview
Fixes incorrect block index map updates when a node's _key is renamed while another sibling still uses the old or new key—a transient state that can appear during remote value sync before normalization.

handleKeyChange no longer only re-adds the renamed node's subtree; it rebuilds index entries for every sibling sharing the old or new _key, with first occurrence winning, aligned with a fresh buildIndexMaps. SiblingContext now carries a keyedPrefix to support that scoped rebuild across blocks, spans, and nested rows.

User-visible: after an update value removes a block above the caret, Backspace at the end of the next block deletes one character instead of wiping from document start to the caret. Patch flows with duplicate-key siblings before rename/unset are covered by new unit and integration tests; a patch changeset is included.

Reviewed by Cursor Bugbot for commit 546b792. Bugbot is set up for automated code reviews on this repo. Configure here.

`update value` syncs blocks by position, so removing a block above the
caret replaces each later block with the content of the block after it.
For a moment the replaced block and the not-yet-replaced block below it
share a `_key`, and the duplicate-key normalizer renames the second one
with a `set` on `_key`. `handleKeyChange` then pruned the map entries
under the old keyed path, which the surviving sibling shares, and
re-added only the renamed node. Every shifted block lost its
`blockIndexMap` entry along with its spans' entries. A later collapsed
`delete.backward` compared paths against the missing entry
(`comparePathsInTree` reads it as -1), `before()` found no position,
and `deleteCollapsed` fell back to the editor start: one Backspace
deleted everything from the top of the document to the caret.

After a rename, `handleKeyChange` now rebuilds the entries of every
sibling carrying the old or the new key, first occurrence wins, which
is what `buildIndexMaps` produces for the same value. This holds for
root blocks, spans and container children, and for three or more
siblings sharing a key. Pinned by map tests with full expected maps
(red on the old transform), an `update value` plus Backspace test, and
a remote patch batch that renames one of three same-key blocks and then
unsets by key, which guards against restoring the siblings last-wins.

Only renames are covered. Inserting, removing or replacing a node while
its key is duplicated among its siblings can still leave the map
different from a fresh build, as before.
@vercel

vercel Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
portable-text-editor-documentation Ready Ready Preview Oct 2, 2026 4:13pm UTC
portable-text-example-basic Ready Ready Preview Oct 2, 2026 4:13pm UTC
portable-text-playground Ready Ready Preview Oct 2, 2026 4:13pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 546b792

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@portabletext/editor Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-decorations Patch
@portabletext/plugin-dnd Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-list-index Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value Patch
@portabletext/plugin-table Patch
@portabletext/plugin-typeahead-picker Patch
@portabletext/plugin-typography Patch
@portabletext/toolbar Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Bundle Stats

✅ No significant changes.

All scenario measurements (7)

🗺️ @portabletext/editor / @portabletext/editor · @portabletext/editor / @portabletext/editor/behaviors · @portabletext/editor / @portabletext/editor/plugins · @portabletext/editor / @portabletext/editor/selectors · @portabletext/editor / @portabletext/editor/traversal · @portabletext/editor / @portabletext/editor/utils · @portabletext/markdown / @portabletext/markdown · Artifacts

Scenario Kind Bundle (raw / gzip) Gzip change Import time Import change
⚪ @portabletext/editor / @portabletext/editor export 1.07 MB / 135.6 KB +154 B, +0.1% 65 ms -1 ms, -0.9%
⚪ @portabletext/editor / @portabletext/editor/behaviors export 4.0 KB / 147 B None 2 ms -0 ms, -1.8%
⚪ @portabletext/editor / @portabletext/editor/plugins export 5.1 KB / 645 B None 7 ms +0 ms, +0.7%
⚪ @portabletext/editor / @portabletext/editor/selectors export 94.7 KB / 9.3 KB None 8 ms -0 ms, -1.9%
⚪ @portabletext/editor / @portabletext/editor/traversal export 43.5 KB / 4.0 KB None 6 ms +0 ms, +2.5%
⚪ @portabletext/editor / @portabletext/editor/utils export 33.8 KB / 4.1 KB None 6 ms +0 ms, +1.6%
⚪ @portabletext/markdown / @portabletext/markdown export 393.8 KB / 62.7 KB None 27 ms +0 ms, +0.3%

Significant means at least 1.0 KB and 1% gzip, or at least 5 ms and 10% import time.

This branch was successfully deployed

3 active deployments
Preview – portable-text-editor-documentation — 546b7925 Deployed Oct 2, 2026 by vercel[bot]
Preview – portable-text-playground — 546b7925 Deployed Oct 2, 2026 by vercel[bot]
Preview – portable-text-example-basic — 546b7925 Deployed Oct 2, 2026 by vercel[bot]
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