Skip to content

Prevent image duplication when splitting blocks near images#159

Merged
hackerwins merged 1 commit into
mainfrom
fix/image-split-duplication
Apr 25, 2026
Merged

Prevent image duplication when splitting blocks near images#159
hackerwins merged 1 commit into
mainfrom
fix/image-split-duplication

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix image duplication bug when pressing Enter near an image inline in the docs editor
  • Yorkie Tree's SplitElement deep-copies all attributes to split siblings — for image inlines this creates ghost duplicates with image.* attributes but no text content
  • Add split-aware offset resolvers that skip image inlines at the end boundary, redirecting splitLevel=2 to the next inline

Root Cause

resolveOffset uses <= boundary semantics, so offsets at an image inline's end still resolve to that image inline. When splitLevel=2 splits through an image element, Yorkie's SplitElement DeepCopy()s all attributes (including image.*) to the empty split sibling, creating a ghost image.

Changes

File Change
packages/docs/src/store/block-helpers.ts Add resolveOffsetForSplit() — skips image inline at end boundary
packages/docs/src/index.ts Export new function
packages/frontend/.../yorkie-doc-store.ts Add resolveBlockNodeOffsetForSplit() — Yorkie Tree node equivalent
packages/docs/test/store/block-helpers.test.ts 7 test cases for image split handling

Test plan

  • pnpm verify:fast passes (all 587 docs tests + 162 frontend tests + 107 backend tests)
  • resolveOffsetForSplit correctly skips image inline when charOffset === textLen
  • resolveOffsetForSplit does NOT skip for text inlines or when image is last inline
  • Cache path (applySplitBlock) correctly handles image boundaries without adjustment
  • Manual test: insert image in wafflebase doc, press Enter near image — no duplication

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed document splitting behavior at image boundaries, ensuring images remain correctly positioned in the before block when splitting occurs at image edges, preventing split operations from crossing image elements.

Yorkie Tree's SplitElement deep-copies all attributes to split
siblings.  For image inlines this creates ghost duplicates because
the empty sibling inherits image.* attributes.  The bug manifests
when resolveOffset maps the split position to the image inline's
end boundary (due to its <= semantics), causing splitLevel=2 to
split through the image element.

Add resolveOffsetForSplit (block-helpers) and
resolveBlockNodeOffsetForSplit (yorkie-doc-store) that skip the
image inline when charOffset equals textLen, redirecting the split
to the start of the next inline instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This change introduces a new offset-resolution utility, resolveOffsetForSplit, that adjusts split positions when they land at the boundary of image-styled inlines. The utility prevents CRDT split operations from crossing image elements by redirecting boundary-adjacent offsets to the start of the subsequent inline.

Changes

Cohort / File(s) Summary
Offset Resolution Utility
packages/docs/src/store/block-helpers.ts, packages/docs/src/index.ts
Adds new resolveOffsetForSplit function that adjusts computed InlinePosition when an offset lands at the exact end of an image-styled inline, reassigning it to the subsequent inline's start offset to prevent splits from crossing image element boundaries.
Frontend Split Integration
packages/frontend/src/app/docs/yorkie-doc-store.ts
Integrates new split-aware offset resolver into splitBlock method via private resolveBlockNodeOffsetForSplit helper that detects image-related attributes and adjusts split points at image boundaries.
Test Coverage
packages/docs/test/store/block-helpers.test.ts
Adds comprehensive test suite for resolveOffsetForSplit behavior with image inlines (sentinel \uFFFC), verifying offset redirection at image boundaries and applySplitBlock assertions ensuring correct block partitioning at image end positions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A split that dances 'round the frame,
Where images stand without shame,
The offset hops to safety's floor,
Past image bounds, then on once more!
No breaks through art—just graceful flow,

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main objective: preventing image duplication when splitting blocks near images, which is the core bug fix addressed across all changed files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/image-split-duplication

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.

@github-actions

github-actions Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 119.9s

Lane Status Duration
sheets:build ✅ pass 13.0s
docs:build ✅ pass 8.3s
verify:fast ✅ pass 60.4s
frontend:build ✅ pass 15.7s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 15.6s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Apr 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/docs/src/store/block-helpers.ts (1)

148-164: Logic looks correct and is well-covered by the new tests.

The three guards (image-styled, charOffset at end, has next inline) precisely match the bug condition described in the PR objectives. Note that the image-last-in-block case is intentionally not skipped here because there is no next inline — the cache path's applySplitBlock still puts that image into before via the inlineEnd <= offset comparison (verified by the new test at lines 344–358), so this asymmetry is safe for the cache path. See the related concern in yorkie-doc-store.ts for the tree path.

Optional: tighten the doc comment to call out this design intent explicitly.

📝 Suggested comment refinement (optional)
 /**
  * Like `resolveOffset` but treats an offset at the exact end of an
  * image inline as belonging to the NEXT inline instead.  This prevents
  * `splitLevel=2` from splitting through the image element.
+ *
+ * No skip is performed when the image is the last inline in the block,
+ * because `applySplitBlock` (offset-based) already places such an image
+ * into the `before` block correctly.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/store/block-helpers.ts` around lines 148 - 164, The
implementation of resolveOffsetForSplit is correct, but the doc comment should
be tightened to explicitly state the design intent: clarify that when an offset
is exactly at the end of an image inline we treat it as belonging to the NEXT
inline to avoid splitLevel=2 splitting through images, and note the intentional
asymmetry for image-at-end-of-block (the cache path's applySplitBlock uses
inlineEnd <= offset to keep that image in `before`, while the tree path in
yorkie-doc-store.ts handles it differently); update the comment above
resolveOffsetForSplit to mention these specifics and reference applySplitBlock
and the related logic in yorkie-doc-store.ts so future readers understand the
rationale.
packages/docs/test/store/block-helpers.test.ts (1)

272-359: Test coverage is solid for the new helper and the cache path.

The five resolveOffsetForSplit cases plus the two applySplitBlock boundary cases comprehensively pin the cache-side behavior, including the deliberate non-skip when the image is the final inline. toEqual(IMG) on lines 337 and 353 correctly uses deep equality, so the cloneBlock-via-JSON.parse round-trip in applySplitBlock doesn’t cause false negatives.

One gap to consider: there is no test at the YorkieDocStore layer covering split at end-of-block when the image is the last inline. Given the tree-path concern raised in yorkie-doc-store.ts (no skip is possible, so splitLevel=2 is still applied through the image element), a small integration test there would lock down the absence of a ghost image in the tree representation.

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

In `@packages/docs/test/store/block-helpers.test.ts` around lines 272 - 359, Add
an integration test in yorkie-doc-store.ts (or the YorkieDocStore test suite)
that exercises splitting a block when the image is the final inline to ensure no
ghost image/tree-path bug; create a block with text then an image inline (use
the same IMG shape), call the store-level split method (the YorkieDocStore API
that triggers splitLevel=2 / the tree split), and assert the resulting document
tree contains the image only in the before-block and that the after-block has no
image inline and no stray tree node; this mirrors the cache-level tests for
resolveOffsetForSplit and applySplitBlock but validates YorkieDocStore’s split
path.
🤖 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/docs/src/store/block-helpers.ts`:
- Around line 148-164: The implementation of resolveOffsetForSplit is correct,
but the doc comment should be tightened to explicitly state the design intent:
clarify that when an offset is exactly at the end of an image inline we treat it
as belonging to the NEXT inline to avoid splitLevel=2 splitting through images,
and note the intentional asymmetry for image-at-end-of-block (the cache path's
applySplitBlock uses inlineEnd <= offset to keep that image in `before`, while
the tree path in yorkie-doc-store.ts handles it differently); update the comment
above resolveOffsetForSplit to mention these specifics and reference
applySplitBlock and the related logic in yorkie-doc-store.ts so future readers
understand the rationale.

In `@packages/docs/test/store/block-helpers.test.ts`:
- Around line 272-359: Add an integration test in yorkie-doc-store.ts (or the
YorkieDocStore test suite) that exercises splitting a block when the image is
the final inline to ensure no ghost image/tree-path bug; create a block with
text then an image inline (use the same IMG shape), call the store-level split
method (the YorkieDocStore API that triggers splitLevel=2 / the tree split), and
assert the resulting document tree contains the image only in the before-block
and that the after-block has no image inline and no stray tree node; this
mirrors the cache-level tests for resolveOffsetForSplit and applySplitBlock but
validates YorkieDocStore’s split path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7090160-9dbd-4f94-949a-ba686dad3b4a

📥 Commits

Reviewing files that changed from the base of the PR and between ea7b90b and 6055131.

📒 Files selected for processing (4)
  • packages/docs/src/index.ts
  • packages/docs/src/store/block-helpers.ts
  • packages/docs/test/store/block-helpers.test.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts

@hackerwins
hackerwins merged commit 9b112a8 into main Apr 25, 2026
4 checks passed
@hackerwins
hackerwins deleted the fix/image-split-duplication branch April 25, 2026 14:54
@hackerwins hackerwins mentioned this pull request May 1, 2026
3 tasks
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