Identity-aware per-space presence — one Realtime channel per space
(lib/use-space-collab.ts) drives both presence and cursors. The TopBar count
now reflects distinct people in the active space (deduped by user), each with
a stable color from lib/identity.ts. Fixes the misleading global socket count.
Sharing & roles — space_invites + profiles tables (migration
20260610000100), claim_space_invites() RPC (claims pending invites by email
on sign-in). lib/supabase/members.ts covers list/invite/role-change/revoke
(RLS-enforced; owner-only writes). New ShareDialog to invite by email
(viewer/editor), change roles, revoke access, and copy a link.
Live cursors — colored pointer + name per collaborator, broadcast in flow
coords (so they map across pan/zoom), throttled, with idle cleanup. Color
matches each person's avatar.
Viewer read-only mode — useWorkspace exposes role/canEdit/isOwner
(kept live via a space_members subscription). Viewers get no toolbar, no AI,
no drag/connect/paste, and a read-only context menu; the canvas hides delete
handles. RLS is the backstop.
Changed
TopBar presence is now PresencePerson[] (identity colors + initials),
shared by cloud peers and signed-out WS guests; Share button opens the dialog.
lib/supabase/client.ts already singleton; types regenerated for the new
tables + RPC.
Notes
claim_space_invites is an intentional authenticated-only SECURITY DEFINER
RPC (self-guards via auth.uid()/auth.jwt()); the advisor flag is expected.
Inline text/sticky editing isn't yet blocked for viewers at the field level
(RLS rejects the save); a fast-follow can make those fields non-editable.