mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
The thread pane renders the thread-replies query cache, whose only live writer was the relay's WebSocket echo (post-commit, fire-and-forget on the relay). useSendMessageMutation optimistically painted the channel window but never threadRepliesKey(channelId, rootId), so a user's own thread reply waited a full round-trip plus fanout lag to appear while the main pane painted instantly — the thread pane absorbed 100% of echo lag. Mirror the optimistic lifecycle into the thread cache: - onMutate inserts the pending reply under the resolved thread root (insertPendingThreadReply). Root resolution now searches cached thread subtrees too (collectReplyParentContext) — a nested reply's parent lives in the thread cache, not the channel timeline, and the old channel-only lookup would mis-root it. - onSuccess swaps pending → confirmed under the same localKey (reconcileSentThreadReply), so the row never remounts and a WS echo racing ahead of the REST response cannot duplicate (isMatchingPendingMessage already reconciles the echo in place). - onError removes only the failed pending entry (rollbackPendingThreadReply) — a snapshot restore would drop live replies that arrived during the in-flight send. - useThreadReplies' refetch snapshot now also preserves still-pending optimistic sends, which the server cannot return yet. The e2e mock relay previously echoed sends synchronously inside the send command — exactly why no test ever caught this. Add a sendEchoDelayMs knob that defers the echo like the real relay's post-commit fanout, plus a regression spec (proven red pre-fix) that asserts own-sends paint before the echo, the late echo reconciles without duplicating, and a thread switch mid-send never bleeds the pending reply into the wrong thread. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>