Appearance
State & Events
Subscribe to real-time state changes from PulseClient.
When Do You Need This?
Only if you're building custom UI instead of using <pulse-widget>.
Subscribing to Changes
Use client.state.on() to get notified when data changes. It returns an unsubscribe function.
typescript
// Fires when auth completes — safe point to read config and initial state
client.state.on('auth', (user) => {
console.log('Signed in as', user.name);
console.log('Max file size:', client.state.config.maxFileSizeMb, 'MB');
renderUI();
});
// Threads updated (created, resolved, deleted, new comment, etc.)
const unsub = client.state.on('threads', (threads) => {
renderThreadList(threads);
});
// Presence changed (user joined, left, or changed status)
client.state.on('presence', (users) => {
renderAvatars(users);
});
// Notifications
client.state.on('notifications', (notifs) => {
const unread = notifs.filter(n => !n.read).length;
updateBadge(unread);
});
// Reactions
client.state.on('reactions', ({ targetId, reactions }) => {
updateReactionCounts(targetId, reactions);
});
// Activity log
client.state.on('activity-logs', (logs) => {
renderActivityFeed(logs);
});
// Unsubscribe when done
unsub();Real-Time Events
Individual events for live interactions:
typescript
// Someone's cursor moved
client.state.on('cursor', ({ userId, position }) => { ... });
// Someone clicked
client.state.on('click', ({ userId, position }) => { ... });
// Someone is typing
client.state.on('typing', ({ userId, threadId }) => { ... });
// Viewport update
client.state.on('viewport', ({ userId }) => { ... });
// Text selection changed
client.state.on('selection', ({ userId, selection }) => { ... });
// Drawing stroke received
client.state.on('draw-stroke', ({ userId, points, color, width }) => { ... });
// Drawing cleared
client.state.on('draw-clear', ({ userId }) => { ... });
// Emoji drop
client.state.on('emoji-drop', ({ userId, emoji, position }) => { ... });Connection Events
These fire on client directly:
typescript
client.on('connection', (state) => {
// 'connected' | 'connecting' | 'disconnected'
});
client.on('auth:ok', (data) => {
console.log('Authenticated as:', data.user.name);
});
client.on('auth:error', (data) => {
console.error('Auth failed:', data.message);
});Full Event Reference
| Event | Fires On | Payload | When |
|---|---|---|---|
auth | client.state | PulseUser | Authentication completed; initial state populated |
threads | client.state | Thread[] | Any thread or comment change |
presence | client.state | PresenceUser[] | User joined, left, or changed status |
notifications | client.state | Notification[] | New notification or read state changed |
reactions | client.state | { targetId, reactions } | Reaction added or removed |
activity-logs | client.state | ActivityLog[] | New activity logged |
cursor | client.state | { userId, position } | Cursor moved |
click | client.state | { userId, position } | Click indicator |
typing | client.state | { userId, threadId } | Typing indicator |
viewport | client.state | { userId } | Viewport scrolled |
selection | client.state | { userId, selection } | Text selection changed |
draw-stroke | client.state | { userId, points, color, width } | Drawing stroke received |
draw-clear | client.state | { userId } | Drawing cleared |
emoji-drop | client.state | { userId, emoji, position } | Emoji drop received |
connection | client | 'connected' | 'connecting' | 'disconnected' | WebSocket state changed |
auth:ok | client | auth data | Server accepted credentials |
auth:error | client | { message } | Server rejected credentials |
State Properties
Direct accessors on client.state:
| Property | Type | Description |
|---|---|---|
user | PulseUser | null | Current authenticated user |
config | EnvironmentConfig | Environment feature flags (set on auth, read-only) |
users | PulseUser[] | All known users in the room (from presence + comment authors) |
threads | Thread[] | All threads in the room |
presence | PresenceUser[] | Currently online users |
notifications | Notification[] | Notifications for the current user |
unreadCount | number | Count of unread notifications |
activityLogs | ActivityLog[] | Recent room activity (max 100) |
Key Types
typescript
interface PulseUser {
id: string;
name: string;
avatar?: string;
color: string; // auto-assigned by Pulse
}
interface Thread {
id: string;
roomId: string;
resolved: boolean;
position: PinPosition | null;
comments: Comment[];
createdAt: string;
updatedAt: string;
}
interface PinPosition {
x: number;
y: number;
selector?: string;
path?: string;
elementOffsetX?: number;
elementOffsetY?: number;
scrollX?: number;
scrollY?: number;
}
interface Comment {
id: string;
threadId: string;
userId: string;
body: string;
mentions: string[];
attachments: Attachment[];
createdAt: string;
editedAt: string | null;
}
interface Attachment {
id: string;
commentId: string;
type: 'image' | 'audio' | 'video';
filename: string;
mimeType: string;
sizeBytes: number;
url: string;
thumbnailUrl?: string;
durationMs?: number;
width?: number;
height?: number;
createdAt: string;
}
interface PresenceUser {
user: PulseUser;
cursor: CursorPosition | null;
status: 'online' | 'idle';
lastSeen: string;
deviceType?: 'desktop' | 'mobile' | 'tablet';
}
interface CursorPosition {
x: number;
y: number;
pageX: number;
pageY: number;
}
interface Notification {
id: string;
userId: string;
type: 'comment:created' | 'comment:mention' | 'comment:reply'
| 'thread:resolved' | 'reaction:added';
roomId: string;
threadId?: string;
commentId?: string;
actorId: string;
read: boolean;
createdAt: string;
}
interface Reaction {
id: string;
targetId: string;
targetType: 'comment' | 'thread';
userId: string;
emoji: string;
createdAt: string;
}
interface ActivityLog {
id: string;
roomId: string;
userId: string;
type: 'join' | 'leave' | 'comment' | 'thread' | 'resolve';
description: string;
createdAt: string;
}
interface SelectionRange {
startSelector: string;
startOffset: number;
endSelector: string;
endOffset: number;
}
interface EnvironmentConfig {
allowImages: boolean;
allowAudio: boolean;
allowVideo: boolean;
maxFileSizeMb: number;
maxAttachmentsPerComment: number;
allowReactions: boolean;
allowDrawing: boolean;
allowMentions: boolean;
showCursors: boolean;
showPresence: boolean;
showTypingIndicators: boolean;
}