fix(social): realtime correctness — duplicate bubbles, unscoped reaction reloads, has_more off-by-one#205
Conversation
|
Warning Review limit reached
More reviews will be available in 51 minutes and 58 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
frontend | a3d05c1 | Commit Preview URL Branch Preview URL |
Jun 12 2026, 05:01 AM |
Fetch limit+1 and set has_more = len(rows) > limit, dropping the probe row before returning. The previous len(rows) == limit was true at exact page boundaries, surfacing a phantom "load earlier" that fetched an empty page. Update pagination tests to the corrected contract.
Message dedup (#7): reconcile the optimistic temp row against the decrypted POST response (matched by real id) and skip the sender's own realtime INSERT/UPDATE echoes, which carry ciphertext and never matched the plaintext temp row. This stops the sender seeing each message twice (one readable, one ciphertext) and preserves reply_to/reactions instead of hardcoding them. handleImage appends from the POST response so the uploader still sees their own image despite the self-echo skip. Reaction scope (#31): the room_reactions INSERT/DELETE subscriptions had no room scope, so a reaction in any room triggered a reload of the open room. Only reload when the affected message_id is currently loaded.
c606f1a to
a3d05c1
Compare
What
Fixes the three realtime correctness bugs in the Social study-rooms chat tracked in #131:
has_moreoff-by-one — a phantom "load earlier" appeared at exact page boundaries and fetched an empty page.Why
These are the concrete root causes behind the Social page misbehaving (#85):
send()appended a plaintext temp row and the realtime dedup kept rows wherem.text !== row.text. Plaintext never equals ciphertext, so the temp row was never removed → duplicate bubble. The realtime row also hardcodedreply_to: null/reactions: []and the UPDATE handler overwrote edited plaintext with ciphertext.room_reactionsINSERT/DELETE subscriptions had no room scope (the table has noroom_idcolumn), so every reaction anywhere triggeredload()for the open room.has_more = len(rows) == limitis true whenever a page is exactly full, even when no older messages exist.How
Frontend (
Social.tsx)reply_to/reactionsinstead of hardcoding.handleImageappends from the POST response so the uploader still sees their own image despite the self-echo skip.load()when the affectedmessage_idis currently loaded. DELETE payloads omitmessage_idwithoutREPLICA IDENTITY FULL, so that case falls back to a reload.Backend (
routes/social.py)limit + 1, sethas_more = len(rows) > limit, and drop the probe row before reversing. Pagination tests updated to the corrected contract.How verified
python -m pytest tests/test_social_messages.py -q→ 8 passed (boundary cases: exact-page →has_more=False, probe-row present →has_more=Truewith truncation, limit clamp/floor account for the +1).npx tsc --noEmit→ clean;npx vitest run→ 37 passed (5 files).Caveats
room_reactionsDELETE realtime path can't bemessage_id-scoped withoutREPLICA IDENTITY FULLon the table (noroom_idcolumn exists); it falls back to a reload only when scoping is impossible. Per the issue, the fix for Feature/chat system #31 is intentionally client-side.Closes #131