Implements Option 2 of #93. The restricted branch of broadcastPageMoved previously resolved its audience twice — emitToAuthorizedUsers and emitDeleteToUnauthorized each ran an independent fetchSockets + getUserIdsWithPageAccess — leaving a race window between the two snapshots where a socket could receive both the move and the delete (leak) or neither (lost compensating delete). - ws.service.ts: add emitMoveWithRestrictionSplit() that takes ONE socket snapshot and ONE authorization resolution, then partitions the room: authorized users get the moveTreeNode, everyone else (unauthorized + anonymous) get the compensating deleteTreeNode. Disjoint + complete by construction. Remove the now-unused emitToAuthorizedUsers / emitDeleteToUnauthorized; keep private broadcastToAuthorizedUsers (still used by emitRestrictedAwareToSpace). - ws-tree.service.ts: broadcastPageMoved restricted branch now drives move + delete from the single method. - specs: assert the single method is used and that fetchSockets / getUserIdsWithPageAccess are each called exactly once (single snapshot); re-route ws-service.spec to emitTreeEvent after the method removal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
18 KiB
18 KiB