Context
The server currently relays messages without any acknowledgment protocol. Clients have no way to know if an operation succeeded, failed validation, or was rejected due to permissions/conflicts. Sim's server confirms or rejects every structural operation so clients can reconcile.
What to build
Server: Ack/Nack protocol
- For every structural operation received, server responds to the sender only with:
{ type: "ack", opId: "...", status: "ok" } on success
{ type: "nack", opId: "...", status: "error", reason: "..." } on failure (validation, conflict, permissions)
- Value ops (text edits) don't need individual acks — they're optimistic
Client: Reconciliation on nack
- On
ack: mark operation as complete in the operation queue, proceed to next
- On
nack: depending on reason:
- Validation error: revert local state, remove from queue
- Conflict: re-fetch authoritative state for that entity, apply server version
- Permissions: revert local state, show user notification
- On timeout (no ack within N seconds): retry the operation (ties into operation queue retry logic)
Operation IDs
- Every outbound operation needs a unique
opId (UUID) so acks can be correlated
- Add
opId field to CanvasOutboundEvent types
Current code references
services/collab/src/main/java/com/leetdoodle/collab/handler/CanvasWebSocketHandler.java — relay logic (no ack today)
frontend/src/canvas/hooks/useCanvasCollab.ts — send() with no response handling
frontend/src/shared/events.ts — event types (no opId field today)
Acceptance criteria
Context
The server currently relays messages without any acknowledgment protocol. Clients have no way to know if an operation succeeded, failed validation, or was rejected due to permissions/conflicts. Sim's server confirms or rejects every structural operation so clients can reconcile.
What to build
Server: Ack/Nack protocol
{ type: "ack", opId: "...", status: "ok" }on success{ type: "nack", opId: "...", status: "error", reason: "..." }on failure (validation, conflict, permissions)Client: Reconciliation on nack
ack: mark operation as complete in the operation queue, proceed to nextnack: depending on reason:Operation IDs
opId(UUID) so acks can be correlatedopIdfield toCanvasOutboundEventtypesCurrent code references
services/collab/src/main/java/com/leetdoodle/collab/handler/CanvasWebSocketHandler.java— relay logic (no ack today)frontend/src/canvas/hooks/useCanvasCollab.ts—send()with no response handlingfrontend/src/shared/events.ts— event types (no opId field today)Acceptance criteria