Area: Messaging & chat (audit p4) · Surface: GET /messages/{id} (MessageController@showConversation) + conversation.php client · Dimension: competitor-gap · Severity: major
Circle, Intercom, and every modern messenger deliver DMs in real time over websockets/SSE with sub-second latency and no redundant payload. Our DM thread instead refetches the whole rendered page every 5s, re-marks everything read on each poll, and diffs by counting `.msg-bubble-row` elements — so new messages lag up to 5s, typing/read state is stale, and bandwidth is wasted re-parsing the full thread. We already proved the SSE pattern works for chat rooms; DMs should use the same transport.
Evidence
templates/messages/conversation.php:702-722 — `setInterval(function(){ ... fetch('/messages/'+convId,{headers:{'Accept':'text/html'}}).then(r=>r.text()).then(html=>{ var doc = parser.parseFromString(html,'text/html'); ... thread.innerHTML = newThread.innerHTML; ...}); }, 5000)`. The same codebase already has real SSE for chat rooms (ChatRoomController::sse at src/Controllers/ChatRoomController.php:389 routed at /sse/rooms/{id}, routes.php:1002) but NO equivalent SSE route exists for DMs (grep for '/sse/conv|/sse/dm|/sse/messages' returns nothing). DMs re-download and re-parse the entire conversation HTML every 5 seconds.
Suggested fix. Add a /sse/messages/{conversationId} stream mirroring ChatRoomController::sse (gone-away catch, heartbeat, max-duration, isolated FPM pool), emitting new-message + typing + read events. Fall back to the existing poll only when SSE fails, and make the poll a JSON since-id delta (like /api/rooms/{slug}/messages) rather than a full-page refetch.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus