feat: [US-049] - Node editing lock indicators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda
2026-01-24 02:43:06 -03:00
co-authored by Claude Opus 4.5
parent 5b84170a28
commit 4a85d7a64b
7 changed files with 225 additions and 10 deletions
+36
View File
@@ -20,6 +20,13 @@ export type RemoteCursor = {
lastUpdated: number
}
export type NodeLock = {
nodeId: string
userId: string
displayName: string
lockedAt: number
}
type RealtimeCallbacks = {
onConnectionStateChange: (state: ConnectionState) => void
onPresenceSync?: (users: PresenceUser[]) => void
@@ -27,6 +34,7 @@ type RealtimeCallbacks = {
onPresenceLeave?: (user: PresenceUser) => void
onChannelSubscribed?: (channel: RealtimeChannel) => void
onCursorUpdate?: (cursor: RemoteCursor) => void
onNodeLockUpdate?: (lock: NodeLock | null, userId: string) => void
}
const HEARTBEAT_INTERVAL_MS = 30_000
@@ -109,6 +117,20 @@ export class RealtimeConnection {
lastUpdated: Date.now(),
})
})
.on('broadcast', { event: 'node-lock' }, ({ payload }) => {
if (payload.userId === this.userId) return
if (payload.nodeId) {
this.callbacks.onNodeLockUpdate?.({
nodeId: payload.nodeId,
userId: payload.userId,
displayName: payload.displayName,
lockedAt: payload.lockedAt,
}, payload.userId)
} else {
// null nodeId means unlock
this.callbacks.onNodeLockUpdate?.(null, payload.userId)
}
})
.subscribe(async (status) => {
if (this.isDestroyed) return
@@ -167,6 +189,20 @@ export class RealtimeConnection {
})
}
broadcastNodeLock(nodeId: string | null): void {
if (!this.channel) return
this.channel.send({
type: 'broadcast',
event: 'node-lock',
payload: {
userId: this.userId,
displayName: this.displayName,
nodeId,
lockedAt: Date.now(),
},
})
}
private async createSession(): Promise<void> {
try {
await this.supabase.from('collaboration_sessions').upsert(