Skip to content

Add peer cursor name labels for collaborative editing#71

Merged
hackerwins merged 12 commits into
mainfrom
feat/peer-cursor-labels
Mar 23, 2026
Merged

Add peer cursor name labels for collaborative editing#71
hackerwins merged 12 commits into
mainfrom
feat/peer-cursor-labels

Conversation

@hackerwins

@hackerwins hackerwins commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Show transient username labels on peer cursors during collaborative editing
  • Labels auto-show for 4 seconds when a peer moves to a new cell, and on mouse hover
  • Canvas-rendered on the overlay layer with peer-color background and auto-contrast text color
  • Presence avatars now have colored borders matching their cursor color on the canvas

Changes

  • Store interface: Widen getPresences() to include optional username
  • Overlay: Add exported drawPeerLabel function with truncation, edge clamping, and stacking
  • Worksheet: Timer-based visibility state, mouse hover detection, pass visiblePeerLabels to Overlay
  • User presence UI: Avatar borders use peer cursor color with opaque background

Test plan

  • Unit tests for drawPeerLabel (position, boundary flip, truncation, luminance-based text color)
  • Sheets typecheck passes
  • 1097 sheets tests pass
  • Manual: Open two browser tabs, move cursor in tab B → name tag appears in tab A
  • Manual: Wait 4+ seconds → tag disappears, colored border remains
  • Manual: Hover over peer cursor cell → tag reappears
  • Manual: Verify avatar border color matches cursor color on canvas

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added design spec describing transient peer cursor username labels for collaborative editing.
  • New Features

    • Username labels appear above peers’ active cells, stack when multiple peers share a cell, and clamp/flip at viewport edges.
    • Labels auto-show ~4 seconds after a peer changes cells and reappear on hover.
    • Peer cursor/ avatar colors now adapt to the app theme.
  • Tests

    • Added tests validating label drawing, truncation, stacking, flipping, clamping, and contrast.

hackerwins and others added 10 commits March 23, 2026 11:57
Defines transient username tags on collaborative cursors: auto-show
for 4 seconds on peer cell change, hover to show on demand. Canvas-
rendered on the overlay layer, with Store interface widening to pass
username through presences.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
4-task plan: widen Store type, add Overlay label rendering with
exported drawPeerLabel function, add Worksheet visibility state
and hover detection, integration testing.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The presence object now includes an optional username field to support
displaying peer cursor name labels in collaborative editing.
Exported drawPeerLabel function draws username tags above peer cells.
11px white text on peer-color background with truncation at 120px,
edge clamping, and stable stacking for multiple peers on same cell.
Worksheet tracks peer cell changes with 4-second timers and detects
mouse hover over peer cursor cells. Derives visiblePeerLabels set
and passes it to Overlay for rendering.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Include hoveredPeerClientID in the changed condition so the overlay
re-renders when the mouse leaves the scroll container while hovering
a peer cursor.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Combine peer hover change with the existing changed flag to avoid
redundant overlay renders in the same frame.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use dark text on light peer cursor colors (e.g. yellow, gold) and
white text on dark colors for better readability.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Avatar borders match the peer's cursor color on the canvas, so users
can visually connect an avatar with its cursor. Uses getPeerCursorColor
from the sheets package, now exported via the public API.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Avatars are stacked with negative spacing, so a transparent
background causes border rings to overlap visually.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@coderabbitai

coderabbitai Bot commented Mar 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 10 minutes and 55 seconds before requesting another review.

⌛ 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: 9fc19da7-c547-4cb0-b47a-0a4868a5125b

📥 Commits

Reviewing files that changed from the base of the PR and between a722a01 and ea81e81.

📒 Files selected for processing (1)
  • packages/sheets/src/view/worksheet.ts
📝 Walkthrough

Walkthrough

Adds transient peer cursor name labels: a design doc and implementation that widens presence typing to include optional usernames, renders stacked, truncated labels on the overlay with viewport-clamping, and manages visibility via hover and per-peer 4s timers in the worksheet.

Changes

Cohort / File(s) Summary
Design & Tasks
docs/design/README.md, docs/design/peer-cursor-labels.md, docs/tasks/active/20260323-peer-cursor-labels-todo.md
Added design doc and implementation plan describing label visuals, stacking/clamping/truncation rules, store typing change, overlay drawing helper, worksheet visibility timers/hover, and tests/manual verification steps.
Store typings & Worksheet API
packages/sheets/src/store/store.ts, packages/sheets/src/store/memory.ts, packages/sheets/src/store/readonly.ts, packages/sheets/src/model/worksheet/sheet.ts
Widened presence return type to include optional username?: string alongside activeCell across store implementations and Sheet API.
Overlay rendering & helper
packages/sheets/src/view/overlay.ts, packages/sheets/src/view/__tests__/overlay-peer-labels.test.ts
Added exported drawPeerLabel() and getLabelTextColor(), threaded optional visiblePeerLabels?: Set<string> into rendering, implemented grouping/stacking per-cell, viewport-aware placement, ellipsis truncation, and tests validating positioning, truncation, stacking, and contrast.
Worksheet visibility & hover management
packages/sheets/src/view/worksheet.ts
Added peer-hover detection on mouse move, per-peer label timers and prev-active-cell tracking, updatePeerLabelVisibility() and getVisiblePeerLabels(), cleanup on leave, and pass-through of visible set into overlay render.
Public exports & frontend usage
packages/sheets/src/index.ts, packages/frontend/src/components/user-presence.tsx
Re-exported getPeerCursorColor; integrated theme-aware peer cursor color into presence avatar borders in frontend component.

Sequence Diagram

sequenceDiagram
    actor User
    participant Worksheet
    participant Store
    participant Timers as "WS Timers"
    participant Overlay
    participant Canvas

    User->>Store: presence.activeCell (+ optional username) changes
    Store-->>Worksheet: getPresences() -> presences (clientID, activeCell, username?)
    Worksheet->>Timers: schedule/refresh 4s timer for clientID
    User->>Worksheet: mousemove -> determine hovered cell
    Worksheet->>Worksheet: set hoveredPeerClientID if matches presence
    Worksheet->>Overlay: render(presences, visiblePeerLabels)
    Overlay->>Overlay: group peers by cell, sort, compute stackIndex
    Overlay->>Canvas: drawPeerLabel(username, color, cellRect, port, stackIndex)
    Canvas-->>User: label appears (positioned, truncated, clamped)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hop and sketch a tiny tag,
Above bright cells where cursors lag.
Timers tick, a hover gleams,
Names appear in stacked little beams.
A joyful nibble — labels on the map! 🐰✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding peer cursor name labels for collaborative editing, which is the primary objective and feature introduced across all modified and new files.

✏️ 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/peer-cursor-labels

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

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 103.8s

Lane Status Duration
sheets:build ✅ pass 14.3s
verify:fast ✅ pass 50.8s
frontend:build ✅ pass 14.9s
verify:frontend:chunks ✅ pass 0.4s
backend:build ✅ pass 4.6s
cli:build ✅ pass 1.9s
verify:entropy ✅ pass 17.0s

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: 3

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)

2267-2270: ⚠️ Potential issue | 🟡 Minor

Timer callback should guard against destroyed instance.

The timeout callback calls this.renderOverlay() without checking if the worksheet is still valid. If cleanup() is called before the timer fires (and timers aren't cleared per the previous comment), renderOverlay() will access this.sheet! which is undefined after cleanup.

🛡️ Proposed fix
         const timerId = window.setTimeout(() => {
           this.peerLabelTimers.delete(clientID);
-          this.renderOverlay();
+          if (this.sheet) {
+            this.renderOverlay();
+          }
         }, 4000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheets/src/view/worksheet.ts` around lines 2267 - 2270, The timer
callback must verify the worksheet instance is still valid before calling
renderOverlay; update the timeout handler that currently invokes
this.renderOverlay() (which follows getAutofillSelectionRect/const range =
this.sheet.getRangeOrActiveCell()) to early-return if the instance was cleaned
up (e.g., if this.sheet is null/undefined or a boolean flag like
this.isDestroyed is true), so renderOverlay() won't run after cleanup() has
cleared this.sheet.
🧹 Nitpick comments (5)
packages/frontend/src/components/user-presence.tsx (1)

85-92: Unify avatar border styling to prevent future drift.

The peer border color logic is duplicated, and bg-background is present on Line 86 but missing on Line 146. Extracting shared style logic keeps both avatar variants consistent.

♻️ Suggested refactor
+  const getPeerBorderColor = (user: (typeof users)[number]) =>
+    user.isCurrentUser
+      ? undefined
+      : getPeerCursorColor(resolvedTheme, user.clientID);
+
   const renderAvatar = (user: (typeof users)[number]) => {
@@
             <Avatar
               className="h-8 w-8 border-2 bg-background"
               style={{
-                borderColor: user.isCurrentUser
-                  ? undefined
-                  : getPeerCursorColor(resolvedTheme, user.clientID),
+                borderColor: getPeerBorderColor(user),
               }}
             >
@@
                         <Avatar
-                          className="h-6 w-6 border-2"
+                          className="h-6 w-6 border-2 bg-background"
                           style={{
-                            borderColor: user.isCurrentUser
-                              ? undefined
-                              : getPeerCursorColor(resolvedTheme, user.clientID),
+                            borderColor: getPeerBorderColor(user),
                           }}
                         >

Also applies to: 145-152

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

In `@packages/frontend/src/components/user-presence.tsx` around lines 85 - 92, The
Avatar components duplicate border/background styling; extract a shared computed
style object (e.g., avatarBorderStyle) that sets borderColor to
user.isCurrentUser ? undefined : getPeerCursorColor(resolvedTheme,
user.clientID) and includes bg-background, then apply that object to both Avatar
usages (the one around Line ~85 and the one around Line ~146) to ensure
consistent border and background across variants; update any inline style
references to use the new shared variable so future changes stay unified.
packages/sheets/src/store/store.ts (1)

76-76: Extract a shared StorePresence type to avoid signature drift.

Line 76 duplicates a complex inline shape now repeated across store.ts, memory.ts, readonly.ts, and sheet.ts. A shared exported type will reduce future mismatch risk.

♻️ Proposed refactor
 export interface Store {
+  // Presence payload returned by collaboration-capable stores.
+  // `username` is optional because some clients may not publish it.
+  // `activeCell` remains required for cursor rendering.
+}
+
+export type StorePresence = {
+  clientID: string;
+  presence: { activeCell: string; username?: string };
+};
+
+export interface Store {
@@
-  getPresences(): Array<{ clientID: string; presence: { activeCell: string; username?: string } }>;
+  getPresences(): StorePresence[];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sheets/src/store/store.ts` at line 76, Extract and export a shared
type named StorePresence that represents { clientID: string; presence: {
activeCell: string; username?: string } }, then replace the inline signature in
the getPresences declaration in store.ts with Array<StorePresence> and update
all other occurrences (memory.ts, readonly.ts, sheet.ts) to use StorePresence so
the shape is defined in one place and exported for reuse; ensure the new type is
exported from the module that previously declared the inline shape.
docs/tasks/active/20260323-peer-cursor-labels-todo.md (1)

15-15: Heading levels skip h2.

The document jumps from # Peer Cursor Name Labels Implementation Plan (h1) directly to ### Task 1 (h3), skipping h2. This violates markdown best practices and accessibility guidelines for document structure.

📝 Suggested fix

Either change task headings to h2:

-### Task 1: Widen Store presence type to include `username`
+## Task 1: Widen Store presence type to include `username`

Or add an h2 section wrapper:

 ---

+## Implementation Tasks
+
 ### Task 1: Widen Store presence type to include `username`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tasks/active/20260323-peer-cursor-labels-todo.md` at line 15, The
document skips from the H1 "Peer Cursor Name Labels Implementation Plan" to "###
Task 1: Widen Store presence type to include `username`" (H3); update the
heading structure by changing each task heading like "### Task 1: Widen Store
presence type to include `username`" to an H2 ("## Task 1: ...") or
alternatively insert a new H2 wrapper (e.g., "## Tasks" immediately after the
H1) and keep the existing task headings as H3, ensuring the heading hierarchy is
H1 → H2 → H3 for proper accessibility.
docs/design/peer-cursor-labels.md (1)

42-42: Documentation doesn't match implementation: text color is now luminance-based.

The design spec states text color is always #FFFFFF (white), but the implementation in overlay.ts uses getLabelTextColor() which selects black (#000000) or white (#FFFFFF) based on background luminance. This is a better approach for accessibility, but the documentation should be updated to reflect the actual behavior.

📝 Suggested doc update
-| Text color     | White (`#FFFFFF`)                                |
+| Text color     | Auto-contrast (white on dark, black on light)    |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/peer-cursor-labels.md` at line 42, The docs state label text
color is always `#FFFFFF`, but the implementation in overlay.ts uses
getLabelTextColor() to pick `#000000` or `#FFFFFF` based on background luminance;
update docs/design/peer-cursor-labels.md to describe this adaptive behavior,
mentioning getLabelTextColor() and that text color is chosen (black or white)
depending on background luminance for accessibility rather than always white.
packages/sheets/src/view/overlay.ts (1)

37-44: getLabelTextColor assumes well-formed 6-digit hex color.

The function expects a hex color like #FF6B6B or FF6B6B. If a non-hex format is passed (CSS color name, rgb(), 3-digit hex), the substring parsing will produce incorrect results. Currently safe since getPeerCursorColor always returns hex, but consider adding validation or a comment noting this assumption.

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

In `@packages/sheets/src/view/overlay.ts` around lines 37 - 44, getLabelTextColor
currently assumes a 6-digit hex string and will misparse other color formats;
update getLabelTextColor to validate and normalize input (accept leading '#',
expand 3-digit hex to 6-digit, and detect non-hex formats) and fall back to a
safe default color if validation fails, or add a concise comment documenting the
assumption and linking its use by getPeerCursorColor so future callers know the
hex-only contract; reference the getLabelTextColor function and its caller
getPeerCursorColor when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/sheets/src/view/__tests__/overlay-peer-labels.test.ts`:
- Around line 20-87: Add two unit tests for drawPeerLabel: one that verifies
vertical stacking by passing different stackIndex values to drawPeerLabel and
asserting the produced fillText Y coordinates are offset by the expected stack
spacing relative to cellRect.top/height (use createMockCtx() and read
ctx.fillText mock.calls to compare Y positions for stackIndex 0 vs 1+), and
another that verifies horizontal clamping by placing cellRect near the left and
right edges of the port and asserting the fillText X coordinate is clamped
inside the port bounds (use port.left/width and inspect ctx.fillText
mock.calls[0][1]; if needed mock ctx.measureText to ensure width calculations).
Ensure tests reference drawPeerLabel, stackIndex, cellRect, port and reuse
createMockCtx() for consistency.

In `@packages/sheets/src/view/overlay.ts`:
- Around line 259-261: The freeze-pane branch is always calling drawPeerLabel
with stackIndex 0, causing overlapping labels when multiple peers share the same
frozen cell; update the logic in the freeze-pane path (the block that checks
visiblePeerLabels, presence.username, peerColor, rect, port) to group peer
presences by the same rect/port (same cell) and call drawPeerLabel for each peer
with an incrementing stackIndex (or extract the existing grouping/stacking
helper used in the non-freeze path and reuse it) so labels are stacked the same
way as in the non-freeze rendering.

In `@packages/sheets/src/view/worksheet.ts`:
- Around line 179-181: The peer label timeouts stored in peerLabelTimers are not
cleared in the Worksheet.cleanup() routine, allowing setTimeout callbacks (which
call renderOverlay()) to run after destruction; update the cleanup() method to
iterate over this.peerLabelTimers and call clearTimeout for each timer id, then
clear the map and reset hoveredPeerClientID (if needed) so no stale timers or
state remain, ensuring any callbacks won't execute against a destroyed Worksheet
instance.

---

Outside diff comments:
In `@packages/sheets/src/view/worksheet.ts`:
- Around line 2267-2270: The timer callback must verify the worksheet instance
is still valid before calling renderOverlay; update the timeout handler that
currently invokes this.renderOverlay() (which follows
getAutofillSelectionRect/const range = this.sheet.getRangeOrActiveCell()) to
early-return if the instance was cleaned up (e.g., if this.sheet is
null/undefined or a boolean flag like this.isDestroyed is true), so
renderOverlay() won't run after cleanup() has cleared this.sheet.

---

Nitpick comments:
In `@docs/design/peer-cursor-labels.md`:
- Line 42: The docs state label text color is always `#FFFFFF`, but the
implementation in overlay.ts uses getLabelTextColor() to pick `#000000` or `#FFFFFF`
based on background luminance; update docs/design/peer-cursor-labels.md to
describe this adaptive behavior, mentioning getLabelTextColor() and that text
color is chosen (black or white) depending on background luminance for
accessibility rather than always white.

In `@docs/tasks/active/20260323-peer-cursor-labels-todo.md`:
- Line 15: The document skips from the H1 "Peer Cursor Name Labels
Implementation Plan" to "### Task 1: Widen Store presence type to include
`username`" (H3); update the heading structure by changing each task heading
like "### Task 1: Widen Store presence type to include `username`" to an H2 ("##
Task 1: ...") or alternatively insert a new H2 wrapper (e.g., "## Tasks"
immediately after the H1) and keep the existing task headings as H3, ensuring
the heading hierarchy is H1 → H2 → H3 for proper accessibility.

In `@packages/frontend/src/components/user-presence.tsx`:
- Around line 85-92: The Avatar components duplicate border/background styling;
extract a shared computed style object (e.g., avatarBorderStyle) that sets
borderColor to user.isCurrentUser ? undefined :
getPeerCursorColor(resolvedTheme, user.clientID) and includes bg-background,
then apply that object to both Avatar usages (the one around Line ~85 and the
one around Line ~146) to ensure consistent border and background across
variants; update any inline style references to use the new shared variable so
future changes stay unified.

In `@packages/sheets/src/store/store.ts`:
- Line 76: Extract and export a shared type named StorePresence that represents
{ clientID: string; presence: { activeCell: string; username?: string } }, then
replace the inline signature in the getPresences declaration in store.ts with
Array<StorePresence> and update all other occurrences (memory.ts, readonly.ts,
sheet.ts) to use StorePresence so the shape is defined in one place and exported
for reuse; ensure the new type is exported from the module that previously
declared the inline shape.

In `@packages/sheets/src/view/overlay.ts`:
- Around line 37-44: getLabelTextColor currently assumes a 6-digit hex string
and will misparse other color formats; update getLabelTextColor to validate and
normalize input (accept leading '#', expand 3-digit hex to 6-digit, and detect
non-hex formats) and fall back to a safe default color if validation fails, or
add a concise comment documenting the assumption and linking its use by
getPeerCursorColor so future callers know the hex-only contract; reference the
getLabelTextColor function and its caller getPeerCursorColor when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6e3acf1-cf61-46bb-b37c-6d9621d982c2

📥 Commits

Reviewing files that changed from the base of the PR and between da86b70 and b752d79.

📒 Files selected for processing (12)
  • docs/design/README.md
  • docs/design/peer-cursor-labels.md
  • docs/tasks/active/20260323-peer-cursor-labels-todo.md
  • packages/frontend/src/components/user-presence.tsx
  • packages/sheets/src/index.ts
  • packages/sheets/src/model/worksheet/sheet.ts
  • packages/sheets/src/store/memory.ts
  • packages/sheets/src/store/readonly.ts
  • packages/sheets/src/store/store.ts
  • packages/sheets/src/view/__tests__/overlay-peer-labels.test.ts
  • packages/sheets/src/view/overlay.ts
  • packages/sheets/src/view/worksheet.ts

Comment thread packages/sheets/src/view/__tests__/overlay-peer-labels.test.ts
Comment on lines +259 to +261
if (visiblePeerLabels?.has(clientID) && presence.username) {
drawPeerLabel(ctx, presence.username, peerColor, rect, port, 0);
}

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

Freeze-pane path doesn't support label stacking for multiple peers on same cell.

The freeze-pane rendering always passes stackIndex: 0 to drawPeerLabel, so if multiple peers are on the same cell in frozen panes, their labels will overlap. The non-freeze path at lines 641-648 correctly groups peers by cell and uses incrementing stack indices.

This is an edge case (multiple peers on same frozen cell is rare), but for consistency, consider extracting the stacking logic to a shared helper or applying the same grouping approach here.

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

In `@packages/sheets/src/view/overlay.ts` around lines 259 - 261, The freeze-pane
branch is always calling drawPeerLabel with stackIndex 0, causing overlapping
labels when multiple peers share the same frozen cell; update the logic in the
freeze-pane path (the block that checks visiblePeerLabels, presence.username,
peerColor, rect, port) to group peer presences by the same rect/port (same cell)
and call drawPeerLabel for each peer with an incrementing stackIndex (or extract
the existing grouping/stacking helper used in the non-freeze path and reuse it)
so labels are stacked the same way as in the non-freeze rendering.

Comment thread packages/sheets/src/view/worksheet.ts
Prevent stale timer callbacks after Worksheet destruction. Add tests
for label stacking with stackIndex and right-edge viewport clamping.

Co-Authored-By: Claude Opus 4.6 <[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: 1

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

Inline comments:
In `@packages/sheets/src/view/worksheet.ts`:
- Around line 2477-2489: The loop that checks peer presences calls
parseRef(presence.activeCell) without validating the value, which can throw for
malformed or empty strings; update the presence iteration in the mouse-move
handling code to first check presence.activeCell is a non-empty string (or
matches expected ref pattern) and wrap parseRef in a try-catch (or conditional
validation) so a bad remote value won’t throw and break the hover logic;
reference symbols: getPresences(), presence.activeCell, parseRef(),
toRowFromMouse(), toColFromMouse().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10bdc02a-212a-4f8a-bb32-fc6433db0909

📥 Commits

Reviewing files that changed from the base of the PR and between b752d79 and a722a01.

📒 Files selected for processing (2)
  • packages/sheets/src/view/__tests__/overlay-peer-labels.test.ts
  • packages/sheets/src/view/worksheet.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sheets/src/view/tests/overlay-peer-labels.test.ts

Comment thread packages/sheets/src/view/worksheet.ts
Wrap parseRef in try-catch in the hover detection loop to prevent
a malformed remote presence value from breaking the mouse handler.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@hackerwins
hackerwins merged commit 14d28c6 into main Mar 23, 2026
3 checks passed
@hackerwins
hackerwins deleted the feat/peer-cursor-labels branch March 23, 2026 06:18
@coderabbitai coderabbitai Bot mentioned this pull request Mar 23, 2026
2 tasks
@coderabbitai coderabbitai Bot mentioned this pull request May 8, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant