Skip to content

Conversation

@spencerc99
Copy link
Owner

@spencerc99 spencerc99 commented Feb 11, 2026

Add cursor presence and stable awareness APIs with improved element awareness scoping

This release adds new APIs for accessing cursor positions and per-user awareness data keyed by stable user IDs, eliminating the need to access internal providers directly.

New Cursor Presence APIs:

// Get all cursor presences (slim shape, keyed by stable ID)
const presences = playhtml.cursorClient.getCursorPresences();

// Subscribe to cursor presence changes
const unsubscribe = playhtml.cursorClient.onCursorPresencesChange((presences) => {
  // presences is Map<string, CursorPresenceView>
});

// Get my stable player identity
const identity = playhtml.cursorClient.getMyPlayerIdentity();
const stableId = identity.publicKey; // Persists across sessions

New Awareness API - awarenessByStableId:

Element awareness is now provided keyed by stable user ID in addition to the array format:

// Core playhtml
updateElementAwareness: ({ awareness, awarenessByStableId }) => {
  const userDrunkLevel = awarenessByStableId.get(stableId)?.drunkLevel;
}

// React withSharedState
withSharedState(({ data, awareness, awarenessByStableId, myAwareness }) => {
  const userDrunkLevel = awarenessByStableId.get(stableId)?.drunkLevel;
  // ...
});

New React Hook:

import { useCursorPresences } from "@playhtml/react";

function MyComponent() {
  const cursorPresences = useCursorPresences();
  // Map<string, CursorPresenceView> keyed by stable ID
}

Breaking Change - Awareness Scope:

Element awareness now follows cursor scope instead of always being page-specific:

  • If cursors are configured with room: "domain", element awareness is domain-wide
  • If cursors are configured with room: "page", element awareness is page-specific
  • If cursors are configured with room: "section", element awareness is section-specific

This is more intuitive (your user state follows you) but may affect apps that relied on the old behavior. To restore page-specific awareness when cursors are domain-wide, you can configure cursors to use page scope separately.

Benefits:

  • Stable user IDs (playerIdentity.publicKey) persist across page refreshes
  • No need to access (playhtml.cursorClient as any).provider - use clean public APIs
  • Easier to correlate cursor positions with user-specific awareness data
  • Awareness scope matches cursor scope for more intuitive behavior

Note

Medium Risk
Changes cursor coordinate storage/rendering and moves element awareness onto the cursor provider, which can change awareness scope/behavior across rooms and affect existing integrations.

Overview
Adds stable, public cursor/awareness APIs. CursorClientAwareness now exposes getMyPlayerIdentity(), getCursorPresences() (slim CursorPresenceView keyed by playerIdentity.publicKey), and onCursorPresencesChange(), and common types add CursorPresenceView.

Changes element awareness behavior (breaking). Element awareness is now read/written via the cursor provider (matching cursor room scope) and is delivered to element handlers as both the existing awareness array and new awarenessByStableId: Map<stableId, awareness>, using getStableIdForAwareness() for stable-ID resolution.

Updates cursor rendering/coords + React surface area. Cursor tracking adds optional coordinateMode (relative viewport % vs absolute document px) with coordinate conversions and spring-smoothed cursor movement; @playhtml/react exposes cursorPresences via context + new useCursorPresences() hook, updates withSharedState to pass awarenessByStableId, and migrates the drunk-cursor experiment off internal provider access.

Also adjusts CI test command and adds targeted unit tests for stable-ID awareness resolution and handler awareness updates.

Written by Cursor Bugbot for commit b653b93. This will update automatically on new commits. Configure here.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// Listen to awareness changes on the cursor provider (where element awareness is stored)
// Fall back to doc provider if cursors are disabled
const awarenessProvider = cursorClient?.getProvider() ?? yprovider;
awarenessProvider.awareness.on("change", () => onChangeAwareness());
Copy link

Choose a reason for hiding this comment

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

Element awareness rebuilds on every cursor movement

Medium Severity

The onChangeAwareness function is now registered on the cursor provider's awareness change event. Since the cursor client also writes to this same provider on every mouse move (at up to 60fps per user), onChangeAwareness fires on every cursor movement from any connected user — even though only __playhtml_cursors__ changed and element awareness data is unchanged. Each invocation rebuilds new array and Map references for every element with awareness, unconditionally calling handler.updateAwareness, which in the React layer triggers state updates and re-renders for all awareness-consuming components.

Additional Locations (1)

Fix in Cursor Fix in Web

return userAwareness?.drunkLevel ?? 0;
},
[drunkLevel],
[drunkLevel, awarenessByStableId],
Copy link

Choose a reason for hiding this comment

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

Animation loop restarts on every awareness change

Medium Severity

getUserDrunkLevel now depends on awarenessByStableId (React state), which gets a new Map reference on every awareness change — including cursor movements from all users. Since the animation useEffect at line 492 has getUserDrunkLevel as a dependency, the smooth cursor interpolation loop (smoothStep) restarts on every remote awareness change, causing visible animation jitter. The old code read directly from the provider (avoiding React state), keeping the callback stable across awareness changes.

Additional Locations (1)

Fix in Cursor Fix in Web

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