From 1f853aa54e8be8054e601992aafebc721ad5210b Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Mon, 16 Feb 2026 18:30:27 -0800 Subject: [PATCH] Fix out of order path WS broadcasts overwriting each other --- frontend/AGENTS.md | 4 ++++ frontend/src/messageCache.ts | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 8c7b99b..0853854 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -91,6 +91,10 @@ Specialized logic is delegated to hooks: - WebSocket: realtime deltas/events. - On WS connect, backend sends `health` only; contacts/channels still come from REST. +### New Message modal + +`NewMessageModal` intentionally preserves form state (tab, inputs, checkboxes) when closed and reopened. The component instance persists across open/close cycles. This is by design so users don't lose in-progress input if they accidentally dismiss the dialog. + ### Message behavior - Outgoing sends are optimistic in UI and persisted server-side. diff --git a/frontend/src/messageCache.ts b/frontend/src/messageCache.ts index 75973d5..26e913d 100644 --- a/frontend/src/messageCache.ts +++ b/frontend/src/messageCache.ts @@ -70,11 +70,13 @@ export function updateAck(messageId: number, ackCount: number, paths?: MessagePa for (const entry of cache.values()) { const idx = entry.messages.findIndex((m) => m.id === messageId); if (idx >= 0) { + const current = entry.messages[idx]; const updated = [...entry.messages]; updated[idx] = { - ...entry.messages[idx], - acked: ackCount, - ...(paths !== undefined && { paths }), + ...current, + acked: Math.max(current.acked, ackCount), + ...(paths !== undefined && + paths.length >= (current.paths?.length ?? 0) && { paths }), }; entry.messages = updated; return; // Message IDs are unique, stop after first match