All checks were successful
CD / Build and push images (push) Successful in 2m54s
CI / Lint, typecheck, test (push) Successful in 1m58s
CI / Auth e2e pack (push) Successful in 2m10s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m15s
CD / Promote to Int (push) Successful in 11s
Seeing other participants live (ADR 0003/0004, realtime-collaboration.md §Awareness): - The collaboration-caret extension renders remote carets and selections with a name flag and a per-user colour. Colours come from a small, hand-picked palette hashed by user id (FNV-1a), so they are stable across sessions; a unit test asserts each palette colour clears WCAG AA contrast (4.5:1) against the white label text. - A presence strip at the top of the page shows an avatar (initials) per connected participant, deduplicated by user id, with an overflow count. Read-only participants appear in the strip (with a marker) but broadcast no caret — the caret render suppresses read-only users — so the same awareness feed drives both cursors and presence. Own identity (id + display name) comes from the auth context into the awareness `user` field. - Presence updates on every awareness change, so a disconnect drops the participant within seconds. The collab e2e pack gains a test: two browsers see each other in the presence strip, one participant's named caret appears in the other's editor, and disconnecting removes them. Validated locally against the full stack. de + en strings and cursor/presence styles added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import type { HocuspocusProvider } from '@hocuspocus/provider';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import type { CaretUser } from './collaboration-caret';
|
|
|
|
export type Participant = CaretUser;
|
|
|
|
/**
|
|
* The current participants on a page, derived from the collab awareness states
|
|
* (issue #37). Deduplicated by user id (a user with two tabs is one avatar) and
|
|
* updated whenever awareness changes, so a disconnect drops the participant
|
|
* within seconds.
|
|
*/
|
|
export function usePresence(provider: HocuspocusProvider | null): Participant[] {
|
|
const [participants, setParticipants] = useState<Participant[]>([]);
|
|
|
|
useEffect(() => {
|
|
const awareness = provider?.awareness;
|
|
if (!awareness) {
|
|
setParticipants([]);
|
|
return;
|
|
}
|
|
const update = (): void => {
|
|
const byUser = new Map<string, Participant>();
|
|
for (const state of awareness.getStates().values()) {
|
|
const user = (state as { user?: Partial<CaretUser> }).user;
|
|
if (!user?.userId || byUser.has(user.userId)) continue;
|
|
byUser.set(user.userId, {
|
|
userId: user.userId,
|
|
name: user.name ?? '',
|
|
color: user.color ?? '#495057',
|
|
readOnly: Boolean(user.readOnly),
|
|
});
|
|
}
|
|
setParticipants([...byUser.values()]);
|
|
};
|
|
update();
|
|
awareness.on('change', update);
|
|
return () => awareness.off('change', update);
|
|
}, [provider]);
|
|
|
|
return participants;
|
|
}
|