Fix messenger read receipts never updating - #92
Merged
Conversation
Two gaps kept a sent message on a single tick. The recipient marks messages seen when they open the conversation, but nothing ever told the sender, so the ticks only caught up on a page reload. That is the "sometimes it works" in the report: reopening the chat refetches the real state. The sender's own message was also unreachable. It renders optimistically with a temp- client id and send() returned nothing, so the row never got its stored id and no update could ever match it. send() now returns the stored id and the client swaps it in. The unused check-new-messages endpoint reports which of the sender's recent messages have been read, and the open conversation polls it. Fixes #90
This was referenced Jul 27, 2026
HafizMMoaz
added a commit
that referenced
this pull request
Jul 27, 2026
Follow up to #92, which did not fully fix #90. The recipient only ever marked a conversation seen on a page load or a page of getMessages. A message that arrives over the websocket lands in a chat that is already open and never refetches, so nothing marked it, and the sender's tick stayed on one until somebody reloaded. That is the remaining half of "sometimes it works": the reload that fixes it has to be the recipient's. The poll that #92 added now marks the open conversation seen, which is the same thing a page load does. It runs immediately rather than after a full interval, and skips while the tab is hidden so a chat left open in a background tab does not report messages nobody looked at as read. The conversation query built `(A) or (B) and (C)`. SQL binds `and` tighter than `or`, so the delete filter only ever applied to received messages and a sender deleting their own message saw it return on the next load. It only looked deleted once the recipient deleted it too, which hard deletes the row. Grouping the two halves of the conversation fixes it in both getMessages and the Inertia index. Fixes #90
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #90
Root cause
Two separate gaps, both on the sender's side:
getMessages()flipsseenwhen the recipient opens the conversation, but no event, no poll, nothing told the sender. The frontend only polls online status. That is the "sometimes it is working" in the report: reopening the chat refetches messages and the ticks look correct again, so the state was never wrong in the database, only on screen.handleSendMessagerenders optimistically withid: temp-<timestamp>andsend()returned only{success, message}, so the row never picked up its stored id. Even with a read receipt arriving, nothing could match it to the message on screen.Changes
MessengerController::send()returns the stored id (plus the saved attachment path), and the client swaps the temp id for it.checkNewMessages()was in the routes and the controller but no caller anywhere. It now also reportsseen_message_idsfor the open conversation, scoped tofrom_id = auth idand bounded to the 50 most recent.is_read, which is what turns the tick into the existing blueCheckCheck.Verification
tests/Feature/MessengerReadReceiptTest.php: send returns the stored id, the sender sees the receipt only after the recipient opens the chat, and another conversation's read state does not leak in. All 3 fail without the controller change.npm run buildandtsc --noEmitclean.seen_message_ids: []before the recipient opens the chat and[1]after.Not covered
A distinct grey "delivered" tick, separate from "sent", needs a delivery ack from the recipient's client that does not exist today. This restores read status, which is what was stuck.
🤖 Generated with Claude Code