Skip to content

fix: swallow a following decorator when a removal window reaches into it - #83

Merged
jjrodcast merged 1 commit into
jjrodcast:mainfrom
LaatonWalaBhoot:fix/removal-window-decorator-fragments
Jul 30, 2026
Merged

fix: swallow a following decorator when a removal window reaches into it#83
jjrodcast merged 1 commit into
jjrodcast:mainfrom
LaatonWalaBhoot:fix/removal-window-decorator-fragments

Conversation

@LaatonWalaBhoot

Copy link
Copy Markdown
Contributor

Closes #82

Two changes, both in the multi-paragraph removal paths:

  • isLastDecoratorPartiallySelected is now computed in document coordinates (window end strictly inside the last item's decorator span, and only for list items). The previous form compared the decorator piece's buffer offset against document offsets, so the partial-decorator swallow stopped firing once decorators sat deep in the ADDED buffer.
  • TextUpdateTransaction gets the replace-side mirror of extendPastOrphanedDecorator (Deleting across a paragraph boundary up to a list item's decorator orphans the decorator in the live text #67/TextDeletedTransaction): a removal window that consumes the terminating line break and stops at the next item's start extends one char into the decorator and re-dispatches, so the existing handling swallows it whole and renumbers. One difference from the delete rule: a non-empty replacement is itself a head for the merged line, so the extension also applies when the whole first line is replaced (and not when the replacement itself ends with a line break).

Tests: RemovalWindowIntoDecoratorTest — 6 cases covering delete/replace windows ending inside a decorator, replacing the break between bulleted/task items, renumbering after the merge, and the downstream insert-crash. All 6 fail on main. Full suite passes, JS/Wasm compile clean. In the seeded sweep (100 seeds × 250 ops) these two holes accounted for every remaining crash; none remain with the fix.

Two holes let delete/replace leave decorator text stranded mid-line, where
the export silently drops it and later inserts crash on the leaked offsets:

- isLastDecoratorPartiallySelected (delete and replace multi-paragraph
  paths) passed the decorator piece's buffer offset into document-offset
  math, so once an item's decorator lived deep in the ADDED buffer the
  predicate went false and a window ending inside the decorator deleted it
  only partially. Rewritten in document coordinates.

- The replace path had no equivalent of the delete path's
  extendPastOrphanedDecorator: replacing the line break between two items
  merged the lines but kept the second item's decorator. Mirrored the rule,
  with one difference — a non-empty replacement is itself a head for the
  merged line, so the extension also applies when the whole first line is
  replaced.

Both re-use the existing partially-selected-decorator handling, so merges
renumber remaining numbered items as before.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a corruption/crash class in multi-paragraph delete/replace operations where a removal window can partially consume (or stop exactly at) a following list item’s decorator, leaving decorator fragments mid-line and causing later offset-based edits to crash.

Changes:

  • Compute isLastDecoratorPartiallySelected using document offsets (not piece/buffer offsets) so partial-decorator swallowing works even when decorators live deep in the ADDED buffer.
  • Add TextUpdateTransaction.extendPastOrphanedDecorator to mirror the delete-path behavior: when a replace consumes a terminating line break and stops at the next list item start, extend one char into the decorator and re-dispatch so existing logic swallows/renumbers correctly.
  • Add RemovalWindowIntoDecoratorTest covering delete/replace windows reaching into decorators, break-replacement merges for bullet/task items, renumbering, and the downstream insert-crash regression.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
shared/src/commonTest/kotlin/com/jjrodcast/textkit/RemovalWindowIntoDecoratorTest.kt Adds regression tests ensuring decorator fragments are not left mid-line after multi-paragraph delete/replace and that export/merge behavior remains stable.
shared/src/commonMain/kotlin/com/jjrodcast/textkit/editor/core/transactions/text/TextUpdateTransaction.kt Extends replace windows into following decorators in the specific orphaning case, and fixes partial-decorator detection to use document coordinates.
shared/src/commonMain/kotlin/com/jjrodcast/textkit/editor/core/transactions/text/TextDeletedTransaction.kt Fixes partial-decorator detection to use document coordinates, preventing missed “swallow decorator” behavior when buffer/document offsets diverge.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@LaatonWalaBhoot

Copy link
Copy Markdown
Contributor Author

@jjrodcast I think it can be merged

@jjrodcast
jjrodcast merged commit 2df48c2 into jjrodcast:main Jul 30, 2026
1 check passed
@LaatonWalaBhoot
LaatonWalaBhoot deleted the fix/removal-window-decorator-fragments branch July 30, 2026 12:59
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.

Delete/replace ending inside a following list item's decorator leaves the fragment mid-line (later inserts crash)

3 participants