Skip to content

Fix read receipts for open chats and one sided message deletes - #95

Merged
HafizMMoaz merged 1 commit into
mainfrom
fix/messenger-read-receipts-and-delete
Jul 27, 2026
Merged

Fix read receipts for open chats and one sided message deletes#95
HafizMMoaz merged 1 commit into
mainfrom
fix/messenger-read-receipts-and-delete

Conversation

@HafizMMoaz

@HafizMMoaz HafizMMoaz commented Jul 27, 2026

Copy link
Copy Markdown
Member

Fixes #90

Follow up to #92, which @Mueeza-517 reports did not fix #90, plus the second bug raised in the same comment.

Read receipts, the half #92 missed

#92 got the reporting right. The sender polls, and the server tells it which of its messages have been read. What it did not fix is that nothing marks a message seen while the conversation is already open.

seen is only ever flipped in two places: index() when the page loads with a user_id, and getMessages() on page 1. A message that arrives over Pusher is appended to an open chat that never refetches, so it is displayed to the recipient and stays seen = 0 forever.

That is the rest of "sometimes it works, sometimes it does not". With both chats open the tick never turns blue, and the reload that fixes it has to be the recipient's, not the sender's. Reloading the sender's page only refetches the same unread state.

checkNewMessages() now marks the open conversation seen, which is the same thing a page load does, on a request that was already being made. On the client the poll fires immediately instead of after a full 10s interval, and skips while document.visibilityState !== 'visible', so a chat left open in a background tab does not report messages nobody looked at as read.

Messages coming back after the sender deletes them

when a sender deletes his messages from his own side, the messages still appear after refreshing the page. The messages only get deleted when the receiver also deletes them from their side.

Operator precedence. The conversation query chained where(A)->orWhere(B)->where(C), which renders as:

where (from_id = ? and to_id = ?) or (from_id = ? and to_id = ?) and ((from_id = ? and deleted_by_sender = ?) or (to_id = ? and deleted_by_receiver = ?))

and binds tighter than or, so this is A or (B and C). The delete filter never applied to the sender's own messages. Deleting for yourself did write deleted_by_sender, it just had no effect on what you were shown. It only appeared to work once the recipient deleted too, because that path hard deletes the row.

Grouping the two halves of the conversation into one closure fixes it. Same bug and same fix in getMessages() and in the Inertia index().

Verification

tests/Feature/MessengerDeleteAndSeenTest.php, 7 tests. 3 fail on main and pass here:

  • a message the sender deleted stays hidden from the sender
  • polling marks messages seen for the open conversation
  • polling only marks the open conversation seen

The other 4 pin behaviour that must not change: deleting for yourself leaves the message on the other side, the recipient's own delete still hides it, no third party message leaks into a conversation, and a poll with no conversation open marks nothing seen.

Full suite: 126 tests, 13 failures, all pre-existing (Auth\PasswordResetTest, PasswordUpdateTest, RegistrationTest, ProfileTest, UserCreationTest, which are Laravel starter stubs) and the same 13 as before this branch. npm run build and tsc --noEmit clean.

Worth checking separately

@Mueeza-517 tested on zerp.pk. The comment landed about 5 hours after #92 merged, so it is worth confirming the deployment there actually had #92 on it. The gaps above are real either way, but it changes what the screenshots were showing.

Not addressed here: a distinct grey "delivered" tick still needs a delivery ack from the recipient's client that does not exist, and none of this covers the case where Pusher is not configured at all, since nothing else fetches new messages.

Follow up to #92, which the reporter says did not 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.

Refs #90

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@HafizMMoaz
HafizMMoaz marked this pull request as ready for review July 27, 2026 11:55
@HafizMMoaz
HafizMMoaz requested a review from a team as a code owner July 27, 2026 11:55
@HafizMMoaz
HafizMMoaz merged commit 4bd1a9e into main Jul 27, 2026
1 check passed
@HafizMMoaz
HafizMMoaz deleted the fix/messenger-read-receipts-and-delete branch July 27, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Messenger Read Receipts Not Updating

1 participant