Skip to content

fix: derive message sync cursor from server timestamps#7506

Open
diegolmello wants to merge 13 commits into
developfrom
swamp-string
Open

fix: derive message sync cursor from server timestamps#7506
diegolmello wants to merge 13 commits into
developfrom
swamp-string

Conversation

@diegolmello

@diegolmello diegolmello commented Jul 23, 2026

Copy link
Copy Markdown
Member

Proposed changes

Messages that arrived while a room wasn't actively synced could permanently disappear on mobile. subscription.lastOpen doubles as the chat.syncMessages cursor, but it was written from the client clock and advanced unconditionally — on every room read and again when leaving the room. That asserts "synced up to now" without the client having verified it, so the cursor could jump past messages the client never received. Once the cursor points past a message, the server answers with a successful empty response, the cursor re-advances (client clock again), and that message is skipped forever. Web/desktop were unaffected — only the mobile cursor pointed past the messages.

The cursor overran real sync coverage two independent ways:

  • Coverage gap — leaving or reading a room stamped the cursor with the current client time even for messages the client hadn't received yet (offline, dropped stream event, or in-flight latency). Reproducible offline with a perfectly-synced clock.
  • Clock skew — with the device clock ahead of the server by Δ, the cursor landed Δ in the server's future, so any message arriving within Δ of backgrounding fell behind it.

This PR makes the cursor server-derived, so it can only ever point at timestamps the client has actually persisted:

  • New advanceSyncCursor helper: after a batch of messages is persisted, lastOpen advances to the max _updatedAt of the batch — only forward, never on an empty fetch, and never from older-history (scroll-up) windows, where an edited old message's recent _updatedAt could jump the cursor past unsynced newer messages.
  • loadMissedMessages advances the cursor after every successful sync; loadMessagesForRoom bootstraps it on the first/newest load.
  • readMessages no longer writes lastOpen (it only marks the room read now), and the client-clock reseed on room unsubscribe (updateLastOpen) is removed.

Two adjacent sync bugs in loadMissedMessages are also fixed:

  • When only the DELETED stream paginated, positional destructuring put the DELETED page into the updated slot, persisting deleted messages as updates.
  • The recursive pagination call was not awaited, so pagination failures were silent.

Issue(s)

How to test or reproduce

Offline coverage gap (no clock change needed):

  1. Open a room and stay on it.
  2. Enable airplane mode, then leave the room (navigate back).
  3. From another device/user, post messages to that room.
  4. Disable airplane mode and reopen the room.

Clock skew:

  1. Set the device clock manually ~10 minutes ahead of the server's clock (disable automatic time).
  2. Open a room, then force-stop the app.
  3. Have another user send messages that land within the skew window.
  4. Reopen the app and enter the room.

Before this fix the new messages never appear (and never recover, even after restarts). After the fix they load normally. Regression coverage lives in app/lib/methods/loadMissedMessages.test.ts, which exercises both the offline coverage gap and the skewed-clock scenario against the >=7.1 cursor API semantics.

Screenshots

N/A (sync behavior; no UI changes)

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

Reusing lastOpen (instead of adding a dedicated cursor field) was a deliberate choice: enumerating all readers/writers showed it is exclusively the sync cursor — the unread separator uses room.ls — so a new field plus migration would only duplicate it. Messages received live over the stream while a room is open don't advance the cursor; the next open re-syncs them once (idempotent upsert), which is safe and keeps the invariant simple: the cursor only ever points at server timestamps the client has actually persisted.

Summary by CodeRabbit

  • New Features

    • Improved room synchronization using server-provided message update timestamps.
    • Automatically retrieves missed updated and deleted messages, including paginated results.
    • Preserves synchronization progress safely during reconnects and concurrent activity.
  • Bug Fixes

    • Prevents historical message loading and local read actions from incorrectly advancing sync progress.
    • Avoids restoring messages that were deleted on the server.
    • Ensures partial sync failures can be retried without losing progress.
  • Tests

    • Added extensive coverage for pagination, reconnects, offline recovery, deletions, and concurrent updates.

…ent clock skew

subscription.lastOpen doubles as the chat.syncMessages cursor but was
written from the client clock and advanced unconditionally after every
room open. A client clock ahead of the server orphans messages that
arrived while the app was backgrounded: the server answers a future
cursor with a successful empty response, the app re-advances the
cursor, and the window is skipped forever.

The cursor now advances only from the max _updatedAt of persisted
batches (advanceSyncCursor), never on an empty fetch and never from
older-history windows. readMessages no longer writes lastOpen and
updateLastOpen (client-clock reseed on unsubscribe) is removed.

Also fixes two sync bugs in loadMissedMessages: DELETED pagination
results landing in the updated slot when only deletedNext paginated,
and the un-awaited recursive pagination call.
@diegolmello
diegolmello temporarily deployed to approve_e2e_testing July 23, 2026 00:53 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 290d11ba-135f-4a64-acdd-3f87ef0f4a56

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca4ead and 55569fe.

📒 Files selected for processing (1)
  • app/lib/methods/subscriptions/room.test.ts
💤 Files with no reviewable changes (1)
  • app/lib/methods/subscriptions/room.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format

Walkthrough

Room synchronization now tracks server-derived _updatedAt cursors, awaits paginated missed-message recovery, and separates lastOpen from client read timestamps. LokiJS integration tests cover cursor advancement, pagination, persistence, concurrency, reconnects, and error handling.

Changes

Sync cursor flow

Layer / File(s) Summary
Cursor advancement and room loading
app/lib/methods/helpers/advanceSyncCursor.ts, app/lib/methods/loadMessagesForRoom.ts, app/lib/database/__tests__/advanceSyncCursor.test.ts, app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
Adds forward-only cursor advancement from maximum valid _updatedAt values and skips advancement for historical windows marked with latest.
Missed-message pagination and cursor updates
app/lib/methods/loadMissedMessages.ts, app/lib/database/__tests__/fakeSyncServer.ts, app/lib/database/__tests__/loadMissedMessages.integration.test.ts, app/lib/database/__tests__/lokiTestDatabase.*
Separates UPDATED and DELETED cursors, awaits recursive pages, applies accumulated changes, and advances lastOpen after successful synchronization.
Read-state and subscription lifecycle separation
app/lib/methods/readMessages.ts, app/views/RoomView/index.tsx, app/lib/methods/subscriptions/room.ts, app/lib/methods/updateLastOpen.ts, app/lib/database/__tests__/reconnectCycle.integration.test.ts
Removes client-read updates to lastOpen, removes the unsubscribe cursor update, and validates reconnect and clock-skew behavior.
Room persistence and stream validation
app/lib/database/__tests__/room.integration.test.ts, app/lib/methods/subscriptions/room.test.ts, jest.config.js
Adds Loki-backed persistence and concurrency coverage while removing obsolete updateLastOpen mocking and the duplicated concurrency test.

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

Sequence Diagram(s)

sequenceDiagram
  participant RoomView
  participant loadMissedMessages
  participant syncMessages
  participant Database
  RoomView->>loadMissedMessages: open room and request missed messages
  loadMissedMessages->>syncMessages: request UPDATED and DELETED pages
  syncMessages-->>loadMissedMessages: return messages and cursors
  loadMissedMessages->>Database: persist updates and removals
  loadMissedMessages->>Database: advance lastOpen from _updatedAt
  RoomView->>Database: persist client read state without changing lastOpen
Loading

Possibly related PRs

Suggested labels: type: bug

🚥 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 summarizes the main change: deriving the message sync cursor from server timestamps.
Linked Issues check ✅ Passed The cursor-advancement fix and related sync flow changes address the missing-message bug described in issue #7499.
Out of Scope Changes check ✅ Passed The added tests and helper utilities support the cursor-sync fix and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • SUP-1078: 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.

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

🧹 Nitpick comments (1)
app/lib/methods/loadMissedMessages.test.ts (1)

34-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the fake server and subscription fixtures.

The any-typed subscription, callbacks, SDK parameters, and object-shaped TFakeServerMessage alias bypass the contract this regression test is meant to protect. Use interfaces for the fixture shapes and explicit parameter/return types.

As per coding guidelines, “Use TypeScript for type safety; add explicit type annotations to function parameters and return types” and “Prefer interfaces over type aliases for defining object shapes in TypeScript.”

Also applies to: 64-71, 106-117

🤖 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.test.ts` around lines 34 - 40, Replace the
any-based mockSubscription, fake server, callbacks, SDK parameters, and
object-shaped TFakeServerMessage alias with interfaces describing their fixture
contracts. Add explicit parameter and return types across the affected helpers
and fixture methods, including the update callback, while preserving the
existing test behavior and SDK-compatible shapes.

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 `@app/lib/methods/helpers/advanceSyncCursor.ts`:
- Around line 33-41: Make cursor advancement atomic in the update callback
passed to subscription.update: re-read and compare s.lastOpen against latest
inside db.write, and assign the new Date only when latest is greater. Preserve
the existing early return optimization while ensuring concurrent transactions
cannot move lastOpen backward.

In `@app/lib/methods/loadMissedMessages.ts`:
- Around line 106-109: Update loadMissedMessages so advanceSyncCursor is not
called before recursive DELETED/UPDATED pagination completes. Accumulate the
updated and deleted records across the entire successful pagination chain, then
advance the shared cursor once after all recursive loadMissedMessages calls
succeed, preserving failure behavior so no cursor advancement occurs when a
later page fails.

---

Nitpick comments:
In `@app/lib/methods/loadMissedMessages.test.ts`:
- Around line 34-40: Replace the any-based mockSubscription, fake server,
callbacks, SDK parameters, and object-shaped TFakeServerMessage alias with
interfaces describing their fixture contracts. Add explicit parameter and return
types across the affected helpers and fixture methods, including the update
callback, while preserving the existing test behavior and SDK-compatible shapes.
🪄 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: 668e8fa7-ae6a-4973-bbf5-75e707783855

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9b5cc and 0ec20cd.

📒 Files selected for processing (9)
  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/loadMessagesForRoom.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/lib/methods/loadMissedMessages.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/index.tsx
💤 Files with no reviewable changes (3)
  • app/lib/methods/subscriptions/room.test.ts
  • app/lib/methods/subscriptions/room.ts
  • app/lib/methods/updateLastOpen.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 (4)
**/*.{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/helpers/advanceSyncCursor.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
  • app/lib/methods/loadMessagesForRoom.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

Use TypeScript strict mode; resolve application imports relative to the app/ base URL.

Files:

  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
  • app/lib/methods/loadMessagesForRoom.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the repository Prettier style: tabs, single quotes, 130-character width, no trailing commas, omitted arrow-function parentheses where allowed, and same-line brackets.

Files:

  • app/lib/methods/helpers/advanceSyncCursor.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
  • app/lib/methods/loadMessagesForRoom.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run Jest tests with TZ=UTC to ensure deterministic timezone-dependent test behavior.

Files:

  • app/lib/methods/loadMissedMessages.test.ts
🧠 Learnings (4)
📚 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/helpers/advanceSyncCursor.ts
  • app/lib/methods/readMessages.ts
  • app/lib/methods/loadMissedMessages.test.ts
  • app/views/RoomView/index.tsx
  • app/lib/methods/loadMissedMessages.ts
  • app/lib/methods/loadMessagesForRoom.ts
📚 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/loadMissedMessages.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
📚 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/views/RoomView/index.tsx
🔇 Additional comments (1)
app/lib/methods/loadMissedMessages.test.ts (1)

97-185: 📐 Maintainability & Code Quality

Verify this Jest suite runs in UTC.

Please run this timezone-sensitive suite with TZ=UTC as required by the test-path guideline.

As per coding guidelines, “**/*.test.{ts,tsx,js,jsx}: Run Jest tests with TZ=UTC to ensure deterministic timezone-dependent test behavior.”

Source: Coding guidelines

Comment thread app/lib/methods/helpers/advanceSyncCursor.ts
Comment thread app/lib/methods/loadMissedMessages.ts Outdated
Advancing lastOpen per page was unsafe across the independent
UPDATED/DELETED streams: a newer timestamp from one stream's page could
move the shared cursor past records the other stream still had pending,
skipping them permanently if a later page failed. Pages are still
persisted as they arrive, but the cursor now advances exactly once after
the whole chain completes.

Also re-checks lastOpen inside the database write so concurrent syncs
can't move the cursor backward.

Addresses CodeRabbit review on #7506.
@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109394

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.75.0.109396

@diegolmello diegolmello changed the title fix: derive message sync cursor from server timestamps to survive client clock skew fix: derive message sync cursor from server timestamps, not the client clock Jul 23, 2026
@diegolmello diegolmello changed the title fix: derive message sync cursor from server timestamps, not the client clock fix: derive message sync cursor from server timestamps Jul 23, 2026

@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.

🧹 Nitpick comments (1)
app/lib/methods/loadMissedMessages.test.ts (1)

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

Add the explicit async return type.

Per the TypeScript guidelines, annotate the Jest callback with (): Promise<void>.

Proposed change
-	it('keeps a message received while offline: reading/leaving the room must not advance the cursor to the client clock', async () => {
+	it('keeps a message received while offline: reading/leaving the room must not advance the cursor to the client clock', async (): Promise<void> => {
🤖 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.test.ts` at line 180, Update the Jest
callback for the test beginning “keeps a message received while offline” to
explicitly declare the async return type as Promise<void>, preserving the
existing test behavior.

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.

Nitpick comments:
In `@app/lib/methods/loadMissedMessages.test.ts`:
- Line 180: Update the Jest callback for the test beginning “keeps a message
received while offline” to explicitly declare the async return type as
Promise<void>, preserving the existing test behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 31667a08-33dd-4742-b934-528a16218685

📥 Commits

Reviewing files that changed from the base of the PR and between 9b78fd7 and 04e69c2.

📒 Files selected for processing (1)
  • app/lib/methods/loadMissedMessages.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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/loadMissedMessages.test.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

Use TypeScript strict mode; resolve application imports relative to the app/ base URL.

Files:

  • app/lib/methods/loadMissedMessages.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the repository Prettier style: tabs, single quotes, 130-character width, no trailing commas, omitted arrow-function parentheses where allowed, and same-line brackets.

Files:

  • app/lib/methods/loadMissedMessages.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run Jest tests with TZ=UTC to ensure deterministic timezone-dependent test behavior.

Files:

  • app/lib/methods/loadMissedMessages.test.ts
🧠 Learnings (2)
📚 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/loadMissedMessages.test.ts
📚 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/loadMissedMessages.test.ts
🔇 Additional comments (1)
app/lib/methods/loadMissedMessages.test.ts (1)

180-203: 📐 Maintainability & Code Quality

Run this test with TZ=UTC.

The test appears timezone-independent because it uses Date.UTC, but repository guidance requires Jest tests to be executed with TZ=UTC; please verify this test through the repository’s Jest command with that environment variable set.

Source: Coding guidelines

Stands up the real WatermelonDB JS core (writer lock, db.write
serialization, queries) over an in-memory LokiJSAdapter and the
production appSchema, mocking only sdk. Ships a shared fake >=7.1
chat.syncMessages server (paginated UPDATED/DELETED cursors, failure
injection) and seed/reset helpers for the sync-cursor integration
suites on PR #7506.
Add advanceSyncCursor.test.ts (9 tests) asserting the real persisted
subscription.lastOpen on the LokiJS in-memory WatermelonDB harness, with
only log mocked and the real getSubscriptionByRoomId running against the
live DB. Covers max-of-batch, forward-only (older + equal), empty batch,
all-missing _updatedAt, missing-skipped-without-poisoning, no-subscription
clean return, and error swallow+log.

The re-read-inside-write guard is exercised deterministically against the
real writer lock: a gated concurrent db.write holds the lock while
advanceSyncCursor reads the stale cursor and queues behind it, then commits
a higher timestamp so the in-flight write must skip its stale value rather
than regress. Mutating the guard to an unconditional set fails this test.

Also fix 3 pre-existing tsc errors in the harness (optional Relation access
and SubscriptionType cast) so the project tsc gate passes.
Cover the !args.latest gate in loadMessagesForRoom: newest load advances the
sync cursor to the window max; a scroll-up window (latest set) never advances
it; and the edited-old-message defense keeps the cursor pinned when scroll-up
surfaces an old message carrying a recent _updatedAt. Real persisted messages
+ real subscription.lastOpen on the LokiJS harness; only sdk/auxStore/
encryption/log mocked. Gate proven load-bearing by mutation.
The LokiJS integration harness left the in-memory driver open, so a full
`jest --runInBand` (the CI command) hung after a green run ("Jest did not
exit one second after the test run has completed"). Add closeLokiTestDatabase
— reaching the in-process driver the same way the adapter's own testClone does
(_dispatcher._worker._bridge.driver) — and call it from afterAll in every suite
that builds a database. The real CI command now exits on its own; no
--forceExit needed.

Also exclude the harness helpers (lokiTestDatabase.ts, fakeSyncServer.ts) from
jest collection via testPathIgnorePatterns, matching the existing
testHelpers.tsx entry. jest-expo's testMatch globs every __tests__/**/*, so
these non-test helpers failed the full suite with "Your test suite must
contain at least one test".
The re-read-guard test deliberately queues a db.write behind a running
writer. In non-production builds WatermelonDB's WorkQueue.enqueue arms a
~1.5s setTimeout to warn about a possibly stuck queue whenever a write is
enqueued behind an in-flight one. That live timer outlives Jest's 1s
post-run window and prints the benign "Jest did not exit one second after
the test run has completed" warning on the advanceSyncCursor suite.

Wrap the contended section in withWriterQueueDiagnosticCleared, which
captures and clears that timer, so the event loop drains and the suite
exits cleanly without --forceExit. No assertion or behavior changed.
babel-jest hoists jest.mock() to the top of the module regardless of
source position (the factories only close over mock-prefixed vars), so
the real imports never needed to sit below the mocks behind
// eslint-disable-next-line import/first. Move them into the normal top
import block and place the jest.mock() calls after, removing the disable
comments and the import/order 'empty line within import group' nit. No
behavior change; mocking still applies identically.

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

🧹 Nitpick comments (3)
app/lib/database/__tests__/fakeSyncServer.ts (1)

53-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add explicit return types to the helper functions.

serialize, createFakeSyncServer, and paginate rely on inference for their return types.

Suggested change
-const serialize = (message: IFakeServerMessage): TFakeServerMessageWire => ({
+const serialize = (message: IFakeServerMessage): TFakeServerMessageWire => ({

-export const createFakeSyncServer = (options: IFakeSyncServerOptions = {}) => {
+export const createFakeSyncServer = (options: IFakeSyncServerOptions = {}): IFakeSyncServer => {

-const paginate = (source: IFakeServerMessage[], next: number | null | undefined, pageSize: number) => {
+const paginate = (
+	source: IFakeServerMessage[],
+	next: number | null | undefined,
+	pageSize: number
+): { page: TFakeServerMessageWire[]; nextCursor: number | null } => {

As per coding guidelines, “add explicit type annotations to function parameters and return types.”

🤖 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/database/__tests__/fakeSyncServer.ts` around lines 53 - 71, Add
explicit return type annotations to the visible helper functions: serialize
should return TFakeServerMessageWire, createFakeSyncServer should return
IFakeSyncServer, and paginate should use an explicit return type matching its
page and nextCursor result shape. Keep the existing implementations unchanged.

Source: Coding guidelines

app/lib/database/__tests__/lokiTestDatabase.test.ts (1)

48-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the reset-harness test self-contained.

This test only proves reset works if the prior test happened to create a record first. Since every other integration test in this cohort relies on resetLokiTestDatabase actually clearing data between tests, it's worth having this specific test seed and assert independently, so it can't pass vacuously in isolation (e.g. --testNamePattern, watch-mode filtering).

♻️ Proposed fix to make the test order-independent
 it('resets between tests: previous records are gone', async () => {
+	await seedSubscription(database, { rid: 'room-2' });
 	const count = await database.get('messages').query().fetchCount();
 	expect(count).toBe(0);
+	const subCount = await database.get('subscriptions').query().fetchCount();
+	expect(subCount).toBe(1); // sanity: seeded before this test's own beforeEach reset already ran
 });
🤖 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/database/__tests__/lokiTestDatabase.test.ts` around lines 48 - 51,
Update the “resets between tests: previous records are gone” test to seed a
message record before invoking or relying on the reset harness, then assert that
the messages collection is empty afterward. Keep the test self-contained and
order-independent while preserving the existing reset behavior used by the
surrounding integration tests.
app/lib/database/__tests__/lokiTestDatabase.ts (1)

74-84: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Pin or recheck this private WatermelonDB close path before bumping the package.

This matches @nozbe/watermelondb@0.28.1-0, but it relies on private adapter internals (database.adapter, _dispatcher._worker._bridge.driver.loki). A version/config change can silently drop the helper to Promise.resolve() and bring back the Jest hang it prevents, so keep an upgrade-related check or comment around this area.

🤖 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/database/__tests__/lokiTestDatabase.ts` around lines 74 - 84, Update
closeLokiTestDatabase to retain an upgrade-related check or focused comment
documenting the pinned WatermelonDB private close path through database.adapter
and _dispatcher._worker._bridge.driver.loki. Ensure future package or
configuration changes prompt revalidation instead of silently falling back to
Promise.resolve().
🤖 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/database/__tests__/fakeSyncServer.ts`:
- Around line 88-104: Update handleSyncMessages to explicitly branch only for
'DELETED' and 'UPDATED' types, preserving each stream’s existing pagination and
failure behavior. After those supported cases, throw an error for missing or
unsupported params.type values instead of treating them as updated requests.
- Around line 65-70: Update the paginate helper so each page includes all
records sharing the final record’s _updatedAt timestamp, even when this exceeds
pageSize. Calculate the boundary timestamp from the initial page, extend the
page through matching records at that timestamp, and emit nextCursor only when
records remain beyond that boundary; preserve serialize mapping and cursor
filtering.

---

Nitpick comments:
In `@app/lib/database/__tests__/fakeSyncServer.ts`:
- Around line 53-71: Add explicit return type annotations to the visible helper
functions: serialize should return TFakeServerMessageWire, createFakeSyncServer
should return IFakeSyncServer, and paginate should use an explicit return type
matching its page and nextCursor result shape. Keep the existing implementations
unchanged.

In `@app/lib/database/__tests__/lokiTestDatabase.test.ts`:
- Around line 48-51: Update the “resets between tests: previous records are
gone” test to seed a message record before invoking or relying on the reset
harness, then assert that the messages collection is empty afterward. Keep the
test self-contained and order-independent while preserving the existing reset
behavior used by the surrounding integration tests.

In `@app/lib/database/__tests__/lokiTestDatabase.ts`:
- Around line 74-84: Update closeLokiTestDatabase to retain an upgrade-related
check or focused comment documenting the pinned WatermelonDB private close path
through database.adapter and _dispatcher._worker._bridge.driver.loki. Ensure
future package or configuration changes prompt revalidation instead of silently
falling back to Promise.resolve().
🪄 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: dc08728d-0b0d-4e1c-a564-37b88962f40a

📥 Commits

Reviewing files that changed from the base of the PR and between 04e69c2 and 6ca4ead.

📒 Files selected for processing (9)
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • jest.config.js
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/database/__tests__/lokiTestDatabase.test.ts
  • jest.config.js
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.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

Use TypeScript strict mode; resolve application imports relative to the app/ base URL.

Files:

  • app/lib/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the repository Prettier style: tabs, single quotes, 130-character width, no trailing commas, omitted arrow-function parentheses where allowed, and same-line brackets.

Files:

  • app/lib/database/__tests__/lokiTestDatabase.test.ts
  • jest.config.js
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
app/lib/database/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use WatermelonDB for the offline-first SQLite database; keep models under model/ and schema definitions under schema/.

Files:

  • app/lib/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run Jest tests with TZ=UTC to ensure deterministic timezone-dependent test behavior.

Files:

  • app/lib/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
🧠 Learnings (2)
📚 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/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/fakeSyncServer.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/lokiTestDatabase.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
📚 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/database/__tests__/lokiTestDatabase.test.ts
  • app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts
  • app/lib/database/__tests__/room.integration.test.ts
  • app/lib/database/__tests__/advanceSyncCursor.test.ts
  • app/lib/database/__tests__/reconnectCycle.integration.test.ts
  • app/lib/database/__tests__/loadMissedMessages.integration.test.ts
🔇 Additional comments (6)
app/lib/database/__tests__/room.integration.test.ts (1)

89-179: 📐 Maintainability & Code Quality

Verify this integration suite under UTC.

Run it with TZ=UTC through the repository’s Jest command. As per coding guidelines, “Run Jest tests with TZ=UTC to ensure deterministic timezone-dependent test behavior.”

Source: Coding guidelines

jest.config.js (1)

3-10: LGTM!

app/lib/database/__tests__/advanceSyncCursor.test.ts (1)

1-170: LGTM!

app/lib/database/__tests__/loadMessagesForRoom.integration.test.ts (1)

1-133: LGTM!

app/lib/database/__tests__/loadMissedMessages.integration.test.ts (1)

1-210: LGTM!

app/lib/database/__tests__/reconnectCycle.integration.test.ts (1)

1-178: LGTM!

Comment on lines +65 to +70
const paginate = (source: IFakeServerMessage[], next: number | null | undefined, pageSize: number) => {
const cursor = next ?? 0;
const matching = source.filter(message => message._updatedAt > cursor).sort((a, b) => a._updatedAt - b._updatedAt);
const page = matching.slice(0, pageSize);
const nextCursor = matching.length > page.length ? page[page.length - 1]._updatedAt : null;
return { page: page.map(serialize), nextCursor };

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 | 🟡 Minor | ⚡ Quick win

Do not split identical timestamps across pages.

With pageSize = 1, two records sharing _updatedAt return one record and set nextCursor to that timestamp; the next > query permanently excludes the other. Extend the page through its boundary timestamp before emitting the cursor.

🤖 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/database/__tests__/fakeSyncServer.ts` around lines 65 - 70, Update
the paginate helper so each page includes all records sharing the final record’s
_updatedAt timestamp, even when this exceeds pageSize. Calculate the boundary
timestamp from the initial page, extend the page through matching records at
that timestamp, and emit nextCursor only when records remain beyond that
boundary; preserve serialize mapping and cursor filtering.

Comment on lines +88 to +104
handleSyncMessages(params) {
if (params.type === 'DELETED') {
deletedPageRequests += 1;
if (server.failDeletedPageAtRequest && deletedPageRequests === server.failDeletedPageAtRequest) {
throw new Error('DELETED page request failed');
}
const { page, nextCursor } = paginate(server.deleted, params.next, deletedPageSize);
return { result: { deleted: page, cursor: { next: nextCursor, previous: null } } };
}

updatedPageRequests += 1;
if (server.failUpdatedPageAtRequest && updatedPageRequests === server.failUpdatedPageAtRequest) {
throw new Error('UPDATED page request failed');
}
const { page, nextCursor } = paginate(server.updated, params.next, updatedPageSize);
return { result: { updated: page, cursor: { next: nextCursor, previous: null } } };
},

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 | 🟡 Minor | ⚡ Quick win

Reject unsupported sync types.

Any missing or invalid type currently behaves as UPDATED, so integration tests can pass when callers omit or misspell the cursor stream. Validate 'UPDATED' and 'DELETED' explicitly, then throw for other values.

🤖 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/database/__tests__/fakeSyncServer.ts` around lines 88 - 104, Update
handleSyncMessages to explicitly branch only for 'DELETED' and 'UPDATED' types,
preserving each stream’s existing pagination and failure behavior. After those
supported cases, throw an error for missing or unsupported params.type values
instead of treating them as updated requests.

…B suites

loadMissedMessages.test.ts is entirely superseded — its 5 cases now have
mutation-proven real-DB equivalents in loadMissedMessages.integration.test.ts
(paginated deletions, no-advance-on-failure) and reconnectCycle.integration.test.ts
(offline coverage-gap, clock skew, backgrounded delivery). Delete the file.

subscriptions/room.test.ts keeps its subscribe/handleLogin/handleClose/DDP-recovery/
isAlive coverage but drops the lone updateMessage-concurrency case: the mock cannot
take the WMDB writer lock and only models a hand-rolled "pending changes" string, so
room.integration.test.ts supersedes it with the real prepareUpdate diagnostic.
@diegolmello
diegolmello requested a deployment to approve_e2e_testing July 24, 2026 12:59 — with GitHub Actions Waiting
diegolmello added a commit that referenced this pull request Jul 24, 2026
Advancing lastOpen per page was unsafe across the independent
UPDATED/DELETED streams: a newer timestamp from one stream's page could
move the shared cursor past records the other stream still had pending,
skipping them permanently if a later page failed. Pages are still
persisted as they arrive, but the cursor now advances exactly once after
the whole chain completes.

Also re-checks lastOpen inside the database write so concurrent syncs
can't move the cursor backward.

Addresses CodeRabbit review on #7506.
diegolmello added a commit that referenced this pull request Jul 24, 2026
Stands up the real WatermelonDB JS core (writer lock, db.write
serialization, queries) over an in-memory LokiJSAdapter and the
production appSchema, mocking only sdk. Ships a shared fake >=7.1
chat.syncMessages server (paginated UPDATED/DELETED cursors, failure
injection) and seed/reset helpers for the sync-cursor integration
suites on PR #7506.
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.

bug: Messages missing in chat

1 participant