fix: run cosmetic span normalization only as fallout of local edits#3013
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 1e98388 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 807.5 KB | - |
| Internal (gzip) | 155.1 KB | +4 B, +0.0% |
| Bundled (raw) | 1.42 MB | - |
| Bundled (gzip) | 319.4 KB | +4 B, +0.0% |
| Import time | 102ms | -0ms, -0.1% |
@portabletext/editor/behaviors
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 467 B | - |
| Internal (gzip) | 207 B | - |
| Bundled (raw) | 424 B | - |
| Bundled (gzip) | 171 B | - |
| Import time | 2ms | +0ms, +0.9% |
@portabletext/editor/plugins
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 2.7 KB | - |
| Internal (gzip) | 894 B | - |
| Bundled (raw) | 2.5 KB | - |
| Bundled (gzip) | 827 B | - |
| Import time | 7ms | -0ms, -0.8% |
@portabletext/editor/selectors
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 82.7 KB | - |
| Internal (gzip) | 15.4 KB | - |
| Bundled (raw) | 78.4 KB | - |
| Bundled (gzip) | 14.3 KB | - |
| Import time | 8ms | +0ms, +0.6% |
@portabletext/editor/traversal
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 28.1 KB | - |
| Internal (gzip) | 5.6 KB | - |
| Bundled (raw) | 28.1 KB | - |
| Bundled (gzip) | 5.5 KB | - |
| Import time | 6ms | +0ms, +2.8% |
@portabletext/editor/utils
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 30.6 KB | - |
| Internal (gzip) | 6.4 KB | - |
| Bundled (raw) | 28.2 KB | - |
| Bundled (gzip) | 6.1 KB | - |
| Import time | 6ms | +0ms, +1.3% |
🗺️ . · ./behaviors · ./plugins · ./selectors · ./traversal · ./utils · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @portabletext/markdown
Compared against main (149cc6f8)
| Metric | Value | vs main (149cc6f) |
|---|---|---|
| Internal (raw) | 53.8 KB | - |
| Internal (gzip) | 9.8 KB | - |
| Bundled (raw) | 348.9 KB | - |
| Bundled (gzip) | 96.3 KB | - |
| Import time | 39ms | -1ms, -2.6% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
christianhg
force-pushed
the
skip-cosmetic-normalization-on-value-adoption
branch
from
July 23, 2026 08:27
de871f6 to
b9a577e
Compare
The sync machine's five apply sites nested `withoutNormalizing` outside `withRemoteChanges`, so the normalization flush at the outermost exit ran after the remote flag was restored and cosmetic rules (adjacent same-mark span merges, empty sibling drops) still ran on value adoption: `initialValue` and `update value` canonicalized content the user never touched, parking a document-wide mutation wave that flushed with the first local edit. Reordering the wrappers puts `withRemoteChanges` outermost at all five sites, so the flush runs inside the flag and the cosmetic gate covers every adoption path. History and patching wrappers keep their positions relative to the flush, so repair emission and undo semantics are byte-identical; the only delta is that cosmetic rules no longer run on adopted content. The engine-side diff round-trip fuzz on the revised-patch-pipeline branch proved this necessary: seven seeds failed because mount-time merging forked the engine's base from the document before any patch arrived, and diffed patches hit dead keys. Those seeds are pinned there as `test.fails` and will report unexpectedly-passing when that branch rebases over this fix, graduating to its green set. Four pins of the old adoption-merge behavior are deliberately re-homed: the two normalization merge contracts re-trigger via local edits (the order-independent marks comparison keeps its coverage), the selection-preservation pin flips to the stronger no-remap contract (the caret's span survives adoption untouched), and the stored-split- shape sync-survival scenario creates its shape mismatch via a local merge instead of a load-time one.
christianhg
force-pushed
the
skip-cosmetic-normalization-on-value-adoption
branch
from
July 23, 2026 08:27
b9a577e to
1e98388
Compare
christianhg
force-pushed
the
skip-cosmetic-normalization-on-value-adoption
branch
from
July 23, 2026 08:51
4c3f976 to
de871f6
Compare
christianhg
marked this pull request as ready for review
July 23, 2026 09:37
Merged
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.
Opening a fragmented document and typing one character currently pushes a document-wide wave of structural rewrites: mount-time normalization merges every block's adjacent same-mark spans, parks the fallout on the pristine editor, and the first keystroke flushes it all, rewriting blocks the user never touched.
The cause is a nesting order. The sync machine's five apply sites wrap
withoutNormalizingoutsidewithRemoteChanges, so the normalize flush runs after the remote flag is restored and the cosmetic gate from #3001 never sees value adoption. The fix reorders the wrappers sowithRemoteChangesis outermost. History and patching wrappers keep their positions relative to the flush, so repair emission and undo semantics are byte-identical.Net behavior change: adopted span structure (
initialValue,update value) is kept as the document has it, adjacent same-mark spans and empty sibling spans no longer merge away on load, and canonicalizes only as fallout of a local edit to its block. Scoped to the two rules #3001 classified as cosmetic: structural repairs still run everywhere, and markDef GC and empty-span annotation stripping are deliberately untouched (their classification is a tracked open decision). Four pins of the old behavior are re-homed rather than deleted: the merge contracts re-trigger via local edits, the selection pin flips to the stronger no-remap contract, and the sync-survival scenario builds its shape mismatch from a local merge. Full suites green.Independent of #3011, whose round-trip fuzz surfaced the bug (mount-time merging forks the engine's base from the document, so diffed patches target dead keys); its seven
test.failsseeds graduate when that branch rebases over this fix.