Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jul 23, 2025

Summary

  • CRITICAL FIX: Resolves UI becoming unresponsive when optimistic updates queue up behind syncing transactions
  • Root Cause: Event filtering and batching logic was blocking legitimate user actions
  • Solution: User actions now bypass filtering and batching to ensure immediate UI updates
  • Comprehensive test coverage: Added test that reproduces and verifies the fix

Detailed Problem Analysis

This issue manifested when users rapidly clicked checkboxes in the todo app. The symptoms:

  • First few clicks work normally
  • UI suddenly becomes unresponsive to further clicks
  • Backend API calls continue working
  • Problem occurs when optimistic updates back up with slow sync responses

Root Cause Analysis

The issue had two critical components:

1. Event Filtering Problem

User actions on recently synced keys were being filtered out by recentlySyncedKeys logic. This logic was designed to prevent duplicate sync events but was incorrectly blocking legitimate user actions because:

  • When a transaction completes and syncs back, the key gets added to recentlySyncedKeys
  • Subsequent user actions on the same key would be filtered out as "duplicates"
  • The filtering logic didn't distinguish between user-initiated vs sync-initiated events

2. Batching Problem

Once sync transactions started, shouldBatchEvents would become true, causing all subsequent events (including user actions) to be batched instead of immediately updating the UI:

  • User actions would get added to a batch queue instead of immediately emitted
  • UI would appear frozen because React components weren't receiving state updates
  • Events would only be emitted when the batch was eventually flushed

Solution Implementation

1. Added User Action Context

  • Modified recomputeOptimisticState() to accept triggeredByUserAction parameter
  • Updated all user action code paths to pass triggeredByUserAction=true
  • Sync operations continue to pass triggeredByUserAction=false

2. Smart Event Filtering

  • User actions bypass filtering: When triggeredByUserAction=true, events are allowed through even if keys are in recentlySyncedKeys
  • Sync events still filtered: Duplicate sync events continue to be filtered for performance
  • Complex filtering skipped: User actions skip complex filtering logic entirely

3. Intelligent Batching

  • Updated emitEvents() to accept forceEmit parameter
  • User actions force immediate emission: forceEmit=true bypasses batching for immediate UI updates
  • Sync events remain batched: Performance optimizations preserved for sync operations

4. Comprehensive Code Path Coverage

Fixed all transaction creation paths:

  • Direct collection.insert() calls
  • Direct collection.update() calls
  • Direct collection.delete() calls
  • Ambient transaction operations

Key Implementation Details

// Core method signature change
private recomputeOptimisticState(triggeredByUserAction: boolean = false): void

// Smart filtering - user actions bypass restrictions
if (triggeredByUserAction) {
  return true // Always allow user actions through
}

// Smart batching - user actions get immediate updates  
private emitEvents(changes: Array<ChangeMessage<T, TKey>>, forceEmit = false): void {
  if (this.shouldBatchEvents && \!forceEmit) {
    // Batch sync events for performance
    this.batchedEvents.push(...changes)
    return
  }
  // User actions (forceEmit=true) skip batching for immediate UI updates
}

Test Coverage

New test case: should not block user actions when keys are recently synced

  • Reproduces the original issue scenario
  • Simulates rapid user actions on recently synced keys
  • Verifies user actions are never blocked
  • Confirms mutation functions are called for each action
  • Validates optimistic state updates work correctly

Verification

Manual testing: Can no longer reproduce the issue with rapid checkbox clicking
Automated tests: All existing tests pass + new regression test
Performance: Sync batching optimizations preserved
User experience: UI remains responsive under all conditions

This fix ensures that user interactions are never blocked by optimization logic, while preserving all performance benefits for sync operations.

🤖 Generated with Claude Code

…izations

When optimistic updates back up with slow sync responses, rapid user actions
on the same key would get filtered out by recentlySyncedKeys logic, causing
UI to stop responding. This change distinguishes between user-triggered and
sync-triggered recomputations to prevent blocking legitimate user actions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@changeset-bot
Copy link

changeset-bot bot commented Jul 23, 2025

🦋 Changeset detected

Latest commit: 5340138

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@tanstack/db Patch
@tanstack/electric-db-collection Patch
@tanstack/query-db-collection Patch
@tanstack/react-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db Patch
@tanstack/db-example-react-todo Patch

Not sure what this means? Click here to learn what changesets are.

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jul 23, 2025

@tanstack/db-example-react-todo

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@308

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@308

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@308

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@308

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@308

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@308

commit: 5340138

@github-actions
Copy link
Contributor

github-actions bot commented Jul 23, 2025

Size Change: +40 B (+0.07%)

Total Size: 58.1 kB

Filename Size Change
./packages/db/dist/esm/collection.js 9.74 kB +40 B (+0.41%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/change-events.js 1.12 kB
./packages/db/dist/esm/deferred.js 230 B
./packages/db/dist/esm/errors.js 2.98 kB
./packages/db/dist/esm/index.js 1.52 kB
./packages/db/dist/esm/indexes/auto-index.js 689 B
./packages/db/dist/esm/indexes/base-index.js 605 B
./packages/db/dist/esm/indexes/btree-index.js 1.47 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.25 kB
./packages/db/dist/esm/local-only.js 827 B
./packages/db/dist/esm/local-storage.js 2.03 kB
./packages/db/dist/esm/optimistic-action.js 294 B
./packages/db/dist/esm/proxy.js 4.19 kB
./packages/db/dist/esm/query/builder/functions.js 575 B
./packages/db/dist/esm/query/builder/index.js 3.67 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 890 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.48 kB
./packages/db/dist/esm/query/compiler/expressions.js 631 B
./packages/db/dist/esm/query/compiler/group-by.js 2.04 kB
./packages/db/dist/esm/query/compiler/index.js 1.74 kB
./packages/db/dist/esm/query/compiler/joins.js 1.56 kB
./packages/db/dist/esm/query/compiler/order-by.js 705 B
./packages/db/dist/esm/query/compiler/select.js 657 B
./packages/db/dist/esm/query/ir.js 318 B
./packages/db/dist/esm/query/live-query-collection.js 2.45 kB
./packages/db/dist/esm/query/optimizer.js 2.44 kB
./packages/db/dist/esm/SortedMap.js 1.24 kB
./packages/db/dist/esm/transactions.js 2.28 kB
./packages/db/dist/esm/utils.js 419 B
./packages/db/dist/esm/utils/btree.js 5.93 kB
./packages/db/dist/esm/utils/comparison.js 539 B
./packages/db/dist/esm/utils/index-optimization.js 1.62 kB

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Jul 23, 2025

Size Change: 0 B

Total Size: 1.05 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 152 B
./packages/react-db/dist/esm/useLiveQuery.js 902 B

compressed-size-action::react-db-package-size

KyleAMathews and others added 3 commits July 23, 2025 10:14
…atching

This fix resolves a critical issue where rapid user interactions (like clicking
multiple checkboxes) would cause the UI to become unresponsive when optimistic
updates backed up with slow sync responses.

## Root Cause Analysis
The issue had two main components:

1. **Event Filtering Problem**: User actions on recently synced keys were being
   filtered out by `recentlySyncedKeys` logic, which was designed to prevent
   duplicate sync events but was incorrectly blocking legitimate user actions.

2. **Batching Problem**: Once sync transactions started, `shouldBatchEvents`
   would become true, causing all subsequent events (including user actions) to
   be batched instead of immediately updating the UI, making it appear frozen.

## Solution
1. **Added `triggeredByUserAction` parameter** to `recomputeOptimisticState()`
   to distinguish between user-initiated and sync-initiated recomputations

2. **Modified event filtering** to allow user actions through even when keys
   are in `recentlySyncedKeys`

3. **Updated batching logic** so user actions bypass batching via `forceEmit=true`,
   ensuring immediate UI updates while still batching sync-related events for performance

4. **Fixed all transaction creation paths** to pass `triggeredByUserAction=true`
   for direct user operations (insert, update, delete)

## Key Changes
- Modified `recomputeOptimisticState()` to accept `triggeredByUserAction` parameter
- Updated sync status filtering to allow user actions through
- Modified complex filtering to skip when `triggeredByUserAction=true`
- Updated `emitEvents()` to accept `forceEmit` parameter for user actions
- Fixed direct operation transaction calls to pass correct parameters
- Added comprehensive test case to prevent regression

## Test Coverage
- New test case reproduces the original issue and verifies the fix
- Test confirms rapid user actions on recently synced keys are not blocked
- Existing tests continue to pass

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@KyleAMathews KyleAMathews requested a review from samwillis July 23, 2025 16:33
KyleAMathews and others added 2 commits July 23, 2025 10:38
- Document UI responsiveness fix for rapid user interactions
- Explain root causes: event filtering and batching issues
- Detail solution: triggeredByUserAction parameter and forceEmit logic

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Collaborator

@samwillis samwillis left a comment

Choose a reason for hiding this comment

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

LGTM, let's get this out but keep an eye out for side effects.

@KyleAMathews
Copy link
Collaborator Author

Side effects like excessive renders?

@KyleAMathews KyleAMathews merged commit 3e9a36d into main Jul 23, 2025
5 checks passed
@KyleAMathews KyleAMathews deleted the fix/user-action-filtering-bug branch July 23, 2025 18:55
@github-actions github-actions bot mentioned this pull request Jul 23, 2025
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