Skip to content

chore: migrate sendMessage + getReadReceipts callers to REST - #40675

Merged
ggazzo merged 16 commits into
developfrom
chore/ddp-sendmsg-readreceipts-fix
Jul 28, 2026
Merged

chore: migrate sendMessage + getReadReceipts callers to REST#40675
ggazzo merged 16 commits into
developfrom
chore/ddp-sendmsg-readreceipts-fix

Conversation

@ggazzo

@ggazzo ggazzo commented May 25, 2026

Copy link
Copy Markdown
Member

Summary

The two DDP methods that were reverted from #40659 because their client plumbing depends on DDP-specific semantics. This PR contains the deeper refactor each needed.

sendMessage

  • sdk.call('sendMessage', message, previewUrls)sdk.rest.post('/v1/chat.sendMessage', { message, previewUrls }).
  • Primary send flow (apps/meteor/client/lib/chats/flows/sendMessage.ts) feeds the server-rendered { message } back into Messages.state via mapMessageFromApi, replacing the optimistic temp record in the same tick the REST call resolves. Reproduces the Minimongo replication the DDP method triggered.
  • The seven fire-and-forget callsites are straight URL swaps:
    • apps/meteor/client/hooks/notification/useNotification.ts (desktop notification reply)
    • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
    • 5 asciiart slashcommands

getReadReceipts

  • useMethod('getReadReceipts')useEndpoint('GET', '/v1/chat.getMessageReadReceipts') in ReadReceiptsModal.
  • Dialog now takes a rid prop and subscribes to notify-room/<rid>/messagesRead; on event, invalidates the ['read-receipts', messageId] react-query so the dialog refetches when new receipts land.
  • mapReadReceiptFromApi helper revives the Date fields the REST endpoint serializes as strings.

Server-side race fix

ReadReceipt.markMessagesAsRead, markMessageAsReadBySender and storeThreadMessagesReadReceipts no longer fire-and-forget the inner storeReadReceipts(...) call — they await it. Closes the read-after-write race that the omnichannel-livechat-read-receipts e2e exposed.

Test plan

  • Quote a message with attachment in a channel; confirm the blockquote appears in the sent message and the composer's quote preview unmounts.
  • Same flow inside a thread.
  • Edit a message after sending.
  • Run the asciiart slash commands.
  • Reply from a desktop notification.
  • Livechat: visitor sends a message; agent opens the chat; agent inspects "Read receipts" — expects two entries (visitor + agent).
  • CI: `omnichannel-livechat-read-receipts.spec.ts:53` passes.
  • CI: `quote-attachment.spec.ts:29` passes.

Task: ARCH-2165

Task: [ARCH-2266]

Summary by CodeRabbit

  • Improvements

    • Improved message sending reliability across chats, notifications, game invites, and slash commands.
    • Quoted-message composers now dismiss promptly after sending, including when delivery encounters an error.
    • Session credentials are cleared when certain requests indicate an expired session.
  • Read Receipts

    • Read-receipt details now refresh automatically when messages are read.
    • Improved consistency when marking messages and threads as read.
  • Bug Fixes

    • Improved attachment validation to prevent incorrect message formatting.

@dionisio-bot

dionisio-bot Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot

changeset-bot Bot commented May 25, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8b884b3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 335966e0-d609-4885-ab99-eb3410ee18db

📥 Commits

Reviewing files that changed from the base of the PR and between e0d5425 and 8b884b3.

📒 Files selected for processing (2)
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: Hacktron Security Check
  • GitHub Check: CodeQL-Build

Walkthrough

Client message sends migrate from Meteor SDK calls to REST endpoints. REST middleware clears matching credentials on unauthorized mutations. Read receipts gain awaited persistence and live REST/stream refresh. Legacy methods emit deprecation logs, and attachment validation tightens plain-file schema matching.

Changes

REST messaging migration

Layer / File(s) Summary
Client message transport migration
apps/meteor/app/slashcommand-asciiarts/client/*, apps/meteor/client/hooks/notification/useNotification.ts, apps/meteor/client/apps/gameCenter/..., apps/meteor/client/lib/chats/flows/sendMessage.ts
Slash commands, notification replies, game invitations, and regular chat sends now use /v1/chat.sendMessage; quoted-message dismissal occurs before processing.
REST mutation credential handling
apps/meteor/app/utils/client/lib/RestApiClient.ts
Unauthorized POST, PUT, and DELETE responses clear stored credentials when the request token still matches the stored token.

Read receipt synchronization

Layer / File(s) Summary
Awaited receipt persistence
apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
Room, sender, and thread read-receipt flows await receipt storage.
REST receipt fetching and live refresh
apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx, apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
The modal receives a room id, fetches receipts through REST, maps API data, and invalidates the query on room stream notifications.

API transition instrumentation

Layer / File(s) Summary
Legacy method deprecation logging
apps/meteor/ee/server/meteor-methods/getReadReceipts.ts, apps/meteor/server/meteor-methods/messages/*
Legacy read-receipt, thread-message, and send-message methods log their replacement REST routes.
Attachment schema matching
apps/meteor/server/api/validation/ajv.ts
Plain-file attachment schemas without media URL properties now disallow additional properties.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReadReceiptsModal
  participant RESTAPI
  participant RoomStream
  participant QueryClient
  ReadReceiptsModal->>RESTAPI: GET chat.getMessageReadReceipts
  RESTAPI-->>ReadReceiptsModal: return mapped receipts
  RoomStream-->>ReadReceiptsModal: messagesRead notification
  ReadReceiptsModal->>QueryClient: invalidate read-receipts query
  QueryClient->>RESTAPI: refetch receipts
Loading

Possibly related PRs

Suggested labels: type: chore

Suggested reviewers: sampaiodiego, tassoevan

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: migrating sendMessage and getReadReceipts callers to REST.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (2)
  • ARCH-2165: Request failed with status code 401
  • ARCH-2266: Request failed with status code 401

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.72%. Comparing base (d281351) to head (8b884b3).
⚠️ Report is 5 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #40675      +/-   ##
===========================================
+ Coverage    68.66%   68.72%   +0.05%     
===========================================
  Files         4139     4151      +12     
  Lines       159268   159463     +195     
  Branches     27928    27996      +68     
===========================================
+ Hits        109357   109586     +229     
+ Misses       44741    44693      -48     
- Partials      5170     5184      +14     
Flag Coverage Δ
unit 70.68% <71.73%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo
ggazzo force-pushed the chore/ddp-sendmsg-readreceipts-fix branch 2 times, most recently from 274ea6b to a8c6aa9 Compare May 27, 2026 15:02
@ggazzo ggazzo added this to the 8.6.0 milestone May 28, 2026
@ggazzo

ggazzo commented May 28, 2026

Copy link
Copy Markdown
Member Author

/jira ARCH-2156

@ggazzo

ggazzo commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

/jira ARCH-2166

@ggazzo ggazzo modified the milestones: 8.7.0, 8.8.0 Jul 22, 2026
ggazzo and others added 8 commits July 27, 2026 10:25
Two methods that resisted the URL-swap pass in the wider DDP -> REST
migration. This PR contains the deeper refactor each needed.

sendMessage
  Replace sdk.call('sendMessage', message, previewUrls) with
  sdk.rest.post('/v1/chat.sendMessage', { message, previewUrls }).
  In the primary send flow (apps/meteor/client/lib/chats/flows/sendMessage.ts)
  the response's server-rendered { message } is fed back into
  Messages.state via mapMessageFromApi, replacing the optimistic temp
  record in the same tick the REST call resolves. That reproduces the
  Minimongo replication the DDP method triggered — composer quote
  previews unmount, attachment renderers see attachments[] and urls[],
  message _updatedAt advances — all without waiting for the
  room-messages stream event to arrive separately.

  The seven fire-and-forget callsites do not run the optimistic
  reconcile and are straight URL swaps:
    - apps/meteor/client/hooks/notification/useNotification.ts
    - apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
    - apps/meteor/app/slashcommand-asciiarts/client/{lenny,tableflip,unflip,gimme,shrug}.ts

getReadReceipts
  Replace useMethod('getReadReceipts') with
  useEndpoint('GET', '/v1/chat.getMessageReadReceipts') in
  ReadReceiptsModal. Add rid prop and subscribe to
  notify-room/<rid>/messagesRead; on event, invalidate the
  ['read-receipts', messageId] query so the dialog re-fetches when new
  receipts land. mapReadReceiptFromApi revives the Date fields the
  REST endpoint serializes as strings.

Server-side: ReadReceipt.markMessagesAsRead /
ReadReceipt.markMessageAsReadBySender /
ReadReceipt.storeThreadMessagesReadReceipts no longer fire-and-forget
the storeReadReceipts insertion — they await it. Closes the
read-after-write race the omnichannel-livechat-read-receipts e2e test
exposed: a fast REST GET could observe a partial set of receipts
because the visitor's self-receipt insert was still in flight when the
test opened the Read-Receipts dialog.
The optimistic record runs through onClientMessageReceived (which the
E2EE hook subscribes to) so the ciphertext is rendered as plaintext.
The post-REST replacement was bypassing that hook and storing the
server-returned ciphertext directly, leaving encrypted rooms showing
the raw cipher (or the lastUserMessageBody locator timing out in the
e2ee-encrypted-channels e2e suite).

Pipe the server response through the same hook before the state
replacement. For non-encrypted rooms the hook is a no-op
(shouldConvertReceivedMessages returns false), so the change only
affects E2EE flows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The post-REST state.update was replacing the optimistic record with
the server's response unconditionally. When the messages stream
delivered an update first (read-receipt-driven `unread: false`, async
URL/quote attachments arriving via AfterSave hooks, E2EE decrypt
results) the REST resolution would overwrite it with the stale
snapshot the server captured before those side effects landed —
breaking quote rendering in non-encrypted rooms and the 'Message
viewed' status icon used by read-receipts e2e tests.

Restore the original predicate from the DDP-era code: drop the
optimistic `temp` flag only if no other update has touched the record.
The stream (Minimongo replication) keeps doing the heavy lifting for
async-derived fields. The composer's `dismissAllQuotedMessages()`
call already runs from the outer `sendMessage` and doesn't depend on
the state.update body, so quote preview unmount keeps working.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The composer quote preview stayed mounted when /v1/chat.sendMessage rejected
(e.g. response validation under TEST_MODE) even though the message was already
broadcast over the stream, leaving a duplicate blockquote. Dismiss the quoted
messages at the optimistic phase — they are already baked into the outgoing
message — so composer state is decoupled from the request outcome, matching the
optimistic composer.clear() already done for text.
…hema

The typia-generated FileAttachmentProps union includes a catch-all base branch
that only requires `type: 'file'`, so it also matched image/video/audio payloads.
A single media file therefore satisfied both its specific oneOf branch and the
base branch, violating oneOf's "exactly one" rule and failing response validation
(TEST_MODE) for any message carrying a file or quoted-file attachment — e.g.
/v1/chat.sendMessage when quoting a message with an attachment.

Lock the base file branch with additionalProperties:false (same approach already
used for MessageAttachmentDefault) so only genuine plain-file attachments match it.
Verified against the failing quote-attachment payload: invalid -> valid.
…ct to login

DDP-routed calls cleared stored credentials on auth failure via ddpOverREST; direct
sdk.rest calls (e.g. the migrated chat.sendMessage flow) bypassed it, leaving an
expired session wedged instead of redirecting to login. Clear credentials globally in
the REST client middleware on 401 (unauthenticated) only — never 403 (permission).
@ggazzo
ggazzo force-pushed the chore/ddp-sendmsg-readreceipts-fix branch from 82391f6 to 4793399 Compare July 27, 2026 13:26
@ggazzo ggazzo changed the title feat: migrate sendMessage + getReadReceipts callers to REST chore: migrate sendMessage + getReadReceipts callers to REST Jul 27, 2026
ggazzo added 2 commits July 27, 2026 11:30
Its only client caller moved to GET /v1/chat.getThreadMessages in #40998,
but the method was left without a deprecation log, so external consumers
get no warning before 9.0.0 removes the registration.
…t token

The 401 handler cleared the stored credentials unconditionally, so a
transient unauthenticated 401 during app boot logged the user straight
back out. OmnichannelProvider's initializeLivechatInquiryStream calls
GET /v1/livechat/config/routing (authRequired, no permission gate) while
the session is still being established, and custom-sounds.list does the
same; both 401 with no token attached. The wipe then dropped the session
that had just been created and the router fell through to the login page,
so #main-content never rendered — every omnichannel e2e spec in the shard
timed out in beforeEach and took the worker down with it.

Capture the stored token before the request and clear only when the token
is unchanged and was present at send time. An unauthenticated boot call
(no token) and a request still in flight across a re-login (different
token) say nothing about the credentials currently stored; an actually
expired or revoked token still matches and still logs out, which is what
the original fix was for.
@ggazzo
ggazzo force-pushed the chore/ddp-sendmsg-readreceipts-fix branch from 4793399 to da46336 Compare July 27, 2026 14:33
ggazzo added 2 commits July 27, 2026 12:14
…ntials

A 401 from an arbitrary endpoint does not prove the stored token is dead. The
trace from the failing omnichannel e2e shard shows the same token answering 200
on 108 requests and 401 on four bursts of GET /v1/livechat/config/routing, whose
body is the auth middleware's "You must be logged in to do this." — the per-route
rate limiter throttles the auth check and reports it as unauthenticated, exactly
the failure mode already documented in client/startup/startup.ts. Clearing on
that 401 dropped a live session, the router fell through to the login page,
#main-content never rendered, and every omnichannel spec in the shard timed out
in beforeEach.

Route the decision through /v1/me instead: a 401 elsewhere triggers one
single-flight re-check, and only a 401 from /v1/me itself clears the credentials.
The expired-token redirect this was added for still works — that is the same
endpoint synchronizeUserData already relies on in #40870 — while a throttled or
boot-time 401 no longer costs the user their session. The wipe is irreversible:
the login token lives only in localStorage, so there is nothing to resume from
once it is gone.
Reverts the RestApiClient middleware back to develop. Clearing the stored
credentials on any REST 401 logs out live sessions: the auth middleware answers
401 whenever (X-User-Id, hashed X-Auth-Token) has no match, which also covers a
call issued before the session is established and a token that vanished
server-side, and from 9.0.0 ApiClass maps a thrown `error-unauthorized` — the
code this codebase uses for permission denials — to 401 as well. None of those
mean the stored token is dead, and the wipe is irreversible because the token
only lives in localStorage.

The omnichannel e2e shards that fail on this branch and pass on develop are the
symptom: an auxiliary context boots, takes a 401, loses its credentials, lands on
the login page, and #main-content never renders. Gating the wipe behind a /v1/me
re-check narrowed it but did not close it, because /v1/me can take the same 401.

Expired sessions already redirect to login through synchronizeUserData in
client/startup/startup.ts (#40870), which asks /v1/me and keeps the token-stable
guard. If a gap remains outside that path it needs its own change, after the
9.0.0 error-unauthorized mapping is fixed so that a 401 means one thing again.
…t reads

Reinstates the expired-session redirect for the migrated send path, narrowed to
the case that actually carries the signal.

session-expiration-redirect.spec.ts:87 deletes the login tokens server-side and
clicks send: with sendMessage now on POST /v1/chat.sendMessage, nothing cleared
the stored credentials, so the router never fell through to LoginPage. The
sibling test at :49 kept passing because message search is still a DDP method and
ddpOverREST clears there — the gap was only on the path this PR migrated.

Clearing on every REST 401 is what broke the omnichannel e2e shard: an auxiliary
context boots, OmnichannelProvider's GET livechat/config/routing answers 401 with
the same "You must be logged in to do this." before the session is up, the
credentials are wiped, and #main-content never renders. The message body does not
separate the two cases, but the request does: ddpOverREST only ever cleared for
method calls, never for background fetches. Restricting the wipe to POST/PUT/
DELETE reproduces that, so a session that dies while idle is noticed on the
user's next write — the pre-migration behaviour.

Keeps the token-stable guard so a request in flight across a re-login cannot
evict the session it does not belong to, and leaves 403 alone.
@ggazzo
ggazzo force-pushed the chore/ddp-sendmsg-readreceipts-fix branch from e02ffb7 to e0d5425 Compare July 27, 2026 17:42
@ggazzo
ggazzo marked this pull request as ready for review July 27, 2026 18:28
@ggazzo
ggazzo requested review from a team as code owners July 27, 2026 18:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
apps/meteor/server/api/validation/ajv.ts (1)

13-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid implementation comments in this file.

Remove the added explanatory comment block. As per coding guidelines, “Avoid code comments in the implementation.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/server/api/validation/ajv.ts` around lines 13 - 20, Remove the
added explanatory comment block above the plain-file attachment validation in
ajv.ts, leaving the implementation behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/meteor/app/utils/client/lib/RestApiClient.ts`:
- Line 50: Update the isMutation helper to classify PATCH alongside POST, PUT,
and DELETE so PATCH requests use the same mutation credential handling,
including clearing stale credentials after a 401.

In `@apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx`:
- Around line 37-43: Update the invite-message request in the surrounding
game-center flow to await sdk.rest.post instead of detaching it with void, so
failures propagate to the existing try/catch error path and prevent completing
the group creation without its invitation message.

In `@apps/meteor/client/lib/chats/flows/sendMessage.ts`:
- Around line 53-56: Remove the added implementation rationale comments at
apps/meteor/client/lib/chats/flows/sendMessage.ts lines 53-56 and 114-119, and
at apps/meteor/app/utils/client/lib/RestApiClient.ts lines 41-49 and 60-62.
Leave the surrounding implementations unchanged.

In
`@apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx`:
- Around line 26-40: The queryKey array in ReadReceiptsModal is recreated on
every render, causing unnecessary subscription effect cleanup and
re-registration. Memoize queryKey based on messageId with useMemo, then continue
using the memoized key in both useQuery and the subscribeToNotifyRoom effect.

---

Nitpick comments:
In `@apps/meteor/server/api/validation/ajv.ts`:
- Around line 13-20: Remove the added explanatory comment block above the
plain-file attachment validation in ajv.ts, leaving the implementation behavior
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 14a8a0e1-d78c-492f-9a1a-764c29ed2829

📥 Commits

Reviewing files that changed from the base of the PR and between 5ba7bd5 and e0d5425.

📒 Files selected for processing (16)
  • apps/meteor/app/slashcommand-asciiarts/client/gimme.ts
  • apps/meteor/app/slashcommand-asciiarts/client/lenny.ts
  • apps/meteor/app/slashcommand-asciiarts/client/shrug.ts
  • apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts
  • apps/meteor/app/slashcommand-asciiarts/client/unflip.ts
  • apps/meteor/app/utils/client/lib/RestApiClient.ts
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx
  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
  • apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
  • apps/meteor/ee/server/meteor-methods/getReadReceipts.ts
  • apps/meteor/server/api/validation/ajv.ts
  • apps/meteor/server/meteor-methods/messages/getThreadMessages.ts
  • apps/meteor/server/meteor-methods/messages/sendMessage.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Hacktron Security Check
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/app/slashcommand-asciiarts/client/gimme.ts
  • apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts
  • apps/meteor/app/slashcommand-asciiarts/client/lenny.ts
  • apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx
  • apps/meteor/ee/server/meteor-methods/getReadReceipts.ts
  • apps/meteor/app/utils/client/lib/RestApiClient.ts
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/app/slashcommand-asciiarts/client/shrug.ts
  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/app/slashcommand-asciiarts/client/unflip.ts
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
  • apps/meteor/server/meteor-methods/messages/sendMessage.ts
  • apps/meteor/server/meteor-methods/messages/getThreadMessages.ts
  • apps/meteor/server/api/validation/ajv.ts
  • apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
🧠 Learnings (7)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • apps/meteor/app/slashcommand-asciiarts/client/gimme.ts
  • apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts
  • apps/meteor/app/slashcommand-asciiarts/client/lenny.ts
  • apps/meteor/ee/server/meteor-methods/getReadReceipts.ts
  • apps/meteor/app/utils/client/lib/RestApiClient.ts
  • apps/meteor/app/slashcommand-asciiarts/client/shrug.ts
  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/app/slashcommand-asciiarts/client/unflip.ts
  • apps/meteor/server/meteor-methods/messages/sendMessage.ts
  • apps/meteor/server/meteor-methods/messages/getThreadMessages.ts
  • apps/meteor/server/api/validation/ajv.ts
  • apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • apps/meteor/app/slashcommand-asciiarts/client/gimme.ts
  • apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts
  • apps/meteor/app/slashcommand-asciiarts/client/lenny.ts
  • apps/meteor/ee/server/meteor-methods/getReadReceipts.ts
  • apps/meteor/app/utils/client/lib/RestApiClient.ts
  • apps/meteor/app/slashcommand-asciiarts/client/shrug.ts
  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/app/slashcommand-asciiarts/client/unflip.ts
  • apps/meteor/server/meteor-methods/messages/sendMessage.ts
  • apps/meteor/server/meteor-methods/messages/getThreadMessages.ts
  • apps/meteor/server/api/validation/ajv.ts
  • apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.

Applied to files:

  • apps/meteor/app/slashcommand-asciiarts/client/gimme.ts
  • apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts
  • apps/meteor/app/slashcommand-asciiarts/client/lenny.ts
  • apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx
  • apps/meteor/ee/server/meteor-methods/getReadReceipts.ts
  • apps/meteor/app/utils/client/lib/RestApiClient.ts
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/app/slashcommand-asciiarts/client/shrug.ts
  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
  • apps/meteor/app/slashcommand-asciiarts/client/unflip.ts
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
  • apps/meteor/server/meteor-methods/messages/sendMessage.ts
  • apps/meteor/server/meteor-methods/messages/getThreadMessages.ts
  • apps/meteor/server/api/validation/ajv.ts
  • apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts
📚 Learning: 2026-03-27T14:52:56.865Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39892
File: apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx:150-155
Timestamp: 2026-03-27T14:52:56.865Z
Learning: In Rocket.Chat, there are two different `ModalBackdrop` components with different prop APIs. During review, confirm the import source: (1) `rocket.chat/fuselage` `ModalBackdrop` uses `ModalBackdropProps` based on `BoxProps` (so it supports `onClick` and other Box/DOM props) and does not have an `onDismiss` prop; (2) `rocket.chat/ui-client` `ModalBackdrop` uses a narrower props interface like `{ children?: ReactNode; onDismiss?: () => void }` and handles Escape keypress and outside mouse-up, and it does not forward arbitrary DOM props such as `onClick`. Flag mismatched props (e.g., `onDismiss` passed to the fuselage component or `onClick` passed to the ui-client component) and ensure the usage matches the correct component being imported.

Applied to files:

  • apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx
  • apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx
  • apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx
📚 Learning: 2026-02-10T16:32:42.586Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:42.586Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON and Date fields arrive as Date objects; do not manually construct new Date() in stream handlers (for example, in sdk.stream()). Only REST API responses return plain JSON where dates are strings, so implement explicit conversion there if needed. Apply this guidance to all TypeScript files under apps/meteor/client to ensure consistent date handling in DDP streams and REST responses.

Applied to files:

  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
📚 Learning: 2026-05-11T20:30:35.265Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 40480
File: apps/meteor/client/meteor/startup/accounts.ts:59-61
Timestamp: 2026-05-11T20:30:35.265Z
Learning: In Rocket.Chat’s Meteor client code, when calling `dispatchToastMessage` with `{ type: 'error' }`, pass the raw caught error object as `message` without manual normalization. `dispatchToastMessage` is designed to accept `message: unknown` for error toasts, so avoid converting errors to strings (e.g., `String(error)`) or extracting `error.message` before passing them.

Applied to files:

  • apps/meteor/client/hooks/notification/useNotification.ts
  • apps/meteor/client/lib/chats/flows/sendMessage.ts
📚 Learning: 2026-04-14T21:10:31.855Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 36292
File: apps/meteor/client/hooks/useHasValidLocationHash.ts:7-12
Timestamp: 2026-04-14T21:10:31.855Z
Learning: When reviewing files in apps/meteor/client/hooks/, do not treat JSDoc-style comments on React hooks (especially exported hooks) as a violation of any “avoid code comments in implementation” guideline. It’s acceptable to use JSDoc to document the public API of exported hooks (e.g., parameter/return types, intended usage), as long as it documents behavior/contracts rather than adding narrative implementation comments.

Applied to files:

  • apps/meteor/client/hooks/notification/useNotification.ts
🔇 Additional comments (13)
apps/meteor/ee/server/meteor-methods/getReadReceipts.ts (1)

9-9: LGTM!

Also applies to: 41-42

apps/meteor/server/meteor-methods/messages/getThreadMessages.ts (1)

8-8: LGTM!

Also applies to: 23-24

apps/meteor/server/meteor-methods/messages/sendMessage.ts (1)

16-16: LGTM!

Also applies to: 140-141

apps/meteor/app/slashcommand-asciiarts/client/gimme.ts (1)

11-11: LGTM!

apps/meteor/app/slashcommand-asciiarts/client/lenny.ts (1)

12-12: LGTM!

apps/meteor/app/slashcommand-asciiarts/client/shrug.ts (1)

14-14: LGTM!

apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts (1)

14-14: LGTM!

apps/meteor/app/slashcommand-asciiarts/client/unflip.ts (1)

14-14: LGTM!

apps/meteor/client/hooks/notification/useNotification.ts (1)

54-60: LGTM!

apps/meteor/client/lib/chats/flows/sendMessage.ts (1)

51-51: LGTM!

apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.ts (1)

52-58: LGTM!

Also applies to: 83-89, 104-110

apps/meteor/client/components/message/toolbar/useReadReceiptsDetailsAction.tsx (1)

27-27: LGTM!

apps/meteor/server/api/validation/ajv.ts (1)

21-32: 🎯 Functional Correctness

Scope this schema patch to the actual attachment branch.

This loop only checks top-level schemas and uses a broad shape heuristic. If typia nests the union branch under oneOf/allOf, the ambiguity remains; if unrelated schemas have the same type: 'file' shape, they are incorrectly closed. Target the specific generated branch or traverse the union structure, and add a regression test for plain, image, video, and audio attachments.

* — ddpOverREST only clears credentials for method calls, never for background fetches — so a
* session that dies while idle is noticed on the user's next write, exactly as before.
*/
const isMutation = (method: string): boolean => method === 'POST' || method === 'PUT' || method === 'DELETE';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Include PATCH in mutation credential handling.

A 401 from a PATCH mutation will not clear stale credentials, unlike the other write methods.

-const isMutation = (method: string): boolean => method === 'POST' || method === 'PUT' || method === 'DELETE';
+const isMutation = (method: string): boolean => method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isMutation = (method: string): boolean => method === 'POST' || method === 'PUT' || method === 'DELETE';
const isMutation = (method: string): boolean => method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/app/utils/client/lib/RestApiClient.ts` at line 50, Update the
isMutation helper to classify PATCH alongside POST, PUT, and DELETE so PATCH
requests use the same mutation credential handling, including clearing stale
credentials after a 401.

Comment thread apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx Outdated
Comment on lines +53 to +56
// Clear the optimistic `temp` flag only if the messages stream hasn't already
// replaced the record. Overwriting with the server response can clobber stream
// updates that arrive first — e.g. read-receipt-driven `unread: false`, async
// URL/quote attachments, or E2EE decrypt — leading to stale UI state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the added implementation comments.

  • apps/meteor/client/lib/chats/flows/sendMessage.ts#L53-L56: remove the optimistic-state rationale comment.
  • apps/meteor/client/lib/chats/flows/sendMessage.ts#L114-L119: remove the quote-dismissal rationale comment.
  • apps/meteor/app/utils/client/lib/RestApiClient.ts#L41-L49: remove the mutation-auth rationale comment.
  • apps/meteor/app/utils/client/lib/RestApiClient.ts#L60-L62: remove the inline credential-clearing rationale comment.

As per coding guidelines, “Avoid code comments in the implementation.”

📍 Affects 2 files
  • apps/meteor/client/lib/chats/flows/sendMessage.ts#L53-L56 (this comment)
  • apps/meteor/client/lib/chats/flows/sendMessage.ts#L114-L119
  • apps/meteor/app/utils/client/lib/RestApiClient.ts#L41-L49
  • apps/meteor/app/utils/client/lib/RestApiClient.ts#L60-L62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/client/lib/chats/flows/sendMessage.ts` around lines 53 - 56,
Remove the added implementation rationale comments at
apps/meteor/client/lib/chats/flows/sendMessage.ts lines 53-56 and 114-119, and
at apps/meteor/app/utils/client/lib/RestApiClient.ts lines 41-49 and 60-62.
Leave the surrounding implementations unchanged.

Source: Coding guidelines

Comment thread apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 16 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx Outdated
Comment thread apps/meteor/client/views/room/modals/ReadReceiptsModal/ReadReceiptsModal.tsx Outdated
…ead-receipts subscription

Two defects both review bots flagged.

The play-together message was detached with `void`, so a rejection escaped the
surrounding try/catch as an unhandled rejection and the freshly created group was
left without its message. It was awaited before the REST migration; await it again.

ReadReceiptsModal built its query key as a fresh array literal on every render and
passed it as an effect dependency, so the notify-room subscription tore down and
re-registered on every render — messagesRead events landing in that window were
lost. Build the key from a helper at each use and depend on messageId, matching
what useThreadMessagesQuery already does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants