Skip to content

Add intent-preserving Yorkie edits for Docs editor (Phase 1-3)#103

Merged
hackerwins merged 25 commits into
mainfrom
feature/intent-preserving-edits
Apr 3, 2026
Merged

Add intent-preserving Yorkie edits for Docs editor (Phase 1-3)#103
hackerwins merged 25 commits into
mainfrom
feature/intent-preserving-edits

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Migrate Docs editor from full block replacement to character-level Yorkie Tree editing for text insert/delete operations
  • Add inline styling and structural editing (split/merge) via DocStore methods with block-level Yorkie replacement
  • Extract common pure helpers into block-helpers.ts shared by MemDocStore and YorkieDocStore
  • Upgrade @yorkie-js/sdk to 0.7.3

Yorkie Tree Strategy

Operation Yorkie API Granularity Concurrent behavior
Text insert editByPath Character-level CRDT merge
Text delete editByPath Character-level + empty inline cleanup CRDT merge
Style editByPath Block replacement LWW
Split/Merge editByPath Block replacement LWW

styleByPath is element-level only (not text-range), so style operations use block replacement.
Reported upstream: yorkie-team/yorkie-js-sdk#1197

What Changed

New files:

  • packages/docs/src/store/block-helpers.ts — Pure helper functions (252 lines)
  • packages/docs/test/store/block-helpers.test.ts — 32 unit tests

Modified files:

  • DocStore interface — Added insertText, deleteText, applyStyle, splitBlock, mergeBlock
  • MemDocStore — Implements new methods using shared helpers
  • YorkieDocStore — Character-level editByPath for text, block replacement for style/structure
  • Doc class — Routes to new store methods for top-level blocks, keeps existing path for table cells
  • @yorkie-js/sdk — 0.7.3-alpha → 0.7.3 (includes mixed char+block undo/redo fix)

Design doc: docs/design/docs-intent-preserving-edits.md

Known Issues

  • Remote cursor offset not transformed — when a remote user inserts text before the local cursor, the position is not adjusted
  • Style/structure operations are LWW — concurrent style or split/merge on the same block loses one side

Remaining Phases (separate PRs)

  • Phase 4: Table cell internal edits (docs/tasks/active/20260403-intent-preserving-phase4-table-cells-todo.md)
  • Phase 5: Yorkie-native undo/redo (docs/tasks/active/20260403-intent-preserving-phase5-undo-redo-todo.md)

Test plan

  • Single user: type, delete, Enter, Backspace, Undo/Redo
  • Bold/Italic: apply, toggle, persist after refresh
  • Select all + delete + retype
  • Unit tests: pnpm verify:fast passes (339 tests)
  • Two users: concurrent typing in same paragraph — both edits preserved
  • Table cell editing unchanged (still uses old path)

🤖 Generated with Claude Code

hackerwins and others added 16 commits April 2, 2026 01:29
Add character-level Yorkie Tree editing methods that use resolveOffset
and resolveDeleteRange helpers instead of full block replacement, enabling
efficient collaborative text edits.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Yorkie Tree paths for text editing within inline elements use 3 levels
[blockIdx, inlineIdx, charOffset], not 4 levels. The inline node's
hasTextChild() causes the last path element to be interpreted as a
character offset within its text children automatically.
Use in-place cache update (same pattern as updateBlock) instead of
cache invalidation. The invalidation approach caused intermediate
states visible to remote peers, triggering "Block not found" errors
in the formatting toolbar during concurrent editing.
Implement applyStyle in YorkieDocStore using styleByPath per inline
segment (Task 8) and route Doc.applyInlineStyle single-block top-level
case through store.applyStyle instead of the local applyStyleToBlock
helper (Task 9). Also move superscript/subscript mutual exclusion into
the shared applyInlineStyle block-helper so all code paths enforce it.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Implements applySplitBlock and applyMergeBlocks pure helpers in
block-helpers.ts, adds splitBlock/mergeBlock to the DocStore interface,
and wires them up in MemDocStore with full test coverage.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Implements splitBlock/mergeBlock in YorkieDocStore using editByPath for
atomic block replacement/insertion. Wires Doc.splitBlock and Doc.mergeBlocks
top-level paths to these new store methods, removing the manual inline
manipulation. Also fixes applySplitBlock to preserve listKind/listLevel when
the new block type is list-item.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Yorkie's styleByPath sets attributes on a single element node, not on
a text range. It cannot split inline nodes for partial styling. Use
block-level editByPath replacement until Yorkie adds text-range style
support.
@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds design/task docs and implements intent-preserving, character-level Docs edits: new pure block-helpers, expanded DocStore API (insert/delete/style/split/merge), MemDocStore and YorkieDocStore implementations, Doc delegation to store, tests, and re-exports for helpers.

Changes

Cohort / File(s) Summary
Design & Tasks
docs/design/README.md, docs/design/docs-intent-preserving-edits.md, docs/tasks/active/20260402-intent-preserving-edits-todo.md, docs/tasks/active/20260402-intent-preserving-edits-lessons.md
New design/spec, phased migration plan, implementation notes, lessons, and known-issue guidance for moving from block-replacement to intent-preserving, character-level Yorkie edits.
Block Transformation Helpers
packages/docs/src/store/block-helpers.ts
New pure helpers and types (InlinePosition, InlineSegment) for offset resolution, delete/style segment decomposition, inline normalization, and immutable block transforms (insert/delete/split/merge/style, cloneBlock).
Public Exports
packages/docs/src/index.ts
Re-exports block-helpers functions and types (resolveOffset, resolveDeleteRange, resolveStyleRange, normalizeInlines, applyInsertText, applyDeleteText, applyInlineStyle as applyInlineStyleHelper, applySplitBlock, applyMergeBlocks, InlinePosition, InlineSegment).
DocStore Interface
packages/docs/src/store/store.ts
Extended DocStore interface with character-level methods: insertText, deleteText, applyStyle, splitBlock, mergeBlock (imports added: InlineStyle, BlockType).
Memory Store Implementation
packages/docs/src/store/memory.ts
MemDocStore adds block-level APIs (insertText, deleteText, applyStyle, splitBlock, mergeBlock) implemented via block-helpers.
Yorkie Store Implementation
packages/frontend/src/app/docs/yorkie-doc-store.ts
YorkieDocStore implements new methods performing helper-driven transforms and Yorkie tree.editByPath calls (character-level edits and block replacements), and updates cached doc in-place.
Document Model Delegation
packages/docs/src/model/document.ts
Doc methods (insertText, deleteText, applyInlineStyle, splitBlock, mergeBlocks) now delegate non-table-cell mutations to DocStore methods; table-cell logic retained in-method.
Tests
packages/docs/test/store/block-helpers.test.ts, packages/docs/test/store/memory.test.ts
New Vitest suites covering block-helpers (offset resolution, delete ranges, insert/delete/style/split/merge) and MemDocStore fine-grained operations and edge cases.
Dependency updates
packages/backend/package.json, packages/frontend/package.json
Yorkie dependency versions updated from 0.7.3-alpha to 0.7.3.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor/UI
    participant Doc as Doc
    participant Store as DocStore
    participant Helpers as block-helpers
    participant Yorkie as Yorkie Tree
    participant Cache as Local Cache

    Editor->>Doc: insertText(pos, text)
    rect rgba(100, 150, 200, 0.5)
    Note over Doc: check blockParentMap (table-cell?)
    end
    alt Non-table-cell
        Doc->>Store: insertText(blockId, offset, text)
        Store->>Helpers: applyInsertText(block, offset, text)
        Helpers-->>Store: updated Block
        alt Yorkie-backed
            Store->>Yorkie: tree.editByPath(...) (char/edit or block replace)
            Yorkie-->>Store: change event
            Store->>Cache: update cached doc.blocks (in-place)
        else Memory-backed
            Store->>Store: replace block in this.doc.blocks
        end
    else Table-cell
        Doc->>Doc: existing in-method cell edit flow
    end
    Store-->>Doc: void
    Doc-->>Editor: void
Loading
sequenceDiagram
    participant Editor as Editor/UI
    participant Doc as Doc
    participant Store as DocStore
    participant Helpers as block-helpers
    participant Yorkie as Yorkie Tree
    participant Cache as Local Cache

    Editor->>Doc: deleteText(pos, length)
    rect rgba(150, 100, 200, 0.5)
    Note over Doc: check blockParentMap (table-cell?)
    end
    alt Non-table-cell
        Doc->>Store: deleteText(blockId, offset, length)
        Store->>Helpers: resolveDeleteRange(block, offset, length)
        Helpers-->>Store: InlineSegment[]
        Note over Store: perform deletions (reverse inline order) and normalize
        Store->>Helpers: applyDeleteText(...)
        Helpers-->>Store: updated Block
        alt Yorkie-backed
            Store->>Yorkie: tree.editByPath(...) (char deletes / block replace)
            Yorkie-->>Store: change event
            Store->>Cache: update cached doc.blocks (in-place)
        else Memory-backed
            Store->>Store: replace block in this.doc.blocks
        end
    else Table-cell
        Doc->>Doc: existing in-method cell delete flow
    end
    Store-->>Doc: void
    Doc-->>Editor: void
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hop through chars, not hefty blocks,

I split and stitch with tiny knocks.
Helpers mind each style and space,
Store sends edits, Yorkie holds the place.
A nimble hop — intent preserved, hooray!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding intent-preserving Yorkie edits for the Docs editor across phases 1-3, which is the core objective of this multi-file, design-driven changeset.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/intent-preserving-edits

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 2, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 113.3s

Lane Status Duration
sheets:build ✅ pass 13.2s
docs:build ✅ pass 6.5s
verify:fast ✅ pass 55.1s
frontend:build ✅ pass 15.2s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.3s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 16.7s

Verification: verify:integration

Result: ✅ PASS

@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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 102-109: Update the doc to separate shipped Phase 1-3 behavior
from planned Yorkie work: change the table and nearby prose so editByPath is
shown as the character-level API used for text insert/delete, block replacement
is listed for style/structure changes (not styleByPath/splitByPath/mergeByPath),
and editBulkByPath remains a full-document write/undo-fallback; mark
styleByPath, splitByPath, mergeByPath and any *InCell variants as “future work”
(or update examples to use current block-replacement patterns), and add a short
note in the linked README that those APIs are not part of this PR; apply the
same edits to the other mentioned sections referencing
styleByPath/splitByPath/mergeByPath/editByPath/editBulkByPath so examples match
actual shipped behavior.

In `@docs/tasks/active/20260402-intent-preserving-edits-todo.md`:
- Line 3: Update the active task sheet to reflect the shipped phases by removing
or marking completed Phase 1-3 steps and updating the agent guidance that
currently prescribes the discarded four-segment Yorkie paths and functions
styleByPath / splitByPath / mergeByPath; specifically, edit the checklist items
so Phase 1-3 are checked off or moved to a "completed" section, and revise the
agentic-workers instruction block (the sentence referencing
superpowers:subagent-driven-development or superpowers:executing-plans) to only
include the remaining Phase 4-5 actionable steps and the correct current
APIs/approach.

In `@packages/docs/src/model/document.ts`:
- Around line 143-165: The deletion loop in the current code stops when an
inline's available chars equals 0 because offset isn't advanced across inline
boundaries; replace the manual while-loop with the shared block-level deletion
helper from packages/docs/src/store/block-helpers.ts (the same utility used
elsewhere) so deletions traverse inline boundaries correctly. Locate the code
around resolveOffset, normalizeInlines, and updateBlockInStore and call the
block-helpers' delete/remove-text-in-block helper (the function exported in
block-helpers.ts that handles multi-inline deletions) passing the block,
pos.offset and count, then call normalizeInlines(block) and
updateBlockInStore(pos.blockId, block).

In `@packages/docs/src/store/block-helpers.ts`:
- Around line 147-173: The split logic loses the block's cursorStyle when
normalizeInlines turns an empty inline array into a plain {} — preserve
cursorStyle from the original block (or from the side that still has style) when
assigning before.inlines and after.inlines; update the assignment after
normalizeInlines to detect an empty/{} result and copy block.cursorStyle (or
existing cursorStyle on before/after) into the resulting object so splits at run
boundaries keep the formatting; refer to normalizeInlines, before.inlines,
after.inlines, block.inlines and cursorStyle to locate and implement this
change.

In `@packages/docs/src/store/memory.ts`:
- Around line 167-173: In mergeBlock(blockId: string, nextBlockId: string)
ensure you reject merging a block with itself and reject non-adjacent merges:
check that blockId !== nextBlockId and that nextIndex === index + 1 (where index
and nextIndex are located via this.doc.blocks.findIndex), and throw a clear
Error when those guards fail; mirror the same guard logic in
YorkieDocStore.mergeBlock() / DocStore docs and add negative regression tests
for both "same-ID" and "non-adjacent nextBlockId" cases to prevent regressions.

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts`:
- Around line 487-505: deleteText currently issues only character-range edits
via tree.editByPath which leaves empty inline nodes in the Yorkie tree while
applyDeleteText() normalizes the cached block (removing empty inlines and
merging neighbors), causing divergence when treeToDocument() re-parses; fix by
replacing the whole block in deleteText using the same block-replacement pattern
as applyStyle: call tree.editByPath([blockIdx], [blockIdx + 1],
buildBlockNode(applyDeleteText(block, offset, length))) so the Yorkie tree
matches the normalized cache, or if you prefer minimal edits, after the
character-range deletes run targeted cleanup using mergeByPath()/editByPath() to
remove or merge empty inline nodes so the tree and cachedDoc remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd9db341-32f5-437a-ac95-4f8a810e5b96

📥 Commits

Reviewing files that changed from the base of the PR and between 85f7df9 and 57cf961.

📒 Files selected for processing (11)
  • docs/design/README.md
  • docs/design/docs-intent-preserving-edits.md
  • docs/tasks/active/20260402-intent-preserving-edits-todo.md
  • packages/docs/src/index.ts
  • packages/docs/src/model/document.ts
  • packages/docs/src/store/block-helpers.ts
  • packages/docs/src/store/memory.ts
  • packages/docs/src/store/store.ts
  • packages/docs/test/store/block-helpers.test.ts
  • packages/docs/test/store/memory.test.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts

Comment thread docs/design/docs-intent-preserving-edits.md Outdated
Comment thread docs/tasks/active/20260402-intent-preserving-edits-todo.md Outdated
Comment thread packages/docs/src/model/document.ts Outdated
Comment thread packages/docs/src/store/block-helpers.ts Outdated
Comment thread packages/docs/src/store/memory.ts
Comment thread packages/frontend/src/app/docs/yorkie-doc-store.ts

@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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
docs/tasks/active/20260402-intent-preserving-edits-todo.md (1)

887-932: ⚠️ Potential issue | 🟡 Minor

Correct Task 8 implementation to reflect block-level replacement approach.

This task is marked complete but the prescribed implementation (lines 907-912) uses tree.styleByPath(), which contradicts the PR summary stating that "style operations remain block-level LWW because Yorkie does not support text-range styling." Update the implementation code snippet to show the block replacement approach that was actually shipped.

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

In `@docs/tasks/active/20260402-intent-preserving-edits-todo.md` around lines 887
- 932, The current applyStyle implementation incorrectly calls tree.styleByPath
for each segment; instead implement the block-level LWW replace approach: use
getBlock(blockId) and resolveStyleRange(...) to get segments, build a new block
value by applying the serialized style (serializeInlineStyle(style)) to the
affected inline segments (merging/replacing inline segment style slices per the
segments), then inside this.doc.update replace the whole block at blockIdx
(found via findBlockIndex(blockId)) with the newly constructed block value
(e.g., set/replace the root.content entry at blockIdx) so styling is applied by
replacing the block rather than calling tree.styleByPath per-range.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/tasks/active/20260402-intent-preserving-edits-todo.md`:
- Line 1: Create a paired lessons file named
docs/tasks/active/20260402-intent-preserving-edits-lessons.md as required by the
coding guidelines; the new file should document learnings from the completed
Phase 1–3 implementation (referenced by the todo file title "Intent-Preserving
Yorkie Edits — Implementation Plan"), including justification for choosing block
replacement over Yorkie path-based methods for styling and structural edits, any
trade-offs, pitfalls encountered, and recommended next steps or follow-ups so
the todo and lessons files are paired and the rationale is clearly captured.
- Line 9: Update the tech stack line to accurately reflect the shipped
implementation: keep TypeScript, Vitest, and Yorkie Tree CRDT but remove
references to Yorkie methods that were not used (styleByPath, splitByPath,
mergeByPath) and state that only editByPath is used for character-level edits
while style and structural operations are performed via block-level replacement
due to Yorkie limitations; modify the tech stack description text accordingly to
mention block-level replacement for style/structural ops and the use of
editByPath for character edits.

---

Duplicate comments:
In `@docs/tasks/active/20260402-intent-preserving-edits-todo.md`:
- Around line 887-932: The current applyStyle implementation incorrectly calls
tree.styleByPath for each segment; instead implement the block-level LWW replace
approach: use getBlock(blockId) and resolveStyleRange(...) to get segments,
build a new block value by applying the serialized style
(serializeInlineStyle(style)) to the affected inline segments (merging/replacing
inline segment style slices per the segments), then inside this.doc.update
replace the whole block at blockIdx (found via findBlockIndex(blockId)) with the
newly constructed block value (e.g., set/replace the root.content entry at
blockIdx) so styling is applied by replacing the block rather than calling
tree.styleByPath per-range.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0094bccd-bb09-4a20-a0cc-4fdefb72fdb5

📥 Commits

Reviewing files that changed from the base of the PR and between 57cf961 and 31a32e4.

📒 Files selected for processing (1)
  • docs/tasks/active/20260402-intent-preserving-edits-todo.md

Comment thread docs/tasks/active/20260402-intent-preserving-edits-todo.md Outdated
Comment thread docs/tasks/active/20260402-intent-preserving-edits-todo.md Outdated
Address CodeRabbit review feedback:
- Use shared applyDeleteText helper in Doc.deleteText cell fallback
  path to fix cross-inline boundary deletion bug
- Switch YorkieDocStore.deleteText to block replacement to prevent
  empty inline nodes from diverging between tree and cache
- Add self-merge guard to mergeBlock in MemDocStore and YorkieDocStore
- Reflect that only editByPath is used; styleByPath/splitByPath/
  mergeByPath are documented as future work
- Fix path format references (3 levels, not 4)
- Add lessons-learned file for Phase 1-3
Instead of block replacement, use character-level editByPath for text
deletion and then remove any resulting empty inline nodes within the
same doc.update(). This preserves CRDT merge semantics for concurrent
deletions in the same block.

@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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/docs/src/model/document.ts (1)

229-248: ⚠️ Potential issue | 🟠 Major

Guard self-merges before the table-cell branch.

If blockId === nextBlockId for a cell-internal block, idx === nextIdx, so this path concatenates the block with itself and then splices that same entry out of cell.blocks. The top-level path now fails safely in the store; the cell path should do the same.

Suggested fix
  mergeBlocks(blockId: string, nextBlockId: string): void {
+    if (blockId === nextBlockId) {
+      throw new Error('Cannot merge a block with itself');
+    }
+
     const cellInfo = this._blockParentMap.get(blockId);
     if (cellInfo) {
       const tableBlock = this.getBlock(cellInfo.tableBlockId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/model/document.ts` around lines 229 - 248, In mergeBlocks,
guard against self-merges by checking if blockId === nextBlockId and returning
early before the table-cell branch; specifically, inside mergeBlocks (which uses
cellInfo, idx, nextIdx, cell.blocks, block and nextBlock) add a pre-check that
immediately returns when the two IDs are identical so you don't concatenate a
block with itself or splice the wrong entry for the table cell case.
♻️ Duplicate comments (2)
docs/design/docs-intent-preserving-edits.md (2)

10-13: ⚠️ Potential issue | 🟡 Minor

Separate Phase 4 cell edits from the shipped Phase 1-3 scope.

The summary/impact table still read as if same-cell intent-preserving edits already shipped, and this interface section lists *InCell methods as newly added. The current DocStore surface in this PR only adds the top-level text/style/split/merge methods, so these parts should be marked as Phase 4 future work instead.

Also applies to: 53-58, 175-200

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

In `@docs/design/docs-intent-preserving-edits.md` around lines 10 - 13, Update the
docs to clearly separate shipped Phase 1–3 features from future Phase 4 work:
change wording in the summary/impact table and the interface section so that
character-level in-cell editing (the `*InCell` methods and same-cell
intent-preserving edits) are labeled as Phase 4 future work rather than shipped;
ensure references to `DocStore` only describe the currently added top-level
methods (text/style/split/merge) and remove or reclassify any statements
implying `editByPath([idx],[idx+1], blockNode)` was replaced by Yorkie Tree in
the shipped scope; apply the same changes to the other occurrences noted (lines
~53–58 and ~175–200).

121-123: ⚠️ Potential issue | 🟡 Minor

Make the Yorkie text-path examples internally consistent.

Lines 121-123 and 129-131 describe a 3-segment text path, but this example still shows [blockIdx, inlineIdx, 0, charOffset]. One of these examples is stale, and readers will not know which path shape the current YorkieDocStore actually uses.

#!/bin/bash
set -euo pipefail

yorkie_file="$(fd 'yorkie-doc-store\.ts$' . | head -n1)"

echo "== Path examples in docs/design/docs-intent-preserving-edits.md =="
rg -n -C1 '\[blockIdx, inlineIdx|editByPath' docs/design/docs-intent-preserving-edits.md

if [[ -n "${yorkie_file}" ]]; then
  echo
  echo "== Current YorkieDocStore tree-edit usage =="
  rg -n -C2 'editByPath|styleByPath|splitByPath|mergeByPath' "${yorkie_file}"
fi

Also applies to: 129-131, 289-291

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

In `@docs/design/docs-intent-preserving-edits.md` around lines 121 - 123, The docs
show inconsistent Yorkie text-path shapes (some examples use 3 segments
[blockIdx, inlineIdx, charOffset] while others show a stale 4-segment form
[blockIdx, inlineIdx, 0, charOffset]); update the examples to match the actual
shape used by YorkieDocStore by choosing the 3-segment convention and replacing
any occurrences of the extra 0 segment in the examples at lines referenced (also
update the examples near editByPath/styleByPath/splitByPath/mergeByPath to use
the same form); ensure the text mentions hasTextChild() behavior consistently
and run a quick grep against YorkieDocStore (editByPath, styleByPath,
splitByPath, mergeByPath) to confirm the chosen path shape matches
implementation.
🧹 Nitpick comments (1)
docs/design/docs-intent-preserving-edits.md (1)

231-236: Add languages to the unlabeled fences.

Both fences trigger MD040 right now. text or plaintext is enough here.

Also applies to: 358-376

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

In `@docs/design/docs-intent-preserving-edits.md` around lines 231 - 236, The
markdown code fences that list the package tree (the blocks showing
"packages/docs/src/store/ ├── store.ts ..." and the similar block later) are
unlabeled and trigger MD040; add a language identifier such as "text" or
"plaintext" to both fenced blocks (the one containing the
packages/docs/src/store listing and the other at lines ~358-376) so the fences
become ```text ... ``` to satisfy the linter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 267-268: The docs list normalizeInlines with the wrong type
signature (Block -> Block); update the documentation to the actual signature
used in code: normalizeInlines(inlines: Inline[]): Inline[] and mention its
purpose (merge adjacent Inline entries with identical styles) so readers see the
correct API shape and intent; locate references to normalizeInlines and replace
the incorrect Block->Block declaration with the Inline[] -> Inline[] form.

---

Outside diff comments:
In `@packages/docs/src/model/document.ts`:
- Around line 229-248: In mergeBlocks, guard against self-merges by checking if
blockId === nextBlockId and returning early before the table-cell branch;
specifically, inside mergeBlocks (which uses cellInfo, idx, nextIdx,
cell.blocks, block and nextBlock) add a pre-check that immediately returns when
the two IDs are identical so you don't concatenate a block with itself or splice
the wrong entry for the table cell case.

---

Duplicate comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 10-13: Update the docs to clearly separate shipped Phase 1–3
features from future Phase 4 work: change wording in the summary/impact table
and the interface section so that character-level in-cell editing (the `*InCell`
methods and same-cell intent-preserving edits) are labeled as Phase 4 future
work rather than shipped; ensure references to `DocStore` only describe the
currently added top-level methods (text/style/split/merge) and remove or
reclassify any statements implying `editByPath([idx],[idx+1], blockNode)` was
replaced by Yorkie Tree in the shipped scope; apply the same changes to the
other occurrences noted (lines ~53–58 and ~175–200).
- Around line 121-123: The docs show inconsistent Yorkie text-path shapes (some
examples use 3 segments [blockIdx, inlineIdx, charOffset] while others show a
stale 4-segment form [blockIdx, inlineIdx, 0, charOffset]); update the examples
to match the actual shape used by YorkieDocStore by choosing the 3-segment
convention and replacing any occurrences of the extra 0 segment in the examples
at lines referenced (also update the examples near
editByPath/styleByPath/splitByPath/mergeByPath to use the same form); ensure the
text mentions hasTextChild() behavior consistently and run a quick grep against
YorkieDocStore (editByPath, styleByPath, splitByPath, mergeByPath) to confirm
the chosen path shape matches implementation.

---

Nitpick comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 231-236: The markdown code fences that list the package tree (the
blocks showing "packages/docs/src/store/ ├── store.ts ..." and the similar block
later) are unlabeled and trigger MD040; add a language identifier such as "text"
or "plaintext" to both fenced blocks (the one containing the
packages/docs/src/store listing and the other at lines ~358-376) so the fences
become ```text ... ``` to satisfy the linter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bd5a5dc-4f2b-429b-9bf2-a032ff79b726

📥 Commits

Reviewing files that changed from the base of the PR and between 31a32e4 and b28b86d.

📒 Files selected for processing (6)
  • docs/design/docs-intent-preserving-edits.md
  • docs/tasks/active/20260402-intent-preserving-edits-lessons.md
  • docs/tasks/active/20260402-intent-preserving-edits-todo.md
  • packages/docs/src/model/document.ts
  • packages/docs/src/store/memory.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/tasks/active/20260402-intent-preserving-edits-lessons.md
  • docs/tasks/active/20260402-intent-preserving-edits-todo.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/docs/src/store/memory.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts

Comment thread docs/design/docs-intent-preserving-edits.md Outdated

@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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
docs/design/docs-intent-preserving-edits.md (1)

268-269: ⚠️ Potential issue | 🟡 Minor

The normalizeInlines signature still shows Block → Block.

This was flagged in a previous review as incorrect. The actual implementation takes Inline[] and returns Inline[], not Block → Block.

📝 Proposed fix
-// Merge adjacent inlines with identical styles
-function normalizeInlines(block: Block): Block;
+// Merge adjacent inlines with identical styles
+function normalizeInlines(inlines: Inline[]): Inline[];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/docs-intent-preserving-edits.md` around lines 268 - 269, The
function signature for normalizeInlines is incorrect in the docs: it currently
shows "function normalizeInlines(block: Block): Block" but the implementation
operates on Inline[] and returns Inline[]; update the declaration in the docs to
"function normalizeInlines(inlines: Inline[]): Inline[]" (or equivalent wording)
so the documented signature matches the actual function name normalizeInlines
and its parameter/return types (Inline[]).
🧹 Nitpick comments (3)
docs/design/docs-intent-preserving-edits.md (2)

360-378: Add language specifier to fenced code block.

The migration strategy block lacks a language identifier. Consider adding text for consistency.

♻️ Proposed fix
-```
+```text
 Phase 1: Doc.insertText(), Doc.deleteText()

As per coding guidelines, well-formed markdown improves documentation quality.

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

In `@docs/design/docs-intent-preserving-edits.md` around lines 360 - 378, The
fenced code block listing the migration phases (the block starting with "Phase
1: Doc.insertText(), Doc.deleteText()" and containing Phase 1–5) is missing a
language specifier; update the opening fence from ``` to ```text so the block
becomes a well-formed labeled code fence (e.g., for the block that includes
Doc.insertText(), store.insertText(), Doc.applyInlineStyle() and the Phase
3/Phase 4/Phase 5 entries).

232-237: Add language specifier to fenced code block.

The fenced code block showing the directory structure lacks a language identifier. Consider adding text or an appropriate identifier for consistent formatting.

♻️ Proposed fix
-```
+```text
 packages/docs/src/store/
   ├── store.ts              # DocStore interface

As per coding guidelines, well-formed markdown improves documentation quality.

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

In `@docs/design/docs-intent-preserving-edits.md` around lines 232 - 237, The
fenced code block showing the directory listing in
docs/design/docs-intent-preserving-edits.md is missing a language specifier;
update the opening triple backticks to include a language (e.g., use ```text) so
the block renders consistently—apply this change to the block that lists
packages/docs/src/store/ with entries like store.ts, memory.ts, and
block-helpers.ts.
packages/frontend/src/app/docs/yorkie-doc-store.ts (1)

630-630: Make the missing-block error more diagnostic.

Line 630 currently throws a generic message; including both IDs will speed up production debugging.

As per coding guidelines, "Use meaningful error messages that help with debugging".

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

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts` at line 630, The thrown
error at the check using blockIdx and nextIdx is too generic; update the throw
in the block-check (the line that currently does `if (blockIdx === -1 || nextIdx
=== -1) throw new Error('Block not found')`) to include the offending
identifiers (blockIdx and nextIdx) and any nearby context identifiers available
in the scope (e.g., blockId/nextId or a document/operation id) so the error
message is diagnostic and shows the values that caused the failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 286-295: The example uses root.content.editByPath with an
incorrect 4-element path; remove the extra 0 so both call sites pass a 3-element
path [blockIdx, inlineIndex, charOffset]. Update the code around resolveOffset,
findBlockIndex and the this.doc.update / root.content.editByPath example to use
[blockIdx, inlineIndex, charOffset] for both the start and end path arguments to
match the documented format.

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts`:
- Around line 625-650: The mergeBlock method currently allows merging
non-adjacent blocks; update mergeBlock(blockId: string, nextBlockId: string) to
enforce that nextBlockId is exactly the immediate successor of blockId by
checking that nextIdx === blockIdx + 1 (after locating blockIdx and nextIdx via
getDocument()); if not, throw a clear Error('Can only merge adjacent blocks').
Also remove or simplify the unnecessary deleteIdx calculation and ensure
tree.editByPath uses blockIdx and blockIdx+1 for deletion so the DOM and
cachedDoc updates stay consistent with the adjacency check.

---

Duplicate comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 268-269: The function signature for normalizeInlines is incorrect
in the docs: it currently shows "function normalizeInlines(block: Block): Block"
but the implementation operates on Inline[] and returns Inline[]; update the
declaration in the docs to "function normalizeInlines(inlines: Inline[]):
Inline[]" (or equivalent wording) so the documented signature matches the actual
function name normalizeInlines and its parameter/return types (Inline[]).

---

Nitpick comments:
In `@docs/design/docs-intent-preserving-edits.md`:
- Around line 360-378: The fenced code block listing the migration phases (the
block starting with "Phase 1: Doc.insertText(), Doc.deleteText()" and containing
Phase 1–5) is missing a language specifier; update the opening fence from ``` to
```text so the block becomes a well-formed labeled code fence (e.g., for the
block that includes Doc.insertText(), store.insertText(), Doc.applyInlineStyle()
and the Phase 3/Phase 4/Phase 5 entries).
- Around line 232-237: The fenced code block showing the directory listing in
docs/design/docs-intent-preserving-edits.md is missing a language specifier;
update the opening triple backticks to include a language (e.g., use ```text) so
the block renders consistently—apply this change to the block that lists
packages/docs/src/store/ with entries like store.ts, memory.ts, and
block-helpers.ts.

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts`:
- Line 630: The thrown error at the check using blockIdx and nextIdx is too
generic; update the throw in the block-check (the line that currently does `if
(blockIdx === -1 || nextIdx === -1) throw new Error('Block not found')`) to
include the offending identifiers (blockIdx and nextIdx) and any nearby context
identifiers available in the scope (e.g., blockId/nextId or a document/operation
id) so the error message is diagnostic and shows the values that caused the
failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a383dd50-1354-4ed5-9bf8-79ff413bfa79

📥 Commits

Reviewing files that changed from the base of the PR and between b28b86d and 56cc894.

📒 Files selected for processing (3)
  • docs/design/docs-intent-preserving-edits.md
  • docs/tasks/active/20260402-intent-preserving-edits-lessons.md
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/tasks/active/20260402-intent-preserving-edits-lessons.md

Comment thread docs/design/docs-intent-preserving-edits.md Outdated
Comment thread packages/frontend/src/app/docs/yorkie-doc-store.ts
Includes fix for tree undo/redo with mixed character-level and
block-level operations (yorkie-team/yorkie-js-sdk#1196), which is
needed for Phase 5 Yorkie-native undo/redo.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/frontend/package.json (1)

50-77: ⚠️ Potential issue | 🟠 Major

Move @yorkie-js/sdk from devDependencies to dependencies.

Runtime code imports @yorkie-js/sdk in packages/frontend/src/app/docs/yorkie-doc-store.ts (line 2) and packages/frontend/src/types/docs-document.ts (line 1). Currently, the package is only in devDependencies, which will break production installs using pnpm install --prod.

Proposed fix
   "dependencies": {
     "@dnd-kit/core": "^6.3.1",
     "@dnd-kit/modifiers": "^9.0.0",
     "@dnd-kit/sortable": "^10.0.0",
     "@dnd-kit/utilities": "^3.2.2",
     "@wafflebase/docs": "workspace:*",
     "@wafflebase/sheets": "workspace:*",
     "@yorkie-js/react": "0.7.3",
+    "@yorkie-js/sdk": "0.7.3",
     "class-variance-authority": "^0.7.1",
   "devDependencies": {
-    "@yorkie-js/sdk": "0.7.3",
     "@types/node": "^22.14.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/package.json` around lines 50 - 77, The dependency
`@yorkie-js/sdk` is used at runtime (imported in
packages/frontend/src/app/docs/yorkie-doc-store.ts and
packages/frontend/src/types/docs-document.ts) but is listed under
devDependencies; move "@yorkie-js/sdk": "0.7.3" out of devDependencies into the
top-level dependencies object in packages/frontend/package.json so it is
installed in production builds (remove the entry from devDependencies and add
the same version string under dependencies).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/frontend/package.json`:
- Around line 50-77: The dependency `@yorkie-js/sdk` is used at runtime (imported
in packages/frontend/src/app/docs/yorkie-doc-store.ts and
packages/frontend/src/types/docs-document.ts) but is listed under
devDependencies; move "@yorkie-js/sdk": "0.7.3" out of devDependencies into the
top-level dependencies object in packages/frontend/package.json so it is
installed in production builds (remove the entry from devDependencies and add
the same version string under dependencies).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f44801a2-a710-48e8-b39f-8b36c04b370e

📥 Commits

Reviewing files that changed from the base of the PR and between 56cc894 and c14aeff.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/backend/package.json
  • packages/frontend/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/backend/package.json

- Condense design doc to final architecture decisions only
- Split monolithic task file into Phase 1-3 (completed), Phase 4
  (table cells), and Phase 5 (undo/redo)
- Update tasks index
When splitting at the end of bold/italic text, the empty new block
now inherits the style at the split point instead of falling back
to plain {}.
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