Skip to content

Add intent-preserving block/cell attribute edits (Phase 5)#153

Merged
hackerwins merged 10 commits into
mainfrom
feat/block-attribute-intent-preserving
Apr 23, 2026
Merged

Add intent-preserving block/cell attribute edits (Phase 5)#153
hackerwins merged 10 commits into
mainfrom
feat/block-attribute-intent-preserving

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add setBlockType, applyBlockStyle, applyCellStyle, insertImageInline to DocStore interface
  • YorkieDocStore implements via styleByPath/editByPath instead of full block/cell replacement (LWW)
  • Route Doc class through new store methods, removing updateBlockInStore and findRootTableId
  • Reorganize design doc phases 5–9, documenting all remaining LWW operations by migration strategy

Changes

  • DocStore interface: 4 new methods (setBlockType, applyBlockStyle, applyCellStyle, insertImageInline)
  • YorkieDocStore: intent-preserving implementations using styleByPath/editByPath
  • MemDocStore: matching implementations using block-helpers
  • Doc class: -82 lines — delegates to store, removes LWW routing helpers
  • Design doc: Phase table renumbered 5–9, remaining LWW operations cataloged with call sites

Test plan

  • pnpm verify:fast — all 576 docs tests + frontend/backend/sheets tests pass
  • Manual: change block type (paragraph → heading) while peer edits text — no conflict
  • Manual: apply cell background while peer types in cell — no conflict

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added document store methods to change block types, apply block styling, apply cell styling, and insert inline images
    • Exposed new helper function for inline content insertion
  • Tests

    • Added concurrent integration tests validating simultaneous text and structural edits converge correctly across clients
    • Expanded unit test coverage for new editing operations
  • Documentation

    • Updated design documentation with intent-preserving edit migration plans and phase progression
    • Archived completed task documentation

hackerwins and others added 7 commits April 23, 2026 11:21
Route block type and style changes through the store interface
instead of full block replacement (updateBlockInStore). YorkieDocStore
uses styleByPath for CRDT-safe attribute updates, eliminating LWW
conflicts when concurrent users change block type/style while others
edit text in the same block.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Route cell style changes (background, borders, alignment) through
the store interface using styleByPath on the cell node, instead of
replacing the entire cell via updateTableCell. This preserves
concurrent text edits within the cell during style changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Route image inline insertion through the store interface using
editByPath at inline level, instead of full block replacement.
Also removes the now-unused updateBlockInStore and findRootTableId
private methods from Doc class — all editing operations now go
through intent-preserving store methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Document the migration of setBlockType, applyBlockStyle,
applyCellStyle, and insertImageInline from LWW to styleByPath/
editByPath. List remaining LWW operations for future work.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Categorize remaining full-replacement operations into four groups:
A) cell span attributes (styleByPath), B) table-level attributes
(styleByPath on block), C) cell structural changes (editByPath),
D) direct block replacement. Include call sites and priority order.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Renumber Phase 4b → 5 (shipped), add Phase 6 (cell span attrs),
Phase 7 (table-level attrs), Phase 8 (cell structural edits),
and move undo/redo to Phase 9 as the final planned phase.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Archive completed Phase 4 (table cells) and Phase 5 (block attrs)
task files. Update undo/redo task to reflect Phase 9 numbering
and mark Task 4 items completed by Phase 4-5 work.

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

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hackerwins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 24 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 48 minutes and 24 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f87822d-d35d-4844-8ed6-d805aa2ae4f0

📥 Commits

Reviewing files that changed from the base of the PR and between d164519 and 39b3db0.

📒 Files selected for processing (6)
  • docs/tasks/README.md
  • docs/tasks/active/20260403-intent-preserving-phase9-undo-redo-todo.md
  • packages/docs/src/model/document.ts
  • packages/docs/src/store/store.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
  • packages/frontend/tests/app/docs/yorkie-doc-store.test.ts
📝 Walkthrough

Walkthrough

This PR implements intent-preserving edits for block attributes and cell styling by adding new store methods (setBlockType, applyBlockStyle, applyCellStyle, insertImageInline), delegating Doc mutations to DocStore, and updating both memory and Yorkie store implementations with corresponding logic.

Changes

Cohort / File(s) Summary
Documentation Updates
docs/design/docs/docs-intent-preserving-edits.md, docs/tasks/README.md, docs/tasks/active/20260403-intent-preserving-phase5-undo-redo-todo.md, docs/tasks/archive/2026/04/*
Phase progression documentation and task archival for block attribute intent-preserving migrations; updates planned incremental rollout phases and enumerates mappings from full-block replacements to path-based edits.
Store Interface & Memory Implementation
packages/docs/src/store/store.ts, packages/docs/src/store/memory.ts
Extends DocStore interface and MemDocStore with four new intent-preserving methods: setBlockType (manages type-specific attributes), applyBlockStyle (merges block styling), applyCellStyle (applies table cell styling), and insertImageInline (inserts inline content at offset).
Doc Model Refactoring
packages/docs/src/model/document.ts, packages/docs/src/index.ts
Delegates block/cell mutations from Doc class to DocStore instead of direct in-place mutations; removes private helpers for block updates and table persistence; exports applyInsertInline helper from main API.
Frontend Yorkie Implementation
packages/frontend/src/app/docs/yorkie-doc-store.ts
Implements four new store methods in YorkieDocStore using path-based Yorkie tree operations (styleByPath, inline tree manipulation); handles type-specific attribute serialization, style normalization, and tree node editing for intent-preserving behavior.
Test Coverage
packages/frontend/tests/app/docs/yorkie-doc-store.test.ts, packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts
Adds comprehensive unit and concurrent-edit integration tests verifying correct block type transitions with attribute management, style application/merging across cells, inline image insertion at various offsets, and convergence of structural mutations alongside text edits.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Doc
    participant DocStore
    participant YorkieDocStore
    participant Yorkie Tree

    Client->>Doc: setBlockType(blockId, type)
    Doc->>DocStore: setBlockType(blockId, type, opts)
    DocStore->>YorkieDocStore: setBlockType(blockId, type, opts)
    YorkieDocStore->>YorkieDocStore: Serialize/normalize attributes
    YorkieDocStore->>Yorkie Tree: styleByPath() on block node
    Yorkie Tree-->>YorkieDocStore: Update applied
    YorkieDocStore->>YorkieDocStore: Update cached block
    YorkieDocStore-->>DocStore: void
    DocStore-->>Doc: void
    Doc-->>Client: void

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Yorkie Tree: Inline Image Insertion Flow
    Client->>Doc: insertImageInline(blockId, offset, inline)
    Doc->>DocStore: insertImageInline(blockId, offset, inline)
    DocStore->>YorkieDocStore: insertImageInline(blockId, offset, inline)
    YorkieDocStore->>Yorkie Tree: Edit/split inline nodes in tree
    Yorkie Tree-->>YorkieDocStore: Tree structure modified
    YorkieDocStore->>YorkieDocStore: applyInsertInline() on cached block
    YorkieDocStore-->>DocStore: void
    DocStore-->>Doc: void
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hop! Intent-preserving hops so true,
No more full blocks—just paths on view,
Block types shift, styles blend and glow,
Images inline, through layers they flow!
Our CRDT dance, now fine-grained and right,
Convergence awaits—what a treat! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'Add intent-preserving block/cell attribute edits (Phase 5)' clearly and concisely summarizes the main change: introducing intent-preserving edit APIs for block and cell attributes. It directly corresponds to the primary objective of the PR.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/block-attribute-intent-preserving

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

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 120.0s

Lane Status Duration
sheets:build ✅ pass 13.3s
docs:build ✅ pass 8.1s
verify:fast ✅ pass 61.0s
frontend:build ✅ pass 15.7s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 14.9s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

hackerwins and others added 2 commits April 23, 2026 11:49
Unit tests (15 cases): setBlockType, applyBlockStyle, applyCellStyle,
insertImageInline — including cell-internal block variants.

Concurrent integration tests (3 cases): block type change + text
insert, block style + text insert, cell style + text insert in cell
— verifying both operations preserved without LWW conflict.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Use removeStyleByPath to strip stale headingLevel/listKind/listLevel
  when changing block type. Prevents convergence bug where remote
  clients see phantom attributes from the previous type.
- Remove unnecessary serializeBlockStyle from setBlockType attrs —
  only type-specific attributes need to change.
- Fix applyCellStyle to mutate cache after Yorkie update, matching
  the pattern used by all other store methods.
- Add 3 tests: heading→list-item stale removal, list-item→paragraph
  stale removal, heading level change on existing heading.

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

@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: 5

🧹 Nitpick comments (4)
packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts (2)

500-555: Placement nit: setBlockType / applyBlockStyle tests aren't table-cell tests.

Both cases operate on a plain paragraph block (makeBlock('HelloWorld')) but are nested inside describe('YorkieDocStore concurrent table cell edits', …) (line 425). Consider moving them into the YorkieDocStore concurrent inline styling suite (or a new concurrent block attribute edits suite) so the describe label reflects what's actually exercised.

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

In `@packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts`
around lines 500 - 555, These two tests ("concurrent setBlockType and text
insert should both be preserved" and "concurrent applyBlockStyle and text insert
should both be preserved") are misplaced under the describe labeled
"YorkieDocStore concurrent table cell edits"; cut these it blocks out of that
describe and move them into the existing "YorkieDocStore concurrent inline
styling" describe (or create a new describe "YorkieDocStore concurrent block
attribute edits") so the describe label matches the behavior being tested;
ensure you preserve the surrounding setup/teardown context (use of makeBlock and
createTwoUserDocs and ctx.cleanup) and keep the it titles and assertions intact.

500-588: Strengthen convergence assertions with a length check.

Existing cell tests in this file (e.g., line 493: assert.equal(textA.length, 12, 'Insert should be preserved');) assert final length in addition to includes. The three new tests only check includes('XX'|'YY'|'ZZ'), which would pass even if insertion were duplicated or characters from the original were lost. Adding a length assertion (e.g., 'HelloWorld'.length + 2 for XX/YY, 'CellText'.length + 2 for ZZ) would catch regressions in the intent-preserving path where that's exactly the property we care about.

♻️ Example: add length check to the setBlockType test
       const textA = docA.blocks[0].inlines.map((i) => i.text).join('');
       const textB = docB.blocks[0].inlines.map((i) => i.text).join('');
       assert.equal(textA, textB, 'Text should converge');
+      assert.equal(textA.length, 'HelloWorld'.length + 2, `Expected 12 chars, got "${textA}"`);
       assert.ok(textA.includes('XX'), `Inserted text "XX" should be preserved, got "${textA}"`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts`
around lines 500 - 588, Add explicit final-length assertions to the three new
tests to ensure the inserted two characters weren't duplicated or lost: in the
"concurrent setBlockType and text insert should both be preserved" and
"concurrent applyBlockStyle and text insert should both be preserved" tests
assert that textA.length and textB.length equal makeBlock('HelloWorld') length
(10) + 2 => 12 (use the local variable block to compute or hardcode 12); in the
"concurrent applyCellStyle and text insert in same cell should both be
preserved" test assert textA.length and textB.length equal cellBlock original
text length (CellText = 8) + 2 => 10 (use cellBlock to compute or hardcode 10);
place these assertions alongside the existing includes(...) checks after
computing textA/textB so failures show both content and length mismatches,
referencing ctx.storeA/ctx.storeB and the local block/cellBlock variables to
locate the assertions.
docs/tasks/archive/2026/04/20260423-block-attr-intent-preserving-lessons.md (1)

1-3: Placeholder lessons file — no review needed.

Consider populating this before archival (e.g., takeaways from styleByPath vs full-block replacement, stale-attribute handling in setBlockType, cache-order fix in applyCellStyle) so the archive entry is useful to future work.

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

In `@docs/tasks/archive/2026/04/20260423-block-attr-intent-preserving-lessons.md`
around lines 1 - 3, This is a placeholder lessons file; before archiving,
populate it with concise takeaways from recent edits — summarize pros/cons of
styleByPath versus full-block replacement, note stale-attribute handling in
setBlockType, and document the cache-order bug and fix in applyCellStyle so
future readers can find the rationale and patterns used; add short examples or
links to the relevant functions (styleByPath, setBlockType, applyCellStyle) and
a brief “lessons learned” bullet list.
packages/frontend/tests/app/docs/yorkie-doc-store.test.ts (1)

835-1037: LGTM — thorough coverage for the new intent-preserving methods.

Tests for setBlockType (including stale-attribute clearing via removeStyleByPath), applyBlockStyle, applyCellStyle, and insertImageInline exercise the main paths including cell-internal blocks. The hoisted makeTableWithText helper is cleanly reused.

One optional coverage gap you may want to add later: insertImageInline at the end of a block's text (offset === totalText.length), which exercises a boundary in resolveBlockNodeOffset + split-inline path.

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

In `@packages/frontend/tests/app/docs/yorkie-doc-store.test.ts` around lines 835 -
1037, Add a boundary test that inserts an image inline at the end of a block's
text (offset === totalText.length) to exercise resolveBlockNodeOffset and the
split-inline path used by insertImageInline; create a block via makeBlock (or
reuse existing helper), setDocument, call store.insertImageInline(block.id,
totalTextLength, { text: '\uFFFC', style: { image: {...} } }), then assert the
final inlines/text and that the image inline exists — this will cover the edge
case the reviewer mentioned.
🤖 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/20260403-intent-preserving-phase5-undo-redo-todo.md`:
- Line 1: The file slug and README entry are out of sync with the new heading:
rename the file `intent-preserving-phase5-undo-redo-todo.md` to match the
heading (suggested: `20260403-intent-preserving-phase9-undo-redo-todo.md`),
update any internal references (e.g., the entry currently listed in
docs/tasks/README.md as "intent preserving phase5 undo redo"), and then
regenerate the tasks index by running the project script (`pnpm tasks:index`) so
the slug and index/search entries reflect the Phase 9 name.

In `@docs/tasks/README.md`:
- Line 22: Update the stale display name and optionally the slug to match the
renamed heading in the linked file: change the table row text "intent preserving
phase5 undo redo (2026-04-03)" to "Phase 9: Yorkie-Native Undo/Redo
(2026-04-03)" and consider renaming the referenced filename
"20260403-intent-preserving-phase5-undo-redo-todo.md" to a matching slug like
"20260403-phase9-yorkie-native-undo-redo-todo.md" so the entry in
docs/tasks/README.md and the referenced file name are consistent with the
heading in
docs/tasks/active/20260403-intent-preserving-phase5-undo-redo-todo.md.

In `@packages/docs/src/model/document.ts`:
- Around line 209-213: Remove the internal snapshot call in insertImageInline:
delete the this.store.snapshot() invocation inside the insertImageInline method
so it simply delegates to this.store.insertImageInline(blockId, offset,
imageInline) and then calls this.refresh(); this makes insertImageInline
consistent with applyBlockStyle, setBlockType, and applyCellStyle (which rely on
the editor layer to call docStore.snapshot() once) and avoids creating a
duplicate undo unit.

In `@packages/frontend/src/app/docs/yorkie-doc-store.ts`:
- Around line 1641-1665: applyCellStyle currently merges new style into the
cached cell and writes serialized attributes via serializeCellStyle and
styleByPath, but serializeCellStyle omits falsy attributes so attributes set to
undefined never get cleared from the Yorkie Tree causing a cache/tree mismatch;
fix by detecting keys in the incoming style that are explicitly undefined after
computing merged (or by inspecting style), and for each such attribute call the
corresponding removeStyleByPath (same pattern used in setBlockType) instead
of/in addition to styleByPath so the tree entries are removed; update
applyCellStyle to compute attrs for styleByPath for truthy values and call
removeStyleByPath for attributes present in style with undefined to keep cache
and tree in sync.

---

Nitpick comments:
In `@docs/tasks/archive/2026/04/20260423-block-attr-intent-preserving-lessons.md`:
- Around line 1-3: This is a placeholder lessons file; before archiving,
populate it with concise takeaways from recent edits — summarize pros/cons of
styleByPath versus full-block replacement, note stale-attribute handling in
setBlockType, and document the cache-order bug and fix in applyCellStyle so
future readers can find the rationale and patterns used; add short examples or
links to the relevant functions (styleByPath, setBlockType, applyCellStyle) and
a brief “lessons learned” bullet list.

In `@packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts`:
- Around line 500-555: These two tests ("concurrent setBlockType and text insert
should both be preserved" and "concurrent applyBlockStyle and text insert should
both be preserved") are misplaced under the describe labeled "YorkieDocStore
concurrent table cell edits"; cut these it blocks out of that describe and move
them into the existing "YorkieDocStore concurrent inline styling" describe (or
create a new describe "YorkieDocStore concurrent block attribute edits") so the
describe label matches the behavior being tested; ensure you preserve the
surrounding setup/teardown context (use of makeBlock and createTwoUserDocs and
ctx.cleanup) and keep the it titles and assertions intact.
- Around line 500-588: Add explicit final-length assertions to the three new
tests to ensure the inserted two characters weren't duplicated or lost: in the
"concurrent setBlockType and text insert should both be preserved" and
"concurrent applyBlockStyle and text insert should both be preserved" tests
assert that textA.length and textB.length equal makeBlock('HelloWorld') length
(10) + 2 => 12 (use the local variable block to compute or hardcode 12); in the
"concurrent applyCellStyle and text insert in same cell should both be
preserved" test assert textA.length and textB.length equal cellBlock original
text length (CellText = 8) + 2 => 10 (use cellBlock to compute or hardcode 10);
place these assertions alongside the existing includes(...) checks after
computing textA/textB so failures show both content and length mismatches,
referencing ctx.storeA/ctx.storeB and the local block/cellBlock variables to
locate the assertions.

In `@packages/frontend/tests/app/docs/yorkie-doc-store.test.ts`:
- Around line 835-1037: Add a boundary test that inserts an image inline at the
end of a block's text (offset === totalText.length) to exercise
resolveBlockNodeOffset and the split-inline path used by insertImageInline;
create a block via makeBlock (or reuse existing helper), setDocument, call
store.insertImageInline(block.id, totalTextLength, { text: '\uFFFC', style: {
image: {...} } }), then assert the final inlines/text and that the image inline
exists — this will cover the edge case the reviewer mentioned.
🪄 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: 398bbda4-204f-48dc-a474-a3645a25802a

📥 Commits

Reviewing files that changed from the base of the PR and between dcfebb5 and d164519.

📒 Files selected for processing (14)
  • docs/design/docs/docs-intent-preserving-edits.md
  • docs/tasks/README.md
  • docs/tasks/active/20260403-intent-preserving-phase5-undo-redo-todo.md
  • docs/tasks/archive/2026/04/20260403-intent-preserving-phase4-table-cells-todo.md
  • docs/tasks/archive/2026/04/20260423-block-attr-intent-preserving-lessons.md
  • docs/tasks/archive/2026/04/20260423-block-attr-intent-preserving-todo.md
  • docs/tasks/archive/README.md
  • packages/docs/src/index.ts
  • packages/docs/src/model/document.ts
  • packages/docs/src/store/memory.ts
  • packages/docs/src/store/store.ts
  • packages/frontend/src/app/docs/yorkie-doc-store.ts
  • packages/frontend/tests/app/docs/yorkie-doc-store-concurrent.integration.ts
  • packages/frontend/tests/app/docs/yorkie-doc-store.test.ts

@@ -1,11 +1,11 @@
# Intent-Preserving Edits — Phase 5: Yorkie-Native Undo/Redo
# Intent-Preserving Edits — Phase 9: Yorkie-Native Undo/Redo

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Filename/slug out of sync with renumbered phase.

Heading is now "Phase 9: Yorkie-Native Undo/Redo", but the filename slug remains intent-preserving-phase5-undo-redo-todo.md and docs/tasks/README.md line 22 still lists it as "intent preserving phase5 undo redo". Consider renaming the file (e.g., 20260403-intent-preserving-phase9-undo-redo-todo.md) and regenerating the index with pnpm tasks:index so search/navigation by slug doesn't mislead readers cross-referencing the renumbered design doc.

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

In `@docs/tasks/active/20260403-intent-preserving-phase5-undo-redo-todo.md` at
line 1, The file slug and README entry are out of sync with the new heading:
rename the file `intent-preserving-phase5-undo-redo-todo.md` to match the
heading (suggested: `20260403-intent-preserving-phase9-undo-redo-todo.md`),
update any internal references (e.g., the entry currently listed in
docs/tasks/README.md as "intent preserving phase5 undo redo"), and then
regenerate the tasks index by running the project script (`pnpm tasks:index`) so
the slug and index/search entries reflect the Phase 9 name.

Comment thread docs/tasks/README.md Outdated
Comment thread packages/docs/src/model/document.ts
Comment thread packages/frontend/src/app/docs/yorkie-doc-store.ts
Comment on lines +1641 to +1665
applyCellStyle(
tableBlockId: string, rowIndex: number, colIndex: number,
style: Partial<CellStyle>,
): void {
const tablePath = this.resolveTableTreePath(tableBlockId);
const currentDoc = this.getDocument();
const block = this.resolveTableBlock(tablePath, currentDoc);
const cell = block.tableData!.rows[rowIndex].cells[colIndex];
const merged = { ...cell.style, ...style };

// Build serialized attributes for the cell node
const attrs = serializeCellStyle({ ...cell, style: merged });

this.doc.update((root) => {
const tree = root.content;
if (!tree || typeof tree.getRootTreeNode !== 'function') return;
tree.styleByPath([...tablePath, rowIndex, colIndex], attrs);
});

// Update cache after Yorkie update succeeds
cell.style = merged;

this.cachedDoc = currentDoc;
this.dirty = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

applyCellStyle cannot clear cell attributes.

serializeCellStyle gates each attribute with truthy checks (e.g. if (s.backgroundColor)) and styleByPath merges rather than replaces, so calling applyCellStyle(..., { backgroundColor: undefined }) will leave the prior backgroundColor attribute in the Yorkie Tree even though the merged cache has undefined. This produces a cache/tree mismatch the next time getDocument() re-parses from the tree (e.g. after a remote change).

If clearing is not a supported use case, consider tightening the JSDoc on DocStore.applyCellStyle to say "additive only"; otherwise pair the write with removeStyleByPath for attributes explicitly set to undefined in style (mirroring the pattern used in setBlockType).

🤖 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` around lines 1641 - 1665,
applyCellStyle currently merges new style into the cached cell and writes
serialized attributes via serializeCellStyle and styleByPath, but
serializeCellStyle omits falsy attributes so attributes set to undefined never
get cleared from the Yorkie Tree causing a cache/tree mismatch; fix by detecting
keys in the incoming style that are explicitly undefined after computing merged
(or by inspecting style), and for each such attribute call the corresponding
removeStyleByPath (same pattern used in setBlockType) instead of/in addition to
styleByPath so the tree entries are removed; update applyCellStyle to compute
attrs for styleByPath for truthy values and call removeStyleByPath for
attributes present in style with undefined to keep cache and tree in sync.

- Handle end-of-inline boundary in insertImageInline: insert after
  current inline without splitting, avoiding phantom empty inline
  that causes tree/cache divergence.
- Remove duplicate store.snapshot() in Doc.insertImageInline —
  editor layer already calls docStore.snapshot() before the operation.
- Rename phase5 task file to phase9 to match renumbered phases.
- Add JSDoc clarifying applyCellStyle is additive-only.
- Add regression test for image insertion at end of block text.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit bf60954 into main Apr 23, 2026
1 check passed
@hackerwins
hackerwins deleted the feat/block-attribute-intent-preserving branch April 23, 2026 03:14
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