Chat race conditions #474
No reviewers
Labels
No labels
app
app
bug
app
enhancement
client side exclusive
closed
completed
closed
duplicate
closed
invalid
closed
not open grind
closed
not planned
closed
question
🗳️ community vote
⚠️ critical
discussion
docs
emulator
help wanted
paywalled
bypassable
paywalled
non-bypassable
paywalled
unconfirmed
question
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
open-grind/open-grind!474
Loading…
Reference in a new issue
No description provided.
Delete branch "Enucatl/open-grind:fix/chat-snapshot-races"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A delayed snapshot removes a new message.
The app reconnects and requests chat history. While that request is pending, a live message arrives: “Are you nearby?” The response then arrives with older history that excludes it. Previously, the response could remove the message and replace the inbox preview with older text. The fix keeps messages that arrived after the request started.
Recovery produces the wrong unread count.
You miss five messages while offline. During recovery, a sixth arrives live. The delayed snapshot reports five unread; the local state knows about only the sixth. Taking the larger count gives five instead of six. The reverse can happen when another device reads earlier messages: the app keeps too many unread. The fix preserves live activity and requests a fresh snapshot to obtain the correct count.
Old work restarts after switching accounts.
Account A has an inbox request pending. A message for an unloaded conversation joins that request. You switch to account B, destroying A’s state. When A’s request finishes, its waiting message handler could interpret the missing conversation as a reason to retry. That retry uses B’s current credentials and can update A’s retired state or invoke notification callbacks. The fix invalidates pending requests and stops their continuations after destruction.
Also squash commits and force push
@ -10,3 +10,3 @@dim: '[data-slot="live-stack-dim"]',};const NO_EDGE = "0 0 #0000";const NO_EDGE = "-1px 0 0 rgb(255 255 255 / 0.001)";Unrelated to chat, move to a separate PR with the spec it fixes
moved to #483
@ -185,3 +185,3 @@if (this.#seenMessages.has(message.messageId)) {if (!this.#isActive(conversationId))if (conversationId !== this.#activeConversationId)Why inline
#isActive?reverted
@ -222,3 +223,3 @@const isInboxPageRoot = page.route.id === "/(protected)/chat";const twoColLayout = !singleColumnLayout.current;const isConversationsListVisible = isInboxPageRoot || twoColLayout;const isConversationsListVisible =page.route.id === "/(protected)/chat" ||!singleColumnLayout.current;Why
isInboxPageRootandtwoColLayoutwere removed?reverted
@ -278,3 +282,1 @@const windowFloor = reachedEnd? Number.NEGATIVE_INFINITY: oldestFetchedTs;const windowFloor = reachedEnd ? -Infinity : oldestFetchedTs;Number.NEGATIVE_INFINITY, as at 252 and across the codebase. Same at 288, 622reverted
@ -334,3 +342,1 @@}: {errorLabel?: string;}): Promise<boolean> {async #runSyncLatest(args: { errorLabel?: string }): Promise<boolean> {Why stop destructuring
errorLabel?reverted
@ -436,3 +443,1 @@const joinedSyncWasTooEarly =joinedRunningSync && syncSucceeded && !this.#find(id);if (joinedSyncWasTooEarly) await this.ensureLoaded(id);if (joinedRunningSync && syncSucceeded && !this.#find(id))joinedSyncWasTooEarlydroppedreverted
@ -606,3 +607,1 @@return this.entries.find((e) => e.data.conversationId === conversationId,);#find(id: string): Conversation | undefined {Unrelated rename, revert
reverted
@ -618,3 +611,5 @@const { conversationId } = incoming.data;this.#readRequests.noteSnapshot(conversationId);mergeConversation({#mergeIncoming(existing: Conversation,incoming: Conversation,atStart: Map<string, number>,): void {Codebase uses named-params rule. You can accidentally swap
existingandincoming. AlsoatStart/startedWithnames@ -7,16 +7,29 @@ export function mergeConversation({existing,incoming,pendingFlags,lastActivityAtFetchStart = existing.data.lastActivityTimestamp,Make it required, the default brings the old race back for any new caller
required now
@ -242,3 +243,1 @@this.#syncCache();return;}if (!changed) return this.#syncCache();Unrelated, revert
reverted
@ -144,15 +144,18 @@ export function sentMessages(export function mergeServerMessages({local,server,localIdsAtFetchStart = local.map((m) => m.messageId),Make it required, the default brings the old race back for any new caller
required now
Enucatl referenced this pull request2026-10-16 10:00:29 +02:00
362ff376e572460a981cThanks!