Show who else is on a strategy and their live cursors (#214)

* Show who else is on a strategy and their live cursors

Each cloud strategy gets a presence room: one Cloudflare Durable Object
(presence/) that relays who is connected and where their cursor is, and
stores nothing. Convex decides who may join: presence:issueRoomPass checks
viewer access and signs a two-minute pass the Worker verifies with a shared
secret. Clients renew every minute, so revoked access drops out within one
pass lifetime.

The editor's window strip shows teammates' avatars, ringed in a per-person
color. Their cursors appear on the map when they are on the page you are
viewing, in canonical coordinates so zoom, window size and side don't
matter. Cursors are throttled to 20 updates a second and never enter the
op queue or the sync status. Local strategies have no room.

Deploy Web now deploys the presence Worker before the web build, and CI
runs its tests in the Workers runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Harden presence after review: wss URL, pass hygiene, lifetimes

- Convex returns the room as wss://; WebSocket clients refuse https://,
  so presence never connected.
- The Worker keeps passes out of logs (redact_query_string) and never
  forwards the query to the room.
- Rooms stop sending to a socket as soon as its pass runs out, and wake at
  the earliest expiry, so revoked access ends on time even for a silent
  socket.
- One person gets at most four sessions per room; renewals faster than one
  per ten seconds are ignored before any HMAC work; malformed passes are
  rejected with 401 instead of throwing.
- The presence provider auto-disposes, so leaving the editor any way
  (including browser back) leaves the room.
- The cursor is re-sent when the map moves under a still pointer: scroll
  zoom, page or side change, resize.
- Person colors use arithmetic that is exact on web and native, and the
  cursor shadows use the named card shadow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix presence tooltips, email names, and clipped name tags

Found in the live two-account test:
- Avatar tooltips never opened: ShadTooltip only listens to children that
  report hover through the Shad theme, so the ring is a
  ShadGestureDetector now. A widget test hovers it.
- Email sign-ins showed as "Discord user", the ensureCurrentUser
  placeholder. The pass now names them by the part of their address
  before the @.
- Name tags ran off the map near its right and bottom edges; they flip to
  the other side of the arrow there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open presence tooltips below the strip; report zoomed cursors once per frame

Tooltips opened above the avatars, which is off the top of the window.
A wheel zoom sets the transform twice (scale, then the pan that keeps the
pointer's spot fixed); re-reporting on each change sent the in-between
position, so a still cursor flicked across teammates' maps. Re-reports now
wait for the frame to settle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
afa19dfa1aDara Adedeji committed on 9/27/2026, 7:28:48 PM· committed by GitHubparenta8b34ce
34 files changedLine totals unavailable