Skip to content

Add Sheets cell comments (phase B)#192

Merged
hackerwins merged 23 commits into
mainfrom
sheets/comments-phase-b
May 8, 2026
Merged

Add Sheets cell comments (phase B)#192
hackerwins merged 23 commits into
mainfrom
sheets/comments-phase-b

Conversation

@hackerwins

@hackerwins hackerwins commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Google Sheets-style threaded cell comments — multi-thread per cell, resolve/reopen, side panel listing, anchored to stable axis IDs (insert/delete safe).
  • Five UI surfaces: yellow canvas marker, cell-click popover, side panel with Open/Resolved tabs, context menu + Cmd/Ctrl+Alt+M shortcut, and side-panel jump-to-cell.
  • CommentAnchor is a discriminated union from day one so a future @wafflebase/comments extraction (when Docs adds comments) is a move, not a rewrite.

Architecture

  • packages/sheets/src/comment/ — pure TypeScript data model: Comment, Thread, CommentAnchor, plus pure helpers and anchor↔Sref conversion. Zero Yorkie dependency.
  • Worksheet.comments? — optional Yorkie field, no migration needed.
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-comments.ts — Yorkie-local mutations operating on plain object proxies.
  • packages/sheets/src/view/render-comments.ts — canvas marker, drawn in the existing sheets render pipeline.
  • Orphan cleanupdeleteThreadsForAxis runs in the same doc.update() transaction as row/column delete, so undo restores threads atomically.

Spec & docs

  • Design: docs/design/sheets/comments.md
  • Archived todo: docs/tasks/archive/2026/05/20260508-sheets-comments-todo.md

Out of scope (phase C+)

  • @user mentions, in-app/email notifications, per-user unread state.
  • Range / row / column / sheet-wide / doc-wide anchors.
  • Visual / interaction tests (no Playwright harness in repo yet).

Test plan

  • pnpm verify:fast — 748/748 unit tests pass
  • pnpm sheets typecheck — clean
  • pnpm frontend lint — 0 warnings
  • pnpm frontend build — clean
  • pnpm verify:entropy — clean (knip / doc staleness)
  • Yorkie concurrency suite (tests/app/spreadsheet/comments-concurrency.test.ts) runs only when YORKIE_RPC_ADDR is set — verify in CI
  • Manual: create thread → reply → resolve → reopen → delete root → side panel filter
  • Manual: row delete cascades comment, undo restores
  • Manual: Cmd/Ctrl+Alt+M opens composer on empty cell; Cmd/Ctrl+Alt+Shift+M toggles panel
  • Manual: anonymous viewer sees markers/popover read-only with sign-in toast on composer

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added threaded cell comments: users can add, reply to, edit, and delete comments on individual cells
    • Added comments side panel to browse all comments across sheets with filtering by resolved status
    • Added visual comment markers on cells with unresolved comments
    • Added comment resolution workflow to mark threads as resolved or reopen them
    • Comments available in read-only mode for viewing; editing restricted to authenticated users

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

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

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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: e5054d44-b085-4640-b951-e9c3c1e1967b

📥 Commits

Reviewing files that changed from the base of the PR and between 35c35df and a384a14.

📒 Files selected for processing (36)
  • docs/design/README.md
  • docs/design/sheets/comments.md
  • docs/tasks/README.md
  • docs/tasks/archive/2026/05/20260508-sheets-comments-lessons.md
  • docs/tasks/archive/2026/05/20260508-sheets-comments-todo.md
  • docs/tasks/archive/README.md
  • packages/frontend/src/app/documents/document-detail.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentComposer.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentPopover.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentSidePanel.tsx
  • packages/frontend/src/app/spreadsheet/sheet-view.tsx
  • packages/frontend/src/app/spreadsheet/yorkie-store.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-comments.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-structure.ts
  • packages/frontend/src/components/sheet-context-menu.tsx
  • packages/frontend/src/lib/utils.ts
  • packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-comments-orphan.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-worksheet-comments.test.ts
  • packages/frontend/tests/helpers/two-user-spreadsheet-yorkie.ts
  • packages/sheets/src/comment/__tests__/anchor.test.ts
  • packages/sheets/src/comment/__tests__/thread.test.ts
  • packages/sheets/src/comment/anchor.ts
  • packages/sheets/src/comment/thread.ts
  • packages/sheets/src/comment/types.ts
  • packages/sheets/src/index.ts
  • packages/sheets/src/model/workbook/worksheet-document.ts
  • packages/sheets/src/store/__tests__/memory-comments.test.ts
  • packages/sheets/src/store/memory.ts
  • packages/sheets/src/store/readonly.ts
  • packages/sheets/src/store/store.ts
  • packages/sheets/src/view/__tests__/render-comments.test.ts
  • packages/sheets/src/view/gridcanvas.ts
  • packages/sheets/src/view/render-comments.ts
  • packages/sheets/src/view/worksheet.ts
📝 Walkthrough

Walkthrough

This PR implements phase B of sheet cell threaded comments, a complete end-to-end feature spanning comment types, pure mutation helpers, Yorkie persistence, canvas rendering, and React UI components. It includes comprehensive tests validating concurrency and orphan-thread cleanup behavior.

Changes

Sheet Cell Threaded Comments

Layer / File(s) Summary
Type definitions
packages/sheets/src/comment/types.ts, packages/sheets/src/store/store.ts, packages/sheets/src/model/workbook/worksheet-document.ts
CommentAuthor, CommentAnchor, Comment, and Thread types defined; Store interface extended with addThread, addReply, editComment, deleteComment, setThreadResolved, and listThreads methods. Worksheet model now includes optional comments field.
Pure thread helpers
packages/sheets/src/comment/thread.ts, packages/sheets/src/comment/anchor.ts
createThread, addReply, editComment, deleteComment, setThreadResolved for thread mutations with injected now()/ID generators for deterministic testing. cellAnchorToSref and isAnchorAlive for stable anchor conversion and validation.
Store implementations
packages/sheets/src/store/memory.ts, packages/sheets/src/store/readonly.ts
MemStore implements all comment methods with in-memory storage; ReadOnlyStore stubs methods with no-op or error responses.
Yorkie mutations
packages/frontend/src/app/spreadsheet/yorkie-worksheet-comments.ts
applyAddThread, applyAddReply, applyEditComment, applyDeleteComment, applyResolveThread mutate worksheet comment state.
Orphan thread cleanup
packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts, packages/frontend/src/app/spreadsheet/yorkie-worksheet-structure.ts
deleteYorkieWorksheetAxis now returns deleted axis IDs; deleteThreadsForAxis removes threads anchored to deleted rows/columns, integrated into shift operations.
YorkieStore wiring
packages/frontend/src/app/spreadsheet/yorkie-store.ts
Methods addThread, addReply, editComment, deleteComment, setThreadResolved, listThreads delegate to local Yorkie mutations.
Canvas comment markers
packages/sheets/src/view/render-comments.ts, packages/sheets/src/view/gridcanvas.ts, packages/sheets/src/view/worksheet.ts
drawCommentMarker renders yellow triangle; buildOpenThreadKeySet filters unresolved cell-anchored threads; GridCanvas and Worksheet integrated to display markers per cell.
React UI components
packages/frontend/src/app/spreadsheet/components/comments/CommentComposer.tsx, CommentPopover.tsx, CommentSidePanel.tsx
CommentComposer handles textarea input with Cmd/Ctrl+Enter and Escape shortcuts; CommentPopover shows threads/replies for active cell with resolve/edit/delete; CommentSidePanel lists all threads by tab with jump-to-cell.
SheetView integration
packages/frontend/src/app/spreadsheet/sheet-view.tsx
Popover auto-opens for cells with unresolved threads; keyboard shortcuts Ctrl+Alt+M (composer) and Ctrl+Alt+Shift+M (panel toggle); comment operations delegated to YorkieStore.
Document side panel
packages/frontend/src/app/documents/document-detail.tsx
Aggregates all threads across sheets; comment toggle button in header; side panel with open/resolved tabs and jump-to-cell behavior that switches sheet tabs and focuses cells.
Context menu and utils
packages/frontend/src/components/sheet-context-menu.tsx, packages/frontend/src/lib/utils.ts
"Insert comment" action added (Ctrl+Alt+M shortcut); formatRelativeTime utility for human-readable timestamps.
Module exports
packages/sheets/src/index.ts
Comment types and helpers (createThread, addReply, cellAnchorToSref, isAnchorAlive) exposed.
Test helpers
packages/frontend/tests/helpers/two-user-spreadsheet-yorkie.ts
createTwoUserSpreadsheet spins up two Yorkie clients sharing a document; sync() drives convergence; cleanup() detaches clients.
Unit and integration tests
packages/sheets/src/comment/__tests__/*, packages/frontend/tests/app/spreadsheet/yorkie-*.test.ts, packages/sheets/src/store/__tests__/memory-comments.test.ts, packages/sheets/src/view/__tests__/render-comments.test.ts
Thread helpers (createThread, addReply, editComment, deleteComment, setThreadResolved), anchor utilities, Yorkie mutations, orphan cleanup, MemStore, and canvas key set building validated.
Concurrency tests
packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts
Two-client integration tests: concurrent thread creation/replies, row deletion cascade, concurrent resolution—all validating convergence after sync.
Design & documentation
docs/design/sheets/comments.md, docs/tasks/archive/2026/05/20260508-sheets-comments-*.md, docs/design/README.md, docs/tasks/README.md, docs/tasks/archive/README.md
Comprehensive design spec (phase B scope, data model, Yorkie schema, store contract, anchor stability, UI surfaces, testing strategy), task breakdown with 14 subtasks, lessons document, and task archive updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • wafflebase/wafflebase#29: This PR extends the Yorkie worksheet boundary modules introduced in #29 (yorkie-worksheet-axis, yorkie-worksheet-structure, store/sheet model exports) to add orphan cleanup and comment mutations.
  • wafflebase/wafflebase#71: Both PRs extend sheets view rendering (packages/sheets/src/view/worksheet.ts) to overlay new cell-level features; #71 added peer cursor labels, this PR adds comment markers.
  • wafflebase/wafflebase#130: Both PRs introduce axis-ID-based cell anchor utilities and conversion (comment anchors use rowId/colId like the SelectionPresence work in #130).

Poem

🐰 A threaded chat in every cell,
With markers bright and popover swell,
Resolved or open, side by side,
Comments dance through sheets with pride!
Orphans cleaned when rows are gone,
In Yorkie's arms, we carry on!
💬✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sheets/comments-phase-b

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.

@hackerwins
hackerwins force-pushed the sheets/comments-phase-b branch 3 times, most recently from 9ed34f7 to 607acd5 Compare May 8, 2026 12:37

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

Caution

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

⚠️ Outside diff range comments (1)
packages/sheets/src/view/worksheet.ts (1)

5041-5074: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Re-check staleness after listThreads().

This method had a version/sheet guard after fetchGrid(), but the new await sheet.getStore().listThreads() adds another suspension point before gridCanvas.render(...). If a newer render wins during that await, this path can still paint stale grid/comment markers once before the queued render catches up.

Suggested fix
     const allThreads = await sheet.getStore().listThreads();
+    if (sheet !== this.sheet || targetVersion !== this.renderVersion) {
+      return;
+    }
     const commentCellKeys = buildOpenThreadKeySet(allThreads);
 
     this.gridCanvas.render(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sheets/src/view/worksheet.ts` around lines 5041 - 5074, After
awaiting sheet.getStore().listThreads() you must re-check the render
staleness/version guard (the same guard used after fetchGrid()) before calling
buildOpenThreadKeySet/gridCanvas.render; if the sheet/version has changed or a
newer render has won, bail out to avoid painting stale comment markers. Locate
the async path where listThreads() is awaited and add the same conditional check
(using the existing render token/version or sheet identity check used elsewhere)
and return early when stale, then proceed to call buildOpenThreadKeySet and
gridCanvas.render only when current.
🧹 Nitpick comments (3)
packages/frontend/tests/app/spreadsheet/yorkie-comments-orphan.test.ts (1)

16-59: ⚡ Quick win

Missing test: non-sheet-cell anchor threads should be preserved.

deleteThreadsForAxis explicitly skips anchors with kind !== "sheet-cell", but there is no test asserting that behavior. This is a directly-exercisable code path worth covering.

✅ Suggested additional test case
it('preserves threads with non-sheet-cell anchors', () => {
  const ws = {
    comments: {
      // A normal cell thread – should be removed
      t1: thread('t1', 'r1', 'c1'),
      // A hypothetical non-cell thread – should be kept
      t2: {
        ...thread('t2', 'r1', 'c1'),
        anchor: { kind: 'sheet-range', tabId: 't' },
      } as unknown as Thread,
    },
  };
  deleteThreadsForAxis(ws as any, 'row', new Set(['r1']));
  assert.equal(ws.comments.t1, undefined);
  assert.ok(ws.comments.t2); // preserved because kind !== 'sheet-cell'
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/frontend/tests/app/spreadsheet/yorkie-comments-orphan.test.ts`
around lines 16 - 59, Add a test that verifies deleteThreadsForAxis preserves
threads whose anchor.kind !== "sheet-cell": create a workspace object with
comments containing a normal cell thread (use thread('t1', ...)) and a second
thread object (e.g., t2) copied from thread('t2', ...) but with anchor: { kind:
'sheet-range', tabId: 't' } cast to Thread; call deleteThreadsForAxis(ws, 'row',
new Set(['r1'])); assert the cell thread (t1) was removed and that the
non-sheet-cell thread (t2) still exists. Target the test file where other cases
live and reference deleteThreadsForAxis, Thread, thread, and anchor to locate
the code under test.
docs/design/sheets/comments.md (1)

54-75: ⚡ Quick win

Add language identifiers to fenced code blocks.

The code blocks showing directory and component tree structures are missing language identifiers, which affects rendering and syntax highlighting.

📝 Proposed fix

For the directory tree at line 54:

-```
+```text
 packages/sheets/src/comment/

For the component tree at line 278:

-```
+```text
 SpreadsheetPage

Also applies to: 278-285

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/sheets/comments.md` around lines 54 - 75, The fenced code blocks
in the documentation (the directory tree starting with
"packages/sheets/src/comment/" and the component tree under
"packages/frontend/src/app/spreadsheet/") lack language identifiers, which
breaks rendering and highlighting; update those fenced blocks to include an
appropriate language tag such as "text" (e.g., change ``` to ```text) for the
directory/component trees so the tree structures render correctly in the docs
(apply to the block showing packages/sheets/src/comment/ and the block showing
the SpreadsheetPage/component tree around lines 278–285).
packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts (1)

149-188: ⚡ Quick win

Add a reopen convergence case.

This suite covers open -> resolved, but not resolved -> open again. Since reopen is part of the new thread lifecycle, that leaves a key concurrency transition unverified.

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts` around
lines 149 - 188, Add a new test mirroring the "concurrent resolve" case but for
reopen: use createTwoUserSpreadsheet to create a thread via
ctx.storeA.addThread, sync, then perform concurrent reopen calls with
ctx.storeA.setThreadResolved(thread.id, false, authorA) and
ctx.storeB.setThreadResolved(thread.id, false, authorB) (or vice versa) inside
Promise.all, await ctx.sync(), then assert both ctx.storeA.listThreads() and
ctx.storeB.listThreads() return the same single thread and that its resolved
flag is false; place it alongside the existing concurrent-resolve test and
ensure cleanup via ctx.cleanup().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/tasks/archive/2026/05/20260508-sheets-comments-todo.md`:
- Line 15: The heading "### Task 1: Comment data model & pure helpers (sheets
package)" is an H3 with no preceding H2 which triggers markdownlint MD001; fix
it by either promoting this line to an H2 (change "###" to "##") or by inserting
an appropriate H2 section above it (e.g., "## Tasks" or "## Sheet tasks") so the
heading hierarchy is contiguous and markdownlint passes.

In
`@packages/frontend/src/app/spreadsheet/components/comments/CommentComposer.tsx`:
- Around line 39-43: The submit function currently validates using trimmed but
calls onSubmit with the untrimmed body, causing stored comments to include
surrounding whitespace; change the submit handler (submit) to pass trimmed to
onSubmit (or trim inside onSubmit) and then clear body via setBody(""), ensuring
the same trimmed value used for the empty-guard/button-disabled check is what
gets submitted (identify submit, onSubmit, body, trimmed, and setBody in
CommentComposer.tsx).

In
`@packages/frontend/src/app/spreadsheet/components/comments/CommentPopover.tsx`:
- Around line 88-91: The expression computing canEditOrDelete assumes
currentUser is non-null and can throw if currentUser is null; update the logic
in the canEditOrDelete calculation to guard access to currentUser (e.g., use a
null-check or optional chaining on currentUser.userId) so it only compares IDs
when currentUser exists, and ensure any early-return or fallback provides a
clear debug-friendly message/context where appropriate; refer to
canEditOrDelete, currentUser, isReadOnly, c.author.userId and editingCommentId
to locate and fix the check.

In
`@packages/frontend/src/app/spreadsheet/components/comments/CommentSidePanel.tsx`:
- Around line 42-67: The tab buttons in CommentSidePanel.tsx use aria-pressed
(toggle semantics) instead of ARIA tabs; change the container <div> to
role="tablist", change each tab button to role="tab" and replace aria-pressed
with aria-selected={tab === "open"/"resolved"} (remove aria-pressed), and add
role="tabpanel" to the content <ul> that renders the tab panels; also implement
keyboard handling (onKeyDown either on the tab buttons or the tablist) to
support Left/Right (and Home/End) to move focus and call setTab appropriately,
ensuring the active tab receives focus when selected so screen readers and
keyboard users get correct tab semantics (reference symbols: CommentSidePanel,
tab state variable, setTab, openCount, resolvedCount, and the <ul> panel).

In `@packages/frontend/src/app/spreadsheet/sheet-view.tsx`:
- Around line 1242-1258: The computed commentPopoverPosition is only updated
during React renders, so when the grid scrolls the popover becomes stale; fix
this by making the position reactive to sheet viewport changes: move the
computation out of a plain IIFE and into React state (e.g.,
commentPopoverPosition state or a render-key) and add a useEffect that
subscribes to the sheet viewport/scroll change events on sheetRef.current (use
the sheet API methods that emit viewport/scroll events or listen to native
scroll if needed) and recomputes/sets the position using getGridViewportRect()
and getCellRect(activeCellForComment) whenever the sheet viewport changes or
when activeCellForComment changes; also clean up the event listener in the
effect cleanup to avoid leaks.
- Around line 809-816: handleCommentResolve currently closes the cell comment
popover unconditionally after resolving any thread; instead, after calling
storeRef.current?.setThreadResolved(threadId, true, commentAuthor) use the store
to check whether that thread's cell still has any unresolved threads (e.g.
retrieve the thread via storeRef.current?.getThread(threadId) to get its cell
id, then query remaining threads for that cell or a hasUnresolvedThreads(cellId)
helper) and only call setCommentPopoverOpen(false) when no unresolved threads
remain; update handleCommentResolve to perform this conditional close while
preserving the existing commentAuthor and storeRef usage.

In `@packages/frontend/src/app/spreadsheet/yorkie-store.ts`:
- Around line 1221-1239: addThread currently ignores anchor.tabId and will
persist a thread into root.sheets[this.tabId] even if anchor.tabId differs;
update addThread to validate that anchor.tabId === this.tabId (or return/reject)
before creating/applying the thread. Specifically, in the addThread function
check anchor.tabId against this.tabId and throw or return an error (or no-op)
when they differ, ensuring applyAddThread is only called on the matching sheet;
also update callers/tests if they expect a different behavior.
- Around line 1287-1307: listThreads currently returns live objects from
ws.comments allowing external mutation; modify listThreads (in yorkie-store.ts)
to materialize and return detached snapshots instead of the raw live objects —
for each thread from Object.values(ws?.comments ?? {}), produce a shallow/deep
copy (e.g., clone top-level properties, clone anchor object and clone comments
array/objects) so callers get immutable snapshots consistent with other getters
in this class; ensure filtering logic (opts?.cellAnchor and opts?.resolved) runs
against the clones or clone after filtering and return the array of cloned
Thread objects.

In `@packages/frontend/src/components/sheet-context-menu.tsx`:
- Around line 206-208: The UI currently hardcodes the Option symbol (⌥)
regardless of platform; update the span in the SheetContextMenu component (where
navigator.platform.startsWith("Mac") is used) to render "⌥" for Mac and "Alt"
for non-Mac, so the text becomes either "⌘+⌥+M" on Mac or "Ctrl+Alt+M" on
Windows/Linux; modify the expression in that span to compute both the modifier
prefix (navigator.platform.startsWith("Mac") ? "⌘" : "Ctrl") and the middle
modifier (navigator.platform.startsWith("Mac") ? "⌥" : "Alt") and concatenate
them appropriately.

In `@packages/sheets/src/store/memory.ts`:
- Around line 553-629: The store is returning references to the internal
Thread/Comment objects (e.g., from addThread, addReply, listThreads) so callers
can mutate MemStore state; instead, return deep-cloned snapshots. After
creating/updating a thread with createThreadHelper, addReplyHelper,
editCommentHelper, deleteCommentHelper, setThreadResolvedHelper, keep the
internal object as-is in this.threads but return a deep clone to callers (use
structuredClone or a reusable cloneThread/cloneComment helper). Ensure
listThreads maps the stored threads to clones before returning and that addReply
returns a clone of the newly appended comment rather than the internal
reference.

In `@packages/sheets/src/view/render-comments.ts`:
- Around line 4-6: JSDoc is out of sync with the MARKER_SIZE constant (docs say
"7x7" but MARKER_SIZE = 9); update the JSDoc on the comment marker function (the
block above the marker drawing logic that references MARKER_SIZE) so it
accurately describes the marker size (e.g., "9x9" or reference MARKER_SIZE) and
its placement, or alternatively change MARKER_SIZE to 7 if the intended marker
is truly 7x7—ensure the JSDoc and the MARKER_SIZE constant (and any calculations
using it) are consistent.

---

Outside diff comments:
In `@packages/sheets/src/view/worksheet.ts`:
- Around line 5041-5074: After awaiting sheet.getStore().listThreads() you must
re-check the render staleness/version guard (the same guard used after
fetchGrid()) before calling buildOpenThreadKeySet/gridCanvas.render; if the
sheet/version has changed or a newer render has won, bail out to avoid painting
stale comment markers. Locate the async path where listThreads() is awaited and
add the same conditional check (using the existing render token/version or sheet
identity check used elsewhere) and return early when stale, then proceed to call
buildOpenThreadKeySet and gridCanvas.render only when current.

---

Nitpick comments:
In `@docs/design/sheets/comments.md`:
- Around line 54-75: The fenced code blocks in the documentation (the directory
tree starting with "packages/sheets/src/comment/" and the component tree under
"packages/frontend/src/app/spreadsheet/") lack language identifiers, which
breaks rendering and highlighting; update those fenced blocks to include an
appropriate language tag such as "text" (e.g., change ``` to ```text) for the
directory/component trees so the tree structures render correctly in the docs
(apply to the block showing packages/sheets/src/comment/ and the block showing
the SpreadsheetPage/component tree around lines 278–285).

In `@packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts`:
- Around line 149-188: Add a new test mirroring the "concurrent resolve" case
but for reopen: use createTwoUserSpreadsheet to create a thread via
ctx.storeA.addThread, sync, then perform concurrent reopen calls with
ctx.storeA.setThreadResolved(thread.id, false, authorA) and
ctx.storeB.setThreadResolved(thread.id, false, authorB) (or vice versa) inside
Promise.all, await ctx.sync(), then assert both ctx.storeA.listThreads() and
ctx.storeB.listThreads() return the same single thread and that its resolved
flag is false; place it alongside the existing concurrent-resolve test and
ensure cleanup via ctx.cleanup().

In `@packages/frontend/tests/app/spreadsheet/yorkie-comments-orphan.test.ts`:
- Around line 16-59: Add a test that verifies deleteThreadsForAxis preserves
threads whose anchor.kind !== "sheet-cell": create a workspace object with
comments containing a normal cell thread (use thread('t1', ...)) and a second
thread object (e.g., t2) copied from thread('t2', ...) but with anchor: { kind:
'sheet-range', tabId: 't' } cast to Thread; call deleteThreadsForAxis(ws, 'row',
new Set(['r1'])); assert the cell thread (t1) was removed and that the
non-sheet-cell thread (t2) still exists. Target the test file where other cases
live and reference deleteThreadsForAxis, Thread, thread, and anchor to locate
the code under test.
🪄 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: 4ea9d803-b34f-477e-9a25-287fb1db02ba

📥 Commits

Reviewing files that changed from the base of the PR and between a58aab1 and 35c35df.

📒 Files selected for processing (36)
  • docs/design/README.md
  • docs/design/sheets/comments.md
  • docs/tasks/README.md
  • docs/tasks/archive/2026/05/20260508-sheets-comments-lessons.md
  • docs/tasks/archive/2026/05/20260508-sheets-comments-todo.md
  • docs/tasks/archive/README.md
  • packages/frontend/src/app/documents/document-detail.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentComposer.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentPopover.tsx
  • packages/frontend/src/app/spreadsheet/components/comments/CommentSidePanel.tsx
  • packages/frontend/src/app/spreadsheet/sheet-view.tsx
  • packages/frontend/src/app/spreadsheet/yorkie-store.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-axis.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-comments.ts
  • packages/frontend/src/app/spreadsheet/yorkie-worksheet-structure.ts
  • packages/frontend/src/components/sheet-context-menu.tsx
  • packages/frontend/src/lib/utils.ts
  • packages/frontend/tests/app/spreadsheet/comments-concurrency.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-comments-orphan.test.ts
  • packages/frontend/tests/app/spreadsheet/yorkie-worksheet-comments.test.ts
  • packages/frontend/tests/helpers/two-user-spreadsheet-yorkie.ts
  • packages/sheets/src/comment/__tests__/anchor.test.ts
  • packages/sheets/src/comment/__tests__/thread.test.ts
  • packages/sheets/src/comment/anchor.ts
  • packages/sheets/src/comment/thread.ts
  • packages/sheets/src/comment/types.ts
  • packages/sheets/src/index.ts
  • packages/sheets/src/model/workbook/worksheet-document.ts
  • packages/sheets/src/store/__tests__/memory-comments.test.ts
  • packages/sheets/src/store/memory.ts
  • packages/sheets/src/store/readonly.ts
  • packages/sheets/src/store/store.ts
  • packages/sheets/src/view/__tests__/render-comments.test.ts
  • packages/sheets/src/view/gridcanvas.ts
  • packages/sheets/src/view/render-comments.ts
  • packages/sheets/src/view/worksheet.ts

Comment thread docs/tasks/archive/2026/05/20260508-sheets-comments-todo.md
Comment thread packages/frontend/src/app/spreadsheet/components/comments/CommentSidePanel.tsx Outdated
Comment thread packages/frontend/src/app/spreadsheet/sheet-view.tsx
Comment thread packages/frontend/src/app/spreadsheet/yorkie-store.ts
Comment thread packages/frontend/src/app/spreadsheet/yorkie-store.ts
Comment thread packages/frontend/src/components/sheet-context-menu.tsx
Comment on lines +553 to +629
async addThread(
anchor: CommentAnchor,
body: string,
author: CommentAuthor,
): Promise<Thread> {
const thread = createThreadHelper(
anchor,
body,
author,
() => this.newThreadId(),
() => this.newCommentId(),
() => Date.now(),
);
this.threads.set(thread.id, thread);
return thread;
}

async addReply(
threadId: string,
body: string,
author: CommentAuthor,
): Promise<Comment> {
const t = this.threads.get(threadId);
if (!t) throw new Error(`Thread not found: ${threadId}`);
const next = addReplyHelper(t, body, author, () => this.newCommentId(), () => Date.now());
this.threads.set(threadId, next);
return next.comments[next.comments.length - 1];
}

async editComment(threadId: string, commentId: string, body: string): Promise<void> {
const t = this.threads.get(threadId);
if (!t) throw new Error(`Thread not found: ${threadId}`);
this.threads.set(threadId, editCommentHelper(t, commentId, body, () => Date.now()));
}

async deleteComment(threadId: string, commentId: string): Promise<void> {
const t = this.threads.get(threadId);
if (!t) throw new Error(`Thread not found: ${threadId}`);
const next = deleteCommentHelper(t, commentId);
if (next === null) this.threads.delete(threadId);
else this.threads.set(threadId, next);
}

async setThreadResolved(
threadId: string,
resolved: boolean,
by: CommentAuthor,
): Promise<void> {
const t = this.threads.get(threadId);
if (!t) throw new Error(`Thread not found: ${threadId}`);
this.threads.set(threadId, setThreadResolvedHelper(t, resolved, by, () => Date.now()));
}

async listThreads(opts?: {
tabId?: string;
cellAnchor?: { rowId: string; colId: string };
resolved?: boolean;
}): Promise<Thread[]> {
let result = Array.from(this.threads.values());
if (opts?.tabId !== undefined) {
result = result.filter(
(t) => t.anchor.kind === 'sheet-cell' && t.anchor.tabId === opts.tabId,
);
}
if (opts?.cellAnchor) {
result = result.filter(
(t) =>
t.anchor.kind === 'sheet-cell' &&
t.anchor.rowId === opts.cellAnchor!.rowId &&
t.anchor.colId === opts.cellAnchor!.colId,
);
}
if (opts?.resolved !== undefined) {
result = result.filter((t) => t.resolved === opts.resolved);
}
return result;
}

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 | ⚡ Quick win

Clone comment snapshots before returning them.

These methods currently expose the same Thread / Comment objects stored in this.threads. A caller mutating the returned value will mutate MemStore state out-of-band, which is especially easy to do in tests.

Suggested fix
+  private cloneThread(thread: Thread): Thread {
+    return structuredClone(thread);
+  }
+
   async addThread(
     anchor: CommentAnchor,
     body: string,
     author: CommentAuthor,
   ): Promise<Thread> {
@@
     );
     this.threads.set(thread.id, thread);
-    return thread;
+    return this.cloneThread(thread);
   }
@@
   async addReply(
     threadId: string,
     body: string,
     author: CommentAuthor,
   ): Promise<Comment> {
@@
     const next = addReplyHelper(t, body, author, () => this.newCommentId(), () => Date.now());
     this.threads.set(threadId, next);
-    return next.comments[next.comments.length - 1];
+    return structuredClone(next.comments[next.comments.length - 1]);
   }
@@
   async listThreads(opts?: {
@@
   }): Promise<Thread[]> {
     let result = Array.from(this.threads.values());
@@
-    return result;
+    return result.map((thread) => this.cloneThread(thread));
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sheets/src/store/memory.ts` around lines 553 - 629, The store is
returning references to the internal Thread/Comment objects (e.g., from
addThread, addReply, listThreads) so callers can mutate MemStore state; instead,
return deep-cloned snapshots. After creating/updating a thread with
createThreadHelper, addReplyHelper, editCommentHelper, deleteCommentHelper,
setThreadResolvedHelper, keep the internal object as-is in this.threads but
return a deep clone to callers (use structuredClone or a reusable
cloneThread/cloneComment helper). Ensure listThreads maps the stored threads to
clones before returning and that addReply returns a clone of the newly appended
comment rather than the internal reference.

Comment thread packages/sheets/src/view/render-comments.ts
hackerwins and others added 22 commits May 8, 2026 21:42
Spec out Google Sheets-style threaded comments anchored to cells, with
resolve/reopen, multi-thread per cell, and a side panel listing all
threads across tabs. Phase B keeps the body plain-text and defers
mentions/notifications to phase C.

CommentAnchor is a discriminated union from day one so a future
@wafflebase/comments extraction (when Docs adds comments) is a move,
not a rewrite. Anchors carry rowId/colId, so inserts/moves track for
free via the existing axis ID model; row/column delete cascades via
same-transaction cleanup so undo restores the thread.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
14 TDD-driven tasks covering data model, Yorkie schema/mutations,
orphan cleanup, canvas marker rendering, popover/side panel UI, entry
points, and concurrent integration tests. Lessons file seeded for
in-flight pattern capture.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Implement pure thread-mutation helpers (createThread, addReply, editComment,
deleteComment, setThreadResolved) with injected ID and timestamp callbacks
for deterministic testing and flexible integration with Yorkie store and
MemStore. All helpers validate non-empty comment bodies and return immutable
thread copies.
Convert between CellAnchor (rowId/colId identifiers) and visual Sref
strings (e.g. 'B3') using axis order. Includes liveness check to verify
both row and column IDs are still valid.
Add `comments?: { [threadId: string]: Thread }` field to the canonical
Worksheet type in the Yorkie document schema. Re-export Comment,
CommentAnchor, CommentAuthor, and Thread types from @wafflebase/sheets.

This allows thread data to be persisted in the Worksheet and materialized
on first thread insertion without requiring migrations.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Create pure functions that operate on worksheet objects to add threads,
replies, edit/delete comments, and resolve/unresolve threads. These are
called inside doc.update(...) blocks where Yorkie proxies intercept
plain object mutations transparently.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Implement the 6 comment-related methods on YorkieStore: addThread,
addReply, editComment, deleteComment, setThreadResolved, and listThreads.
Each method delegates to the pure mutations in yorkie-worksheet-comments.ts
inside a doc.update(...) block. Also export createThread and addReply from
the main sheets index so they can be imported from @wafflebase/sheets.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
When a row or column is deleted, threads anchored to it become orphans.
Auto-delete them in the same transaction (via deleteThreadsForAxis)
so undo/redo restores both the deleted rows/columns and their threads.

- Add deleteThreadsForAxis helper in yorkie-worksheet-structure
- Modify deleteYorkieWorksheetAxis to return deleted axis IDs
- Wire cleanup call into applyYorkieWorksheetShift for deletes
- Add comprehensive test for orphan cleanup behavior

Fixes task 7 of sheets-comments-phase-b roadmap.
Eliminate type duplication by using Worksheet from @wafflebase/sheets in
both yorkie-worksheet-structure.ts and yorkie-worksheet-comments.ts.
The Worksheet type already includes the comments field, making the local
WorksheetCommentsView and WorksheetWithComments typedefs redundant.

Add a fourth test case to verify mixed scenarios: some threads in the
deleted set are removed while those outside the set are preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Implement Task 8 of comments phase B: draw a 7×7 yellow triangle in the
top-right corner of cells that have at least one unresolved comment thread
on the active tab.

Changes:
- Add render-comments.ts module (both sheets and frontend packages) with
  buildOpenThreadKeySet() helper and drawCommentMarker() canvas function
- Add render-comments.test.ts unit tests for buildOpenThreadKeySet
- Update GridCanvas.render() to accept rowOrder, colOrder, commentCellKeys
- Add Pass 5 to renderQuadrantCells to draw comment markers
- Update all renderQuadrantCells call sites with new parameters
- Wire Worksheet.renderSheet() to build comment key set via listThreads()
  and pass to GridCanvas

The marker respects the 'resolved' filter and only renders on the active
tab (via the Store's listThreads query).

Marker style: 7×7 px yellow triangle (#fbbc04), positioned at cell's
top-right corner to match Google Sheets parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Reusable plain-text input form for new threads and replies.
Supports Cmd/Ctrl+Enter to submit, Escape to cancel, and
disabled mode for unauthenticated users.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- CommentPopover: threaded comment UI with reply/resolve/edit/delete
  actions, reusing CommentComposer; read-only mode for unauthenticated
  users
- SheetView: tracks active cell via onSelectionChange, derives open
  threads from worksheet.comments, auto-opens popover when a cell with
  unresolved threads is selected, positions popover beside the active
  cell using getCellRect + getGridViewportRect, delegates mutations to
  YorkieStore (addThread, addReply, setThreadResolved, editComment,
  deleteComment)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Creates the CommentSidePanel React component (Open/Resolved tabs,
per-thread rows). Wires it into DocumentLayout with a toolbar toggle
button, aggregates threads from all tabs via the Yorkie doc root, and
implements jump-to-cell by converting CommentAnchor → Sref and passing
a commentJumpTarget prop to SheetView which calls focusCell.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add docVersion state that bumps on local-change and remote-change events,
fixing the stale allThreads useMemo that previously relied only on doc and
activeTabId (neither of which change when threads are added/edited/resolved
on the same tab). This ensures the comment side panel stays in sync with
thread mutations.
- Right-click cell → "Insert comment" (Cmd/Ctrl+⌥+M hint)
- Cmd/Ctrl+Alt+M opens the comment composer for the active cell
- Cmd/Ctrl+Alt+Shift+M toggles the comments side panel
- Toolbar IconMessage button was already wired in Task 11

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Four integration tests covering: concurrent thread creation on the same
cell, concurrent replies to the same thread, row-delete cascading to orphan
thread while the other client edits it, and concurrent resolve of the same
thread. Tests are gated behind YORKIE_RPC_ADDR, matching the existing
concurrency-test convention.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
All 14 tasks complete. verify:fast passes (58 test files, 1265 tests).
Visual tests skipped — no browser harness in repo; follow-up for phase C.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Three small post-review polishes:
- CommentPopover renders comment bodies with whitespace-pre-wrap so
  user-typed newlines survive display, matching the spec's plain-text-
  with-newlines goal (would otherwise collapse to spaces).
- handleJumpToCell in DocumentLayout now uses cellAnchorToSref instead
  of the older anchorToRef + toSref pair, eliminating the apparent
  unfinished-refactor smell flagged in final review.
- listThreads JSDoc on the Store interface clarifies it returns a
  single tab's threads (defaulting to the active tab) — callers
  aggregating across tabs should walk Worksheet.comments directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Four UX issues surfaced during browser testing of the comments feature:

1. Comment body in CommentPopover was 14px while surrounding metadata
   was 12px — visually too big and clashing. Reduced body to text-xs.

2. Cell comment marker at 7×7 was too small to spot at typical zoom.
   Bumped MARKER_SIZE to 9 in both gridcanvas and the exported helper.

3. Side panel did not refresh after adding a comment. Replaced the
   manual docVersion state + subscribe-and-bump indirection with the
   reactive `root` value already provided by the Yorkie React SDK
   (useDocument().root), so the allThreads memo recomputes naturally
   on every CRDT mutation.

4. Marker rendered at the wrong position (one row above the target
   cell, horizontally shifted by RowHeaderWidth) and did not track
   scroll. The hand-rolled coord math in Pass 5 had the wrong sign
   on scroll and omitted the header offsets. Replaced with toCellRect,
   the canonical cell-rect helper used by Pass 1-3, which handles
   scroll, headers, and merge anchors correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Iterative UX polish from browser testing. Builds on the previous Tier 1
work (avatars, relative time, hover-only edit/delete, resolve emphasis,
outside-click dismiss, reply indentation) with the following follow-ups:

- Drop the "Comments" header bar entirely. Outside-click and Escape both
  dismiss; the title was occupying vertical space without value.
- Sync composer textarea size to the comment body (text-xs) so editing
  and reading feel like the same surface, not two different fonts.
- Add a `compact` prop to CommentComposer used for the inline reply
  spot — slim 1-row textarea, submit button hidden until content typed.
- Replace the [Reply] button → composer toggle with an always-visible
  inline composer at the bottom of every thread.
- Move Resolve to a small ✓ icon button on the root comment header
  (right-aligned), reclaiming a full button row of vertical space.
- Move Edit / Delete behind a MoreHorizontal "⋯" dropdown (revealed on
  hover/focus-within of the comment row).
- Force focus into the composer on popover open: React's autoFocus prop
  silently no-ops when the textarea is mounted into an already-focused
  container, so call ref.current.focus() explicitly via useEffect. This
  makes "Insert comment" / Cmd+Alt+M put the caret in the input even on
  cells that already have threads.
- Auto-grow the textarea to fit content (capped at 200px with internal
  scroll) so multi-line comments do not require manual resize.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins
hackerwins force-pushed the sheets/comments-phase-b branch from 607acd5 to 1a9359c Compare May 8, 2026 12:47
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 164.6s

Lane Status Duration
sheets:build ✅ pass 13.8s
docs:build ✅ pass 12.0s
slides:build ✅ pass 8.6s
verify:fast ✅ pass 86.7s
frontend:build ✅ pass 18.0s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 2.0s
verify:entropy ✅ pass 18.2s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 8, 2026

Copy link
Copy Markdown

- CommentPopover: use optional chaining (currentUser?.userId) so
  TypeScript does not assume !isReadOnly implies non-null currentUser
- CommentSidePanel: replace aria-pressed with role="tab"/aria-selected
  inside a role="tablist" container per WAI-ARIA tabs pattern
- sheet-view: handleCommentResolve now keeps the popover open when
  other unresolved threads remain on the same cell
- yorkie-store addThread: reject calls whose anchor.tabId mismatches
  the store's own tabId to prevent silent cross-tab writes
- yorkie-store listThreads: return structuredClone copies so callers
  cannot accidentally mutate live Yorkie proxy objects
- memory.ts listThreads: same structuredClone guard for MemStore
- CommentComposer: pass trimmed body to onSubmit (was raw)
- sheet-context-menu: render full "⌘+⌥+M" / "Ctrl+Alt+M" per platform
  instead of mixing the ⌥ symbol into the non-Mac branch
- render-comments + gridcanvas: fix JSDoc "7x7" → "9x9" to match
  the actual MARKER_SIZE = 9 constant
- comments todo: add ## Tasks H2 before first ### Task to fix MD001

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@hackerwins
hackerwins merged commit 25311b8 into main May 8, 2026
4 checks passed
@hackerwins
hackerwins deleted the sheets/comments-phase-b branch May 8, 2026 13:21
hackerwins added a commit that referenced this pull request May 8, 2026
Threaded comments anchored to spreadsheet cells, sourced from
docs/design/sheets/comments.md. Phase B ships:

* Multi-thread per cell with replies, edit/delete (author only),
  resolve/reopen (any collaborator).
* Anchor stability via existing axis IDs (rowId/colId) — inserts
  and moves track for free; row/column delete cascades to threads
  in the same Yorkie transaction so undo restores them atomically.
* Five UI surfaces: yellow canvas marker, cell-click popover,
  right-side panel with Open/Resolved tabs, context menu + Cmd/Ctrl+
  Alt+M / +Shift+M shortcuts, and side-panel jump-to-cell.
* Plain-text bodies with newlines, real-time sync via the existing
  Yorkie pipeline, read-only mode for unauthenticated viewers.

Architecture is anchor-agnostic from day one — `CommentAnchor` is a
discriminated union so a future `@wafflebase/comments` extraction
(when Docs adds comments) is a move, not a rewrite. The sheets
package owns the data model, pure helpers, and Canvas marker; the
frontend owns the Yorkie boundary, React UI (popover, side panel,
composer), and event wiring.

UI polished against browser testing: avatars, relative time stamps,
hover-revealed Edit/Delete behind a "⋯" menu, Resolve as a small ✓
icon button on root comment headers, always-visible inline reply
composer, auto-grow textarea, outside-click and Escape dismiss,
forced focus on popover open. Composer textarea sized to match the
displayed comment body for visual continuity.

Out of scope (phase C+): @user mentions, notifications, per-user
unread state, range / row / column / sheet anchors, popover
scroll-tracking.

Tests: 1265 unit, 4 Yorkie two-client concurrency scenarios gated
behind YORKIE_RPC_ADDR. Visual / interaction tests deferred — no
Playwright harness in repo yet.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins hackerwins mentioned this pull request May 11, 2026
6 tasks
hackerwins added a commit that referenced this pull request May 11, 2026
Adds @wafflebase/slides as a third surface alongside Sheets and Docs,
plus 53-shape library (Phase 1+2), adjustment handles for 9 pilot
shapes (P3-A.1), live snap guides, align/distribute toolbar, themed
authoring, and layout-change UI. Also ships Sheets cell comments
(Phase B), docs peer-avatar caret jump, yorkie-js-sdk 0.7.8 upgrade,
and CLI/REST API improvements (docs export imageFetcher, expired
session refresh, REST docs split). Minor bump because slides is a
new top-level package.

Highlights: #184 #185 #186 #187 #188 #189 #190 #191 #192 #197 #198
#201 #202 #203 #204 #205 #206 #207 #209 #210
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