mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fix(realtime): correct assignment:created echo dedup (H11) (#1183)
When X-Idempotency/X-Socket-Id let an own-echo through, the assignment:created dedup had two bugs: it keyed on place id, so (1) a legitimate second assignment of a place already on the day was silently dropped, and (2) the temp-version reconciliation matched place?.id === placeId, letting undefined === undefined collapse place-less rows onto each other. - dedup now keys on assignment id (exact-id duplicate -> no-op) - temp (negative-id) optimistic rows are reconciled only when a real placeId matches, replacing just that row; a sibling temp of another place is untouched - everything else appends, including a genuine 2nd assignment of the same place - tests: 2nd-of-same-place kept, correct temp picked among siblings, place-less rows don't collapse Note: the broader own-echo suppression relies on X-Socket-Id being sent; this fixes the client-side fallback when an echo slips through.
This commit is contained in:
@@ -193,25 +193,34 @@ export function handleRemoteEvent(set: SetState, get: GetState, event: WebSocket
|
||||
|
||||
// Assignments
|
||||
case 'assignment:created': {
|
||||
const dayKey = String((payload.assignment as Assignment).day_id)
|
||||
const existing = (state.assignments[dayKey] || [])
|
||||
const placeId = (payload.assignment as Assignment).place?.id || (payload.assignment as Assignment).place_id
|
||||
if (existing.some(a => a.id === (payload.assignment as Assignment).id || (placeId && a.place?.id === placeId))) {
|
||||
const hasTempVersion = existing.some(a => a.id < 0 && a.place?.id === placeId)
|
||||
if (hasTempVersion) {
|
||||
return {
|
||||
assignments: {
|
||||
...state.assignments,
|
||||
[dayKey]: existing.map(a => (a.id < 0 && a.place?.id === placeId) ? payload.assignment as Assignment : a),
|
||||
}
|
||||
}
|
||||
const incoming = payload.assignment as Assignment
|
||||
const dayKey = String(incoming.day_id)
|
||||
const existing = state.assignments[dayKey] || []
|
||||
const placeId = incoming.place?.id ?? incoming.place_id
|
||||
|
||||
// Already have this exact assignment id → duplicate broadcast or the
|
||||
// echo of an already-committed assignment. No-op.
|
||||
if (existing.some(a => a.id === incoming.id)) return {}
|
||||
|
||||
// Reconcile our own optimistic create: replace the temp (negative-id)
|
||||
// assignment of the same place on this day with the real one. Guarded on
|
||||
// a real placeId so an assignment with no place can never collapse onto
|
||||
// another place-less one (undefined === undefined).
|
||||
if (placeId != null) {
|
||||
const tempIdx = existing.findIndex(a => a.id < 0 && a.place?.id === placeId)
|
||||
if (tempIdx !== -1) {
|
||||
const next = existing.slice()
|
||||
next[tempIdx] = incoming
|
||||
return { assignments: { ...state.assignments, [dayKey]: next } }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// Genuinely new — including a legitimate second assignment of a place
|
||||
// already on this day (no temp version to reconcile). Append.
|
||||
return {
|
||||
assignments: {
|
||||
...state.assignments,
|
||||
[dayKey]: [...existing, payload.assignment as Assignment],
|
||||
[dayKey]: [...existing, incoming],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user