Skip to content

fix: split lastOpen into sync cursor and last-seen separator - #7520

Open
diegolmello wants to merge 9 commits into
developfrom
automatic-sparrow
Open

fix: split lastOpen into sync cursor and last-seen separator#7520
diegolmello wants to merge 9 commits into
developfrom
automatic-sparrow

Conversation

@diegolmello

@diegolmello diegolmello commented Jul 26, 2026

Copy link
Copy Markdown
Member

Proposed changes

subscription.lastOpen served two conflicting meanings: the fetch cursor for chat.syncMessages and the unread-separator position in RoomView — and it was writable by the device clock (updateLastOpen, readMessages' third argument, mark-as-unread), so a skewed device could poison the sync cursor.

This PR narrows lastOpen to a single meaning — the sync cursor: the newest server-stamped message _updatedAt this device has fetched for the room, computed from raw response payloads only (never from WatermelonDB rows, never from Date.now()), advanced monotonically via the new advanceSyncCursor helper.

  • loadMissedMessages rewritten: null-cursor rooms delegate to loadMessagesForRoom (delegate-and-return, rethrow on failure); recursion replaced by a for(;;) drain loop that writes the cursor once per successful run when the UPDATED stream drains; DELETED pages never write; the pre-7.1.0 legacy branch is kept and also writes the cursor. The getMessages router is deleted — RoomView imports loadMissedMessages directly, fixing a live bug where the room type t was silently dropped whenever lastOpen was present.
  • loadMessagesForRoom writes the cursor on the initial tail load only (never on backfill); jump paths (loadSurroundingMessages, loadNextMessages) are documented non-writers.
  • The unread separator now reads ls, surfaced as RoomView.state.lastSeen (init-time snapshot captured before readMessages). Mark-as-unread writes sub.ls = ts optimistically; the server confirms via stream.
  • All device-clock writers removed: updateLastOpen deleted, readMessages third argument deleted.
  • No schema change, no migration: the column stays, every room self-heals on first fetch.
  • CONTEXT.md Message Loading section documents the new semantics.

Issue(s)

Related to #7499 — this fixes a proven field-meaning clash; whether it resolves the reported missing-messages symptom is unverified and intentionally not gated on this PR.

How to test or reproduce

  • Unit: TZ=UTC pnpm test app/lib/methods — new suites cover null-cursor delegation, once-per-drain writes, DELETED never writes, legacy branch, tail-load writes, and both mutation-proofs (monotonic guard, payload-only).
  • Manual: open a room with unread messages → separator shows at ls; mark a message as unread → separator moves optimistically and the server confirms; kill/reopen the app offline→online → missed messages sync via chat.syncMessages from the cursor.

Screenshots

N/A — no visual changes (separator behavior unchanged).

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Concurrency in the message-fetch path is deliberately tolerated (upsert by _id, monotonic cursor — every step converges); no serialization added. Known adjacent issues (server-side DELETED paging defect, normalizeMessage's device-stamp fallback, rooms-list/thread-list watermarks) are documented as out of scope.

Summary by CodeRabbit

  • New Features

    • Improved room message synchronization, including reliable recovery of missed updates and deletions.
    • Added cursor-based syncing for newer servers and maintained compatibility with older server versions.
    • Room activity tracking now uses “Last Seen” status for clearer unread indicators and message separators.
  • Bug Fixes

    • Fixed unread actions and message loading so synchronization progress is based on server-provided timestamps.
    • Prevented sync progress from moving backward during repeated or overlapping updates.

@diegolmello
diegolmello temporarily deployed to approve_e2e_testing July 26, 2026 18:58 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Room synchronization now derives cursors from raw server _updatedAt values, supports paginated and legacy missed-message loading, removes client-side lastOpen writes, and renames RoomView tracking state to lastSeen.

Room synchronization and last-seen state

Layer / File(s) Summary
Cursor extraction and persistence
app/lib/methods/helpers/*, CONTEXT.md, CLAUDE.md
Adds timestamp extraction, monotonic cursor persistence, tests, and documentation defining the server-payload timestamp boundary.
Initial room-load cursor advancement
app/lib/methods/loadMessagesForRoom.ts, app/lib/methods/loadMessagesForRoom.test.ts
Advances the sync cursor from the first payload batch for initial tail loads while excluding backfills and loader-item paths.
Paged missed-message synchronization
app/lib/methods/loadMissedMessages.ts, app/lib/methods/loadMissedMessages.test.ts
Drains UPDATED and DELETED streams for newer servers, retains a legacy path, applies pages, and persists the maximum UPDATED payload timestamp.
Removal of client-side last-open writes
app/lib/methods/readMessages.ts, app/lib/methods/subscriptions/*, app/containers/MessageActions/index.tsx, app/lib/methods/loadNextMessages.ts, app/lib/methods/loadSurroundingMessages.ts
Removes client-side lastOpen updates from reading and unsubscribe flows, updates unread handling, and documents jump-path behavior.
RoomView last-seen integration
app/views/RoomView/*
Replaces lastOpen UI state with lastSeen, invokes missed-message loading directly, updates unread separators, and removes the obsolete getMessages service.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RoomView
  participant loadMissedMessages
  participant RocketChatSDK
  participant updateMessages
  participant advanceSyncCursor
  RoomView->>loadMissedMessages: load missed messages
  loadMissedMessages->>RocketChatSDK: fetch UPDATED and DELETED pages
  RocketChatSDK-->>loadMissedMessages: return payloads and cursors
  loadMissedMessages->>updateMessages: apply message changes
  loadMissedMessages->>advanceSyncCursor: persist maximum UPDATED timestamp
Loading

Possibly related PRs

Suggested labels: type: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: splitting lastOpen into sync-cursor and last-seen responsibilities.
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.

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.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/views/RoomView/index.tsx (1)

1406-1424: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unread separator misses the "same-as-lastSeen" case for the first rendered item.

The !previousItem branch only checks isAfter(lastSeen), while the subsequent-item branch checks isSame(lastSeen) || isAfter(lastSeen). If the first rendered item's ts exactly equals lastSeen (e.g., the mark-as-unread anchor message happens to be the first item in the window), the separator won't show here, but would show if the same message weren't first — an inconsistent edge case.

🐛 Proposed fix
 		if (!previousItem) {
 			dateSeparator = item.ts;
-			showUnreadSeparator = lastSeen ? dayjs(item.ts).isAfter(lastSeen) : false;
+			showUnreadSeparator = lastSeen ? dayjs(item.ts).isSame(lastSeen) || dayjs(item.ts).isAfter(lastSeen) : false;
 		} else {
🤖 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 `@app/views/RoomView/index.tsx` around lines 1406 - 1424, Update the
!previousItem branch in renderItem so showUnreadSeparator treats an item
timestamp equal to lastSeen the same as a timestamp after lastSeen, matching the
existing subsequent-item logic while preserving the false result when lastSeen
is absent.
app/containers/MessageActions/index.tsx (1)

237-256: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Convert message.ts to a real Date before writing sub.ls.

TAnyMessageModel inherits ts?: string | Date, but sub.ls is typed as Date; this ts as Date can silently persist a string into WatermelonDB and break unread-separator time comparisons. Use a shared timestamp coercion helper or parse string values before assigning.

🤖 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 `@app/containers/MessageActions/index.tsx` around lines 237 - 256, Update
handleUnread so the message ts value is converted to a real Date before
assigning it to sub.ls. Reuse the existing shared timestamp coercion helper if
available, otherwise parse string timestamps explicitly, while preserving the
current subscription update flow.
🧹 Nitpick comments (2)
app/lib/methods/helpers/advanceSyncCursor.test.ts (1)

47-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated subscription-mock boilerplate across two test files. Both suites independently re-implement the same database.active.get/write mocking plus a subscription.update jest.fn that mutates lastOpen in place — this shared root cause is worth extracting into one reusable test helper.

  • app/lib/methods/helpers/advanceSyncCursor.test.ts#L47-L64: extract the mockGet/mockWrite/subscription setup into a shared helper (e.g. test/helpers/mockSubscriptionDb.ts) and import it here.
  • app/lib/methods/loadMessagesForRoom.test.ts#L63-L83: import and use the same shared helper here instead of re-declaring the identical mock.
🤖 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 `@app/lib/methods/helpers/advanceSyncCursor.test.ts` around lines 47 - 64, The
subscription database mock setup is duplicated across both test suites. Extract
the shared subscription state, update mutation behavior, and database active
get/write mocks into a reusable helper, then import and use it in
app/lib/methods/helpers/advanceSyncCursor.test.ts (lines 47-64) and
app/lib/methods/loadMessagesForRoom.test.ts (lines 63-83), preserving each
suite’s existing behavior.
app/lib/methods/loadMissedMessages.ts (1)

99-126: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No safety cap on the pagination loop.

The for (;;) loop only terminates when both cursors resolve to null. If the server never returns a terminal cursor (bug, misconfiguration, or the 0-cursor issue above), this awaited call inside RoomView.init() would hang indefinitely, leaving the room stuck in a loading state.

🛡️ Suggested guard
+	const MAX_PAGES = 200; // sanity cap to avoid an unbounded loop on a misbehaving server
+	let pages = 0;
 	for (;;) {
+		if (++pages > MAX_PAGES) {
+			log(`loadMissedMessages: exceeded max pages for ${rid}`);
+			break;
+		}
 		const { ... } = await getSyncMessagesFromCursor(...);
🤖 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 `@app/lib/methods/loadMissedMessages.ts` around lines 99 - 126, Add a bounded
iteration guard to the pagination loop in the missed-message loading flow around
getSyncMessagesFromCursor, enforcing a finite maximum number of pages and
exiting through the existing completion path when the cap is reached. Preserve
sequential fetching, cursor updates, message processing, and sync-cursor
advancement for normal terminal responses.
🤖 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 `@app/lib/methods/loadMessagesForRoom.test.ts`:
- Line 63: Remove the duplicate let subscription declarations, keeping a single
binding with the existing type and scope used by the test.

---

Outside diff comments:
In `@app/containers/MessageActions/index.tsx`:
- Around line 237-256: Update handleUnread so the message ts value is converted
to a real Date before assigning it to sub.ls. Reuse the existing shared
timestamp coercion helper if available, otherwise parse string timestamps
explicitly, while preserving the current subscription update flow.

In `@app/views/RoomView/index.tsx`:
- Around line 1406-1424: Update the !previousItem branch in renderItem so
showUnreadSeparator treats an item timestamp equal to lastSeen the same as a
timestamp after lastSeen, matching the existing subsequent-item logic while
preserving the false result when lastSeen is absent.

---

Nitpick comments:
In `@app/lib/methods/helpers/advanceSyncCursor.test.ts`:
- Around line 47-64: The subscription database mock setup is duplicated across
both test suites. Extract the shared subscription state, update mutation
behavior, and database active get/write mocks into a reusable helper, then
import and use it in app/lib/methods/helpers/advanceSyncCursor.test.ts (lines
47-64) and app/lib/methods/loadMessagesForRoom.test.ts (lines 63-83), preserving
each suite’s existing behavior.

In `@app/lib/methods/loadMissedMessages.ts`:
- Around line 99-126: Add a bounded iteration guard to the pagination loop in
the missed-message loading flow around getSyncMessagesFromCursor, enforcing a
finite maximum number of pages and exiting through the existing completion path
when the cap is reached. Preserve sequential fetching, cursor updates, message
processing, and sync-cursor advancement for normal terminal responses.
🪄 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: d45d5260-da25-4e5e-add2-692a029492ab

📥 Commits

Reviewing files that changed from the base of the PR and between fbd4243 and e92e812.

📒 Files selected for processing (20)
  • CLAUDE.md
  • CONTEXT.md
  • app/containers/MessageActions/index.tsx
  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMissedMessages.ts
  • app/lib/methods/loadNextMessages.ts
  • app/lib/methods/loadSurroundingMessages.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/subscriptions/room.test.ts
  • app/lib/methods/subscriptions/room.ts
  • app/lib/methods/updateLastOpen.ts
  • app/views/RoomView/constants.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/index.tsx
  • app/views/RoomView/services/getMessages.ts
  • app/views/RoomView/services/index.ts
💤 Files with no reviewable changes (5)
  • app/views/RoomView/services/getMessages.ts
  • app/lib/methods/updateLastOpen.ts
  • app/views/RoomView/services/index.ts
  • app/lib/methods/subscriptions/room.test.ts
  • app/lib/methods/subscriptions/room.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/methods/loadNextMessages.ts
  • app/lib/methods/loadSurroundingMessages.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/constants.ts
  • app/containers/MessageActions/index.tsx
  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/methods/loadNextMessages.ts
  • app/lib/methods/loadSurroundingMessages.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/constants.ts
  • app/containers/MessageActions/index.tsx
  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: In this React Native application, UI code should read local data from WatermelonDB; sagas synchronize that data with the server.
Use Redux and Redux-Saga for global/server state, and Zustand for feature-local stores; do not assume all state is in Redux.
Derive sync watermarks only from raw server response payloads; never use local database rows or Date.now() as the source of watermark timestamps.

Files:

  • app/lib/methods/loadNextMessages.ts
  • app/lib/methods/loadSurroundingMessages.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/constants.ts
  • app/containers/MessageActions/index.tsx
  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-07-26T18:58:43.450Z
Learning: Before committing, run `pnpm prettier-lint` and `TZ=UTC pnpm test` for modified files.
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-07-26T18:58:43.450Z
Learning: Run `pnpm pod-install` before building for iOS.
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative

Timestamp: 2026-07-26T18:58:43.450Z
Learning: Consult `CONTEXT.md` for repository-specific implementation guidance and `.github/README.md` for CI triggers, call graphs, and manual gates.
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/methods/loadNextMessages.ts
  • app/lib/methods/loadSurroundingMessages.ts
  • app/views/RoomView/definitions.ts
  • app/views/RoomView/constants.ts
  • app/containers/MessageActions/index.tsx
  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/containers/MessageActions/index.tsx
  • app/views/RoomView/index.tsx
📚 Learning: 2026-06-25T18:37:25.526Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.test.tsx:16-22
Timestamp: 2026-06-25T18:37:25.526Z
Learning: In Rocket.Chat ReactNative tests that mock selectors for `useAppSelector`, don’t require the mocked selector input to be typed as `IApplicationState` when the fixture only includes a partial Redux state slice (e.g., only `server` and `settings`). Requiring the full `IApplicationState` type in that scenario forces unsafe `as IApplicationState` casts and undermines type-safety. For these narrowly scoped selector-mock fixtures, use a less strict type (e.g., `any`) to keep the mock focused on the slice under test.

Applied to files:

  • app/lib/methods/helpers/advanceSyncCursor.test.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMessagesForRoom.test.ts
📚 Learning: 2026-06-24T22:58:43.390Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7157
File: app/views/MessagesView/index.tsx:392-392
Timestamp: 2026-06-24T22:58:43.390Z
Learning: When wrapping a React Native component (e.g., via `withSafeAreaInsets`) ensure `hoistNonReactStatics` is only required if the wrapped component actually defines static properties/methods that consumers rely on. If the component has no statics (as in `app/views/MessagesView/index.tsx`), you can omit `hoistNonReactStatics` for this case.

Applied to files:

  • app/views/RoomView/index.tsx
🔇 Additional comments (16)
app/lib/methods/helpers/advanceSyncCursor.ts (3)

9-22: LGTM!


26-29: LGTM!

Also applies to: 36-46


30-35: 🎯 Functional Correctness

No change needed for db.get('subscriptions') typing.

active returns TAppDatabase, whose get signature is <T extends TAppDatabaseNames>(db: T) => Collection<ObjectType<T>>; this maps subscriptions to TSubscriptionModel, so the assignment is already typed correctly.

			> Likely an incorrect or invalid review comment.
CONTEXT.md (2)

93-116: LGTM!


264-264: LGTM!

CLAUDE.md (1)

28-28: LGTM!

app/lib/methods/loadMessagesForRoom.ts (1)

13-13: LGTM!

Also applies to: 36-48, 81-84, 117-117, 128-128, 147-150

app/lib/methods/loadMessagesForRoom.test.ts (1)

265-345: LGTM!

app/lib/methods/loadMissedMessages.test.ts (1)

1-199: LGTM!

app/lib/methods/loadNextMessages.ts (1)

40-41: LGTM!

app/lib/methods/loadSurroundingMessages.ts (1)

54-55: LGTM!

app/views/RoomView/constants.ts (1)

3-13: LGTM!

app/views/RoomView/definitions.ts (1)

32-65: LGTM!

app/views/RoomView/index.tsx (1)

82-82: LGTM!

Also applies to: 181-181, 682-696, 1124-1131

app/lib/methods/readMessages.ts (1)

8-8: 🗄️ Data Integrity & Integration

No caller updates needed.

The readMessages call sites use the 2-argument signature, and updateLastOpen is not passed anywhere.

app/lib/methods/loadMissedMessages.ts (1)

36-58: 🎯 Functional Correctness

No change needed: timestamp cursors are not terminal unless null.

chat.syncMessages uses millisecond timestamps for the next cursor, so a non-null 0 value would carry meaningful cursor information and would not be treated as the same terminal state as null/undefined.

			> Likely an incorrect or invalid review comment.

...(params._updatedAt !== undefined ? { _updatedAt: params._updatedAt } : {})
} as any);

let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };

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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Duplicate let subscription declaration.

The subscription variable declaration is duplicated four times at the same location. Re-declaring a let binding in the same block scope is a compile error ("Cannot redeclare block-scoped variable").

🐛 Proposed fix
-let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
-let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
-let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
 let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
📝 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
let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
let subscription: { id: string; lastOpen: Date | null; update: jest.Mock };
🤖 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 `@app/lib/methods/loadMessagesForRoom.test.ts` at line 63, Remove the duplicate
let subscription declarations, keeping a single binding with the existing type
and scope used by the test.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109418

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.

1 participant