Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Dec 30, 2025

Summary

Live queries in on-demand sync mode would return isReady=true with empty data, or release suspense prematurely. Users see a flash of empty state before data appears.

Root Cause

Race condition in CollectionSubscriber: the status:change listener was registered after the snapshot was triggered. If loadSubset resolved quickly, the loadingSubsetready transition was missed, so trackLoadPromise was never called on the live query collection.

Approach

1. Deferred snapshot triggering (core fix)

1. Create subscription with deferSnapshot: true
2. Register status listener  
3. THEN trigger snapshot

2. Ready state gating (defense in depth)

if (this.allCollectionsReady() && !this.liveQueryCollection?.isLoadingSubset) {
  markReady()
}

Key invariant

The status listener must be registered before any async work starts. This is now enforced by the deferSnapshot option.

Non-goals

  • No changes to useLiveSuspenseQuery hook. The fix at the collection level is sufficient—suspense naturally holds because status !== 'ready' while loading.

Trade-offs

  • Adds deferSnapshot to public API surface, but it's an internal option not meant for external use
  • Slightly more complex subscription flow, but eliminates a class of timing bugs

Verification

pnpm --filter "@tanstack/db" --filter "@tanstack/react-db" test

New test: should not mark live query ready while isLoadingSubset is true

Files changed

  • packages/db/src/collection/changes.ts - Add deferSnapshot option
  • packages/db/src/query/live/collection-config-builder.ts - Ready gating + loadingSubset:change listener
  • packages/db/src/query/live/collection-subscriber.ts - Defer snapshot triggering
  • packages/db/src/types.ts - Add deferSnapshot type

@changeset-bot
Copy link

changeset-bot bot commented Dec 30, 2025

🦋 Changeset detected

Latest commit: 5c3ab10

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

This PR includes changesets to release 12 packages
Name Type
@tanstack/db Patch
@tanstack/angular-db Patch
@tanstack/electric-db-collection Patch
@tanstack/offline-transactions Patch
@tanstack/powersync-db-collection Patch
@tanstack/query-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db 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 Dec 30, 2025

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1081

@tanstack/db

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

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1081

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1081

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1081

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1081

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1081

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1081

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 5c3ab10

@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

Size Change: +88 B (+0.1%)

Total Size: 90.2 kB

Filename Size Change
./packages/db/dist/esm/collection/changes.js 1.19 kB +22 B (+1.88%)
./packages/db/dist/esm/query/live/collection-config-builder.js 5.35 kB +29 B (+0.54%)
./packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB +37 B (+1.95%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 kB
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.46 kB
./packages/db/dist/esm/collection/subscription.js 3.62 kB
./packages/db/dist/esm/collection/sync.js 2.38 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.49 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.69 kB
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.93 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 4.01 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 917 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.35 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.8 kB
./packages/db/dist/esm/query/compiler/index.js 1.96 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.46 kB
./packages/db/dist/esm/query/compiler/select.js 1.07 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/internal.js 130 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.3 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 924 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 852 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

Size Change: 0 B

Total Size: 3.47 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.12 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

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

@marbemac
Copy link
Contributor

Doesn't seem to resolve the issue unfortunately.

Also, I'm not sure this is specific to suspense? For example if I have the below with regular useLiveQuery, sometimes isReady=true without any data, and sometimes after there is data.

const { data, isReady, state, } = useLiveQuery(
  q =>
    q
      .from({ w: workflowRunsCollection })
      .where(({ w }) => eq(w.id, 'some_id'))
      .findOne(),
  [],
);

console.log('run', { haveData: !!data, state, isReady });

I've stripped our app down to this one query, and reload the page, and see similar symptom to the suspense version (albeit using isReady as the boundary versus suspense):

Screenshot 2025-12-30 at 5 48 14 PM

@marbemac
Copy link
Contributor

Out of curiosity, I tried the latest from here and unfortunately the problem persists. So I had Claude add a bunch of debug logs to spots that might be relevant, then reloaded our app, and fed the logs back into claude for diagnosis. Here are the resulting findings from Claude in case helpful (including the debug logs from the actual reproduction):


Debug Analysis: isReady=true before data is loaded

Issue Summary

In on-demand sync mode with Electric, useLiveQuery returns isReady=true before the query data has been written to the live query collection. This causes:

  • useLiveQuery to return isReady=true with empty data
  • useLiveSuspenseQuery to release suspense prematurely

Reproduction

function Debug() {
  const { data, isReady, state } = useLiveQuery(
    q =>
      q
        .from({ w: workflowRunsCollection })
        .where(({ w }) => eq(w.id, 'wrun_eco6todnlmxfvg'))
        .findOne(),
    [],
  );

  console.log('run', { haveData: !!data, state, isReady });

  return <div>Debug</div>;
}

Console output on page load:

run {haveData: false, state: Map(0), isReady: false}
run {haveData: false, state: Map(0), isReady: true}   ← BUG: isReady=true but no data
run {haveData: true, state: Map(1), isReady: true}

Debug Logs

Added console.logs to trace the flow. Full output:

[DEBUG allCollectionsReady] id=live-query-1
  statuses: [{id: 'workflowRuns', status: 'loading', isReady: false}]

[DEBUG updateLiveQueryStatus] id=live-query-1
  {allCollectionsReady: false, isLoadingSubset: false, currentStatus: 'loading', willMarkReady: false}

[DEBUG trackLoadPromise] id=workflowRuns
  {loadingStarting: true, pendingCount: 1}

[DEBUG CollectionSubscriber initial status check] alias=w
  {subscriptionStatus: 'ready'}                        ← Already 'ready'!

[DEBUG allCollectionsReady] id=live-query-1
  statuses: [{id: 'workflowRuns', status: 'loading', isReady: false}]

[DEBUG updateLiveQueryStatus] id=live-query-1
  {allCollectionsReady: false, isLoadingSubset: false, currentStatus: 'loading', willMarkReady: false}

run {haveData: false, state: Map(0), isReady: false}   ← Correct initial state

[DEBUG markReady] id=workflowRuns
  {currentStatus: 'loading'}                           ← Source collection becomes ready

[DEBUG allCollectionsReady] id=live-query-1
  statuses: [{id: 'workflowRuns', status: 'ready', isReady: true}]

[DEBUG updateLiveQueryStatus] id=live-query-1
  {allCollectionsReady: true, isLoadingSubset: false, currentStatus: 'loading', willMarkReady: true}
                                                       ↑ isLoadingSubset=false, so it marks ready!

[DEBUG markReady] id=live-query-1
  {currentStatus: 'loading'}                           ← Live query marked ready prematurely

run {haveData: false, state: Map(0), isReady: true}    ← BUG! isReady=true but no data

[DEBUG markReady] id=workflowRuns
  {currentStatus: 'ready'}

run {haveData: true, state: Map(1), isReady: true}     ← Data arrives later

[DEBUG trackLoadPromise.finally] id=workflowRuns
  {loadingEnding: true, pendingCount: 0}               ← Loading finishes after ready was set

Root Cause Analysis

The fix in this branch added a check for isLoadingSubset on the live query collection:

// collection-config-builder.ts
if (this.allCollectionsReady() && !this.liveQueryCollection?.isLoadingSubset) {
  markReady()
}

However, isLoadingSubset is always false on the live query collection because:

  1. trackLoadPromise is called on the source collection (workflowRuns), not the live query collection (live-query-1)

  2. The Electric subscription status is already 'ready' when CollectionSubscriber checks it at line 120-124:

    // collection-subscriber.ts
    if (subscription.status === `loadingSubset`) {
      trackLoadPromise()  // Never called because status is 'ready'
    }
  3. Therefore, trackLoadPromise() is never called on the live query collection

  4. The live query's isLoadingSubset remains false

  5. When the source collection becomes ready, the condition allCollectionsReady() && !isLoadingSubset is satisfied, and the live query is marked ready before its data is populated

Timeline

1. trackLoadPromise() called on workflowRuns (source) - starts loading
2. Electric subscription.status = 'ready' immediately
3. CollectionSubscriber checks status, sees 'ready', doesn't track load on live query
4. workflowRuns.markReady() - source collection ready
5. updateLiveQueryStatus() checks:
   - allCollectionsReady() = true (workflowRuns is ready)
   - isLoadingSubset = false (nothing tracked on live query)
   → Marks live-query-1 ready!
6. React renders: isReady=true, data=empty  ← BUG
7. Data is written to live query collection
8. React renders: isReady=true, data=populated
9. trackLoadPromise.finally() on workflowRuns - loading done

Potential Fix Directions

  1. Track loading on live query collection: When the source collection starts loading subset data, also track that loading on the live query collection that depends on it

  2. Investigate Electric subscription status: Why is subscription.status already 'ready' when data hasn't been loaded yet? This might be a bug in the Electric integration layer or Electric itself

  3. Use source collection's isLoadingSubset: Instead of (or in addition to) checking the live query's isLoadingSubset, check if any source collection has isLoadingSubset=true

Files Involved

  • packages/db/src/query/live/collection-config-builder.ts - updateLiveQueryStatus() and allCollectionsReady()
  • packages/db/src/query/live/collection-subscriber.ts - Electric subscription status tracking
  • packages/db/src/collection/sync.ts - trackLoadPromise() and isLoadingSubset
  • packages/db/src/collection/lifecycle.ts - markReady()

@marbemac
Copy link
Contributor

That changes since my last comment fixed it! There are several adjustments in this PR, unsure which one(s) are the actual fixes, but something in the last couple of commits finally resolved it. Might want to figure out a test that fails before the last couple of commits, and succeeds with the changes from the last couple of commits.

claude and others added 15 commits January 6, 2026 09:25
…in on-demand mode

When using useLiveSuspenseQuery with on-demand sync mode, the suspense
boundary would sometimes release before the query's data was actually
loaded. This happened because the live query collection was marked as
ready immediately when the source collection was already ready, even
though the loadSubset operation for the specific query hadn't completed.

This fix ensures that useLiveSuspenseQuery also suspends while
isLoadingSubset is true, waiting for the initial subset load to complete
before releasing the suspense boundary.
This test verifies that useLiveSuspenseQuery holds the suspense boundary
when isLoadingSubset is true, even if the collection status is 'ready'.

The test confirms:
1. WITHOUT the fix: suspense releases prematurely (test fails)
2. WITH the fix: suspense waits for isLoadingSubset to be false (test passes)
…a is loaded

In on-demand sync mode, the live query collection was being marked as 'ready'
before the subset data finished loading. This caused useLiveQuery to return
isReady=true with empty data, and useLiveSuspenseQuery to release suspense
prematurely.

The fix:
1. Added isLoadingSubset check in updateLiveQueryStatus() to prevent marking
   ready while subset is loading
2. Added listener for loadingSubset:change events to trigger ready check
   when subset loading completes
3. Added test case that verifies the correct timing behavior
…race condition

The loadingSubset:change listener was registered after subscribeToAllCollections(),
which could cause a race condition where the event fires before the listener is
registered. This resulted in the live query never becoming ready.

Also adds await in electric test to account for async subset loading.
Register the status:change listener BEFORE checking the current
subscription status to avoid missing status transitions.

Previously, if loadSubset completed very quickly, the status could
change from 'loadingSubset' to 'ready' between checking the status
and registering the listener, causing the tracked promise to never
resolve and the live query to never become ready.
…ery's

The previous fix incorrectly checked isLoadingSubset on the live query
collection itself, but the loadSubset/trackLoadPromise mechanism runs on
SOURCE collections during on-demand sync, so the live query's isLoadingSubset
was always false.

This fix:
- Adds anySourceCollectionLoadingSubset() to check if any source collection
  has isLoadingSubset=true
- Listens for loadingSubset:change events on source collections instead of
  the live query collection
…rce collections

Reverts the change to check source collections' isLoadingSubset, which was causing
test timeouts in query-db-collection tests. The live query collection's isLoadingSubset
is correctly updated by CollectionSubscriber.trackLoadPromise() which tracks loading
on the live query collection itself.

Also updates changeset to accurately describe the fix.
…r snapshot trigger

The subscription's status:change listener was being registered AFTER the snapshot
was triggered (via requestSnapshot/requestLimitedSnapshot). This meant that if the
loadSubset promise resolved quickly (or synchronously), the status transition from
'loadingSubset' to 'ready' could be missed entirely.

Changes:
- Refactored subscribeToChanges() to split subscription creation from snapshot triggering
- subscribeToMatchingChanges() and subscribeToOrderedChanges() now return both the
  subscription AND a triggerSnapshot function
- The status listener is registered AFTER getting the subscription but BEFORE calling
  triggerSnapshot()
- Added deferSnapshot option to subscribeChanges() to prevent automatic snapshot request
- For non-ordered queries, continue using trackLoadSubsetPromise: false to maintain
  compatibility with query-db-collection's destroyed observer handling
- Updated test for source collection isLoadingSubset independence
- Added regression test for the race condition fix
@KyleAMathews KyleAMathews force-pushed the claude/fix-on-demand-suspense-HKGki branch from 12e4d74 to 2ae80a2 Compare January 6, 2026 16:26
@KyleAMathews KyleAMathews changed the title Investigate on-demand mode suspense issue in on-demand mode fix: prevent on-demand suspense from releasing before data loads Jan 6, 2026
@kevin-dp kevin-dp moved this from In Progress to Ready for review in TanStack DB 1.0.0 release Jan 7, 2026
@KyleAMathews KyleAMathews changed the title fix: prevent on-demand suspense from releasing before data loads fix(db): prevent on-demand live query from being marked ready before data loads Jan 7, 2026
KyleAMathews and others added 3 commits January 7, 2026 09:22
- Remove useLiveSuspenseQuery changes (not needed with core fix)
- Remove artificial test that manually sets isLoadingSubset after ready
- Update changeset to accurately describe the fix
- Remove react-db changeset (no react-db source changes)

The core fix (deferSnapshot + ready gating) is sufficient. The suspense
hook doesn't need additional isLoadingSubset checks because the live
query collection won't be marked ready while isLoadingSubset is true.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests the tricky case where loadSubset returns Promise.resolve() immediately.
This proves the race condition fix works even when the status transition
happens synchronously, not just with delayed promises.

Addresses reviewer feedback to strengthen test coverage.

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

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

kevin-dp commented Jan 8, 2026

@KyleAMathews I find the new flow a bit cumbersome and error-prone since the caller has to remember to defer the snapshot, register the listener, then trigger the snapshot. I'd prefer if we replace the deferSnapshot option by a onStatusChange option and let subscribeChanges do the magic.

I asked Cursor what it thinks about this and it agreed and came up with this more detailed plan:

Analyzing the PR and evaluating the alternative. Searching for deferSnapshot and reviewing the relevant code.

Proposed Solution Analysis

Passing the status listener as an option to subscribeChanges is better because:

  1. Atomic registration: the listener is registered before any snapshot is requested, avoiding missed events.
  2. Simpler API: no need for deferSnapshot and manual snapshot triggering.
  3. Type safety: the listener can be part of the options interface.
  4. Less error-prone: no manual sequencing of create → register → trigger.

Recommended Implementation

Here's how the API could look:

export interface SubscribeChangesOptions {
  /** Whether to include the current state as initial changes */
  includeInitialState?: boolean
  /** Pre-compiled expression for filtering changes */
  whereExpression?: BasicExpression<boolean>
  /** Listener for subscription status changes - registered before snapshot is requested */
  onStatusChange?: (event: SubscriptionStatusChangeEvent) => void
}

Then in subscribeChanges:

public subscribeChanges(
  callback: (changes: Array<ChangeMessage<TOutput>>) => void,
  options: SubscribeChangesOptions = {}
): CollectionSubscription {
  this.addSubscriber()

  const subscription = new CollectionSubscription(this.collection, callback, {
    ...options,
    onUnsubscribe: () => {
      this.removeSubscriber()
      this.changeSubscriptions.delete(subscription)
    },
  })

  // Register status listener BEFORE requesting snapshot to avoid race condition
  if (options.onStatusChange) {
    subscription.on(`status:change`, options.onStatusChange)
  }

  if (options.includeInitialState) {
    subscription.requestSnapshot({ trackLoadSubsetPromise: false })
  }

  this.changeSubscriptions.add(subscription)
  return subscription
}

This ensures:

  • The listener is registered before any status changes occur
  • No need for deferSnapshot
  • Cleaner, more intuitive API
  • Backward compatible (the option is optional)

This approach is better than introducing deferSnapshot because it solves the root cause (listener registration timing) rather than requiring manual coordination.

@KyleAMathews
Copy link
Collaborator Author

@kevin-dp implemented

KyleAMathews and others added 2 commits January 8, 2026 15:57
Simplify the subscription API by replacing the error-prone 3-step
deferSnapshot pattern with a cleaner onStatusChange callback option.

The listener is now registered internally BEFORE any snapshot is
requested, guaranteeing no status transitions are missed regardless
of how quickly loadSubset resolves.

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

@KyleAMathews KyleAMathews left a comment

Choose a reason for hiding this comment

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

Code Review: PR #1081

Overall Assessment: Approve

This is a solid bug fix that addresses a subtle but impactful race condition. Like the importance of covenant timing in LDS doctrine, the order of operations here matters greatly - and this PR gets it right.

Summary

The PR fixes a race condition where live queries in on-demand sync mode would return isReady=true with empty data. The root cause was that the status:change listener was registered after the snapshot was triggered, potentially missing the loadingSubsetready transition.

The Fix

Two-pronged approach:

  1. onStatusChange callback option - Passed to subscribeChanges() and registered BEFORE any snapshot is requested:

    // changes.ts
    if (options.onStatusChange) {
      subscription.on(`status:change`, options.onStatusChange)
    }
    // THEN trigger snapshot
    if (options.includeInitialState) {
      subscription.requestSnapshot({ trackLoadSubsetPromise: false })
    }
  2. Ready state gating - Extra check in updateLiveQueryStatus():

    if (this.allCollectionsReady() && !this.liveQueryCollection?.isLoadingSubset) {
      markReady()
    }

Strengths

  1. Correct ordering: The key insight is ensuring listener registration happens before any async work that could trigger status changes.

  2. Defense in depth: Both the onStatusChange pattern and the isLoadingSubset check provide redundant safety.

  3. Excellent test coverage: Tests specifically target:

    • Delayed loadSubset resolution
    • Synchronously resolving loadSubset - the trickiest case
    • Race condition verification
  4. loadingSubset:change listener: The listener on the live query collection (collection-config-builder.ts:573-581) ensures we catch the transition even if it happens after allCollectionsReady() returns true.

Minor Notes

  1. subscriptionHolder pattern (collection-subscriber.ts:229-232): Using a holder object to forward-reference the subscription in callbacks is a good pattern for this circular reference scenario.

  2. Test in electric-live-query.test.ts: The added await setTimeout(0) is a good fix - it accounts for the now-async nature of subset loading completion.

  3. Documentation: The changeset clearly explains the root cause and fix.

Question

The onStatusChange option is marked @internal in the types. Is there value in documenting a recommended pattern for external consumers who might build similar lazy-loading features? Or is this purely an internal concern?

Great bug fix with thorough testing! 🏁

@KyleAMathews KyleAMathews merged commit c1247e8 into main Jan 9, 2026
7 checks passed
@KyleAMathews KyleAMathews deleted the claude/fix-on-demand-suspense-HKGki branch January 9, 2026 16:35
@github-actions github-actions bot mentioned this pull request Jan 9, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

🎉 This PR has been released!

Thank you for your contribution!

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

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

5 participants