git commit -m "feat(chat): add end-to-end message reactions and action#13
git commit -m "feat(chat): add end-to-end message reactions and action#13BuckyMcYolo merged 1 commit intomainfrom
Conversation
bar polish" \ -m "Add message_reaction persistence, API/realtime reaction payloads, and reaction toggle handling across backend services." \ -m "Update web chat to render and optimistically update reactions, extract reusable message send/reaction hooks, and sync with websocket events." \ -m "Polish chat UI: stabilize action-bar overlay behavior, add owner-only Edit/Delete menu entries, improve reaction pill hover/cursor states, and increase date divider spacing."
📝 WalkthroughWalkthroughThis PR introduces message reaction functionality by adding database schema for storing reactions, extending API queries to fetch reaction data with current user context, implementing realtime socket events for toggling reactions with optimistic UI updates, and updating React components and hooks to display and manage message reactions with real-time synchronization. Changes
Sequence DiagramsequenceDiagram
actor User
participant Client as Client (Browser)
participant RealtimeServer as Realtime Server
participant Database
participant OtherClients as Other Clients in Channel
User->>Client: Click reaction emoji
Client->>Client: Apply optimistic update to cache
Client->>Client: Render updated reaction count<br/>(reactedByCurrentUser = true)
Client->>RealtimeServer: Emit message:reaction:toggle<br/>(messageId, emoji)
RealtimeServer->>Database: Check channel access<br/>Fetch message & existing reaction
alt Reaction exists
Database-->>RealtimeServer: Return existing reaction
RealtimeServer->>Database: Delete reaction (transactional)
else Reaction does not exist
Database-->>RealtimeServer: No reaction found
RealtimeServer->>Database: Insert new reaction (transactional)
end
Database-->>RealtimeServer: Confirm toggle
RealtimeServer->>RealtimeServer: Calculate updated reaction count
RealtimeServer->>RealtimeServer: Build RealtimeMessageReactionUpdated update
RealtimeServer-->>Client: Ack with { ok: true, update }
Client->>Client: Apply server update to cache<br/>(sync optimistic with server)
RealtimeServer->>OtherClients: Broadcast message:reaction:updated<br/>(emoji, count, actorUserId, reactedByActor)
OtherClients->>OtherClients: Receive update & apply to cache<br/>Render updated reaction
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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 |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/api/src/lib/helpers/openapi/message-schemas.ts`:
- Line 22: The new required "reactions" field in message-schemas.ts is
incompatible with existing message payloads; change the reactions schema (the
`reactions` property that currently references `messageReactionSchema`) to be
optional (or provide a safe default like an empty array) so responses from
apps/api/src/lib/queries/messages.ts and the handlers in
apps/api/src/routes/v1/channels/handlers.ts and
apps/api/src/routes/v1/dms/handlers.ts remain valid until you update the message
assembly to include reactions.
In `@apps/realtime/src/services/messages.ts`:
- Around line 168-178: The reaction count query (reactionCount) is executed
outside the transaction, allowing race conditions vs. reactedByActor updates;
move the db.select(...) that computes the count on schema.messageReaction
(currently assigned to reactionCount) into the same transaction scope that
updates/reads reactedByActor so it uses the transaction handle (trx) instead of
the global db connection and runs before committing, ensuring the broadcasted
count is consistent and atomic with the transaction's updates.
In `@apps/web/src/components/chat/emoji-reaction-picker.tsx`:
- Around line 32-36: The handleSelect function closes the picker locally but
doesn't notify the parent—update handleSelect (the handler that calls onSelect,
setOpen and setShowFullPicker) to also invoke onOpenChange(false) (guarded by
optional chaining or a null check) so parent components receive the closed state
when an emoji is selected; ensure this call mirrors the existing close logic
used elsewhere to keep overlay state in sync.
In `@apps/web/src/hooks/use-message-reactions.ts`:
- Around line 89-92: The failure rollback currently calls
toggleReactionLocal(messageId, emoji) inside the result.ok false branch which is
concurrency-unsafe; instead, on failure invalidate or refetch the authoritative
message data so the client state is reconciled with the server. Replace the
inverse local toggle in the error path of the send/ack block (where result.ok is
checked) with a call to the message data resync mechanism used by this hook
(e.g., trigger the existing refetch/invalidate function for the message or emit
a request to reload the message by messageId) so authoritative state is applied
rather than a second local toggle.
In `@apps/web/src/hooks/use-message-sending.ts`:
- Around line 112-130: When emitting with socket.emit("message:send", {
channelId, content, nonce }, ...) add a timeout fallback so optimistic messages
and pendingNonces don't get stuck: start a setTimeout when sending (store timers
keyed by nonce, e.g., pendingTimers.current[nonce] or similar), and on timeout
remove the nonce from pendingNonces.current and updateMessagesInCache to remove
or mark the optimistic message (id === nonce); also clear the timeout when the
callback succeeds or fails (both branches inside the socket callback must clear
the timer) and ensure the timeout is cleared on any early returns to avoid
leaks; use realtimeMessageToMessage only when callback returns result.message.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9596507e-593b-4cc9-9f95-47467b221d87
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
apps/api/src/lib/helpers/openapi/message-schemas.tsapps/api/src/lib/queries/messages.tsapps/api/src/routes/v1/channels/handlers.tsapps/api/src/routes/v1/dms/handlers.tsapps/realtime/src/index.tsapps/realtime/src/services/messages.tsapps/web/package.jsonapps/web/src/components/chat/date-divider.tsxapps/web/src/components/chat/emoji-reaction-picker.tsxapps/web/src/components/chat/message-action-bar.tsxapps/web/src/components/chat/message-item.tsxapps/web/src/components/chat/message-list.tsxapps/web/src/hooks/use-message-reactions.tsapps/web/src/hooks/use-message-sending.tsapps/web/src/lib/realtime-adapter.tsapps/web/src/routes/_authenticated/$guildSlug/$channelId.tsxapps/web/src/routes/_authenticated/dms/$dmId.tsxpackages/db/src/schemas/index.tspackages/db/src/schemas/message-reactions.tspackages/realtime-types/src/events.ts
bar polish"
-m "Add message_reaction persistence, API/realtime reaction payloads, and reaction toggle handling across backend services." \ -m "Update web chat to render and optimistically update reactions, extract reusable message send/reaction hooks, and sync with websocket events."
-m "Polish chat UI: stabilize action-bar overlay behavior, add owner-only Edit/Delete menu entries, improve reaction pill hover/cursor states, and increase date divider spacing."