Skip to content

Conversation

@KyleAMathews
Copy link
Contributor

Summary

Fixes a bug where the UpToDateTracker could get stuck in an infinite loop when the CDN returns cached responses with the same cursor value as stored in localStorage. The issue occurred during replay mode when the client would repeatedly suppress notifications without exiting replay mode.

Changes

  • Added regression test in up-to-date-tracker.test.ts that reproduces the infinite loop scenario:

    • Simulates a user refreshing within the localStorage validity window
    • Mocks a CDN that returns cached responses with matching cursors
    • Verifies the fix prevents excessive fetch calls (≤3 instead of 10+)
    • Confirms notifications are properly emitted after exiting replay mode
  • Added unit test configuration (vitest.unit.config.ts):

    • Dedicated Vitest config for unit tests that don't require Electric server
    • Includes localStorage mock for testing persistence logic
    • Configured with jsdom environment for browser API compatibility

Implementation Details

The test documents the bug scenario and expected fix:

  1. Client enters replay mode with lastSeenCursor=1000 from localStorage
  2. First request returns cursor=1000 → notification suppressed
  3. Fix: lastSeenCursor must be cleared after first suppression to exit replay mode
  4. Subsequent requests proceed normally without tight looping

The safety limit of 3 fetch calls allows for:

  • Initial request (replay mode)
  • Live request (exiting replay mode)
  • One additional request to stabilize

Without the fix, the test would hit the 10-call limit, indicating the infinite loop.

This test reproduces a bug where the client gets stuck in an infinite loop
when the CDN keeps returning cached responses with the same cursor that's
stored in localStorage from a previous session.

The bug occurs because #lastSeenCursor is not cleared after suppressing
an up-to-date notification, causing the client to stay in replay mode
forever and make rapid requests in a tight loop.

Also adds vitest.unit.config.ts to run unit tests without requiring
the Electric server.

https://claude.ai/code/session_01Gc8cwwR7XDsbX5D3jXQKvF
When the CDN keeps returning cached responses with the same cursor as
stored in localStorage, the client would get stuck in an infinite loop
because #lastSeenCursor was never cleared after suppressing an up-to-date.

The fix clears #lastSeenCursor immediately after the first suppression,
which exits replay mode and allows subsequent requests to notify
subscribers normally.

Also fixes the test to properly use the upToDateTracker singleton
instead of just setting localStorage (the singleton doesn't reload
from localStorage after initialization).

https://claude.ai/code/session_01Gc8cwwR7XDsbX5D3jXQKvF
Update the CDN infinite loop test to properly test the loop behavior by:
- Using subscribe: true to allow multiple requests
- Using Promise.race with a timeout as a safety valve
- Verifying gotUpToDate is true (which fails without the fix)
- Verifying fetchCallCount is ≤3 (would be 10+ without fix)

The test now properly fails when the fix is removed and passes when
the fix is in place.

https://claude.ai/code/session_01Gc8cwwR7XDsbX5D3jXQKvF
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@electric-sql/react@3773
npm i https://pkg.pr.new/@electric-sql/client@3773
npm i https://pkg.pr.new/@electric-sql/y-electric@3773

commit: cbbcbc2

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.42%. Comparing base (ba6dd2c) to head (cbbcbc2).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3773      +/-   ##
==========================================
+ Coverage   87.36%   87.42%   +0.05%     
==========================================
  Files          23       23              
  Lines        2011     2012       +1     
  Branches      528      533       +5     
==========================================
+ Hits         1757     1759       +2     
+ Misses        252      251       -1     
  Partials        2        2              
Flag Coverage Δ
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 93.55% <100.00%> (+0.08%) ⬆️
packages/y-electric 56.05% <ø> (ø)
typescript 87.42% <100.00%> (+0.05%) ⬆️
unit-tests 87.42% <100.00%> (+0.05%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

KyleAMathews and others added 2 commits January 27, 2026 07:45
- Clarify comment explaining replay mode exit behavior
- Refactor Promise.race pattern in test to use named variables and Array.some()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants