Skip to content

Conversation

@jhodapp
Copy link
Member

@jhodapp jhodapp commented Nov 14, 2025

Description

Fix collaboration cursor bubbles not appearing in coaching session notes. The issue was caused by the React useEffect cleanup function disconnecting the TipTap collaboration provider whenever dependencies changed, even when the session ID remained the same. This resulted in cursors disappearing when users clicked between browsers.

This also seems to fix the instability of the presence indicators at the top of the coaching session page as well. The unstable user presence in each browser (coach and coachee) seem to be directly related to unstable user carets as well.

Additional fixes: Further improvements to presence indicator behavior during network disconnects, visual consistency of collaboration carets, and UX enhancements for caret name bubbles.

GitHub Issue: Fixes #201 and Closes #184

Changes

Core Collaboration Fixes:

  • Fixed useEffect cleanup to only disconnect provider on session change, not on dependency changes
  • Prevented provider recreation when re-rendering with the same sessionId
  • Added guard to prevent multiple extension creation on repeated synced events
  • Switched from low-level provider.awareness.setLocalStateField() to high-level provider.setAwarenessField() API
  • Removed unnecessary mouse position tracking that was interfering with cursor tracking
  • Fixed awareness state type from Map to Array to match Hocuspocus API
  • Implemented unique color generation per user session using generateCollaborativeUserColor()

Presence Indicator Fixes:

  • Removed setAwarenessField call from disconnect event handler (message can't be delivered when already offline)
  • Preserve users who disappear from awareness states as disconnected instead of removing them
  • Merge previous presence state with current awareness data to maintain user tracking
  • Ensures network disconnect and browser close produce identical behavior (correct user shows as disconnected)

Visual Consistency & UX Fixes:

  • Changed collaboration caret height from 1.2em to 1em to match actual text height
  • Added line-height: inherit and vertical-align: baseline for consistent caret rendering
  • Ensures all carets have uniform heights regardless of document context
  • Added hover transparency effect (20% opacity) to caret name bubbles for better content visibility
  • Changed pointer-events from none to auto to enable hover interactions
  • Added cursor pointer and enhanced shadow on hover for better interactivity feedback

Test Coverage:

  • Added comprehensive test coverage for provider lifecycle, extension creation, and awareness state management
  • Added test for users being marked as disconnected when they disappear from awareness states
  • Updated disconnect event test to reflect new behavior (no setAwarenessField call)
  • All 11 tests passing

Screenshots / Videos Showing UI Changes (if applicable)

Screenshot 2025-11-13 at 22 14 30 Screenshot 2025-11-13 at 22 14 42

Testing Strategy

  1. Unit Tests: All 11 tests in editor-cache-context.test.tsx pass, covering:

    • Provider lifecycle (no disconnect on re-render with same sessionId)
    • Provider cleanup (disconnect on sessionId change)
    • Extension creation (only once despite multiple synced events)
    • Awareness API usage (high-level setAwarenessField())
    • Presence state updates on awarenessChange events
    • Users marked as disconnected when they disappear from awareness states
    • No setAwarenessField call on disconnect event
  2. Manual Testing:

    • Open the same coaching session in two browser windows
    • Log in as coach in one, coachee in the other
    • Click around and type in the coaching notes editor
    • Verify both users can see each other's cursors continuously
    • Verify cursors don't disappear when clicking between browsers
    • Test network disconnect: Set one browser to offline mode in dev tools
    • Verify the correct user shows as disconnected (grey indicator)
    • Verify caret heights are consistent across all text contexts
    • Hover over caret name bubbles and verify they become transparent (20% opacity)

Concerns

None. The fix addresses the root cause of the provider disconnection issue and includes comprehensive test coverage to prevent regression.

Resolved issue where collaboration cursors would disappear when clicking
between browsers by fixing critical useEffect cleanup bug.

Key fixes:
- Prevent provider disconnect on dependency changes (only on session change)
- Prevent extension recreation with synced event guard
- Add provider reinitialization guard
- Fix awareness state type (Array not Map)
- Generate unique colors per user
- Remove mouse position tracking
Use provider.setAwarenessField() instead of provider.awareness.setLocalStateField()
for better encapsulation and consistency with the public Hocuspocus API.
Add test coverage for Issue #201 fixes:
- Provider lifecycle: verify provider NOT disconnected on re-render with same sessionId
- Provider cleanup: verify provider IS disconnected when sessionId changes
- Extension creation: verify extensions created only once despite multiple synced events
- Awareness API: verify high-level setAwarenessField() is used for presence
- Awareness state: verify presence state updates on awarenessChange events
- Disconnect handling: verify awareness set to disconnected status on disconnect event
@jhodapp jhodapp requested a review from calebbourg November 14, 2025 04:17
@jhodapp jhodapp self-assigned this Nov 14, 2025
@jhodapp jhodapp added the bug fix Fixes a specific Issue label Nov 14, 2025
@jhodapp jhodapp added this to the 1.0.0-beta2 milestone Nov 14, 2025
Root cause: When a user's network goes offline, the disconnect event would
try to call setAwarenessField, but this message can't be delivered since
the connection is already lost. Additionally, when users disappeared from
the awareness states array, they were completely removed from the UI rather
than being marked as disconnected.

Changes:
- Remove setAwarenessField call from disconnect event handler (already offline)
- Preserve users who disappear from awareness states as disconnected
- Merge previous presence state with current awareness data
- Users now show correct disconnected status when they go offline

This ensures network disconnect and browser close produce identical behavior:
both result in the correct user showing as disconnected (grey indicator).
The caret height was using 1.2em which is relative to the font-size
of the current context, causing different heights depending on where
the cursor appears in the document.

Changes:
- Changed height from 1.2em to 1em to match actual text height
- Added line-height: inherit to match the line-height of surrounding text
- Added vertical-align: baseline for proper alignment
- Removed min-height: 16px as it's no longer needed

This ensures all collaboration carets have consistent heights that
match the line height of the text where they appear.
When hovering over a collaboration caret name bubble with the mouse,
it now becomes highly transparent (20% opacity) to improve UX by
allowing users to see through the label when it's covering content.

Changes:
- Changed pointer-events from none to auto to enable hover interactions
- Added opacity: 0.2 on hover for the label bubble and tail (80% transparent)
- Consolidated duplicate hover effects into single rule
- Maintains existing hover transform and shadow effects
- Added cursor: pointer to indicate interactivity
Copy link
Collaborator

@calebbourg calebbourg left a comment

Choose a reason for hiding this comment

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

1 question and one suggested change for the tests!

expect(screen.getByTestId('is-ready')).toHaveTextContent('no')
})

describe('Provider Lifecycle (Issue #201 Fixes)', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe a more descriptive name so we don't have to look back at the issue to understand the need for the test?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 1c4d370

Comment on lines 197 to 210
setCache((prev) => {
const mergedUsers = new Map(prev.presenceState.users);

// Mark users who disappeared from awareness as disconnected
for (const [userId, oldPresence] of prev.presenceState.users) {
if (!updatedUsers.has(userId) && oldPresence.status === 'connected') {
// User was connected but no longer in awareness states - mark as disconnected
mergedUsers.set(userId, {
...oldPresence,
status: 'disconnected',
isConnected: false,
lastSeen: new Date()
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm curious what this provides? What is the issue if users "Disappear" and what does that mean exactly?

Copy link
Member Author

Choose a reason for hiding this comment

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

When users "disappear," it means they're no longer in the awareness states array - typically due to network disconnect, browser crash, or navigation away from the coaching session page. Without this code, disconnected users would instantly vanish from the UI, creating an unwanted UX. This preserves them as status: 'disconnected' instead, enabling smooth UX transitions (like showing grayed-out presence indicators).

Copy link
Member Author

Choose a reason for hiding this comment

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

Added this as a code comment in commit 1c4d370

- Update test description to be self-explanatory without referencing issue number
- Add detailed comment explaining awareness state handling for disconnected users
@jhodapp jhodapp merged commit a411e58 into main Nov 14, 2025
6 checks passed
@jhodapp jhodapp deleted the 201-broken-collaboration-cursors branch November 14, 2025 15:35
@github-project-automation github-project-automation bot moved this from Review to ✅ Done in Refactor Coaching Platform Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug fix Fixes a specific Issue

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

[Bug]: Cursor and collaboration bubble missing from other collaborator during coaching session [Bug]: New collaboration bubbles cover over other text

3 participants