Conversation
🦋 Changeset detectedLatest commit: a10c9f4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
Benchmark Spread
Details
| Benchmark suite | Current: a10c9f4 | Previous: 38dbb52 | Ratio |
|---|---|---|---|
setOneEntity in 10k entity store |
154 ops/sec (±1.05%) |
153 ops/sec (±0.97%) |
0.99 |
This comment was automatically generated by workflow using github-action-benchmark.
There was a problem hiding this comment.
Benchmark React
Details
| Benchmark suite | Current: a10c9f4 | Previous: a76996a | Ratio |
|---|---|---|---|
data-client: getlist-100 |
138.89 ops/s (± 4.1%) |
125 ops/s (± 4.8%) |
0.90 |
data-client: getlist-500 |
46.62 ops/s (± 3.4%) |
39.84 ops/s (± 5.5%) |
0.85 |
data-client: update-entity |
370.37 ops/s (± 8.0%) |
312.5 ops/s (± 7.2%) |
0.84 |
data-client: update-user |
357.14 ops/s (± 9.0%) |
294.12 ops/s (± 7.8%) |
0.82 |
data-client: getlist-500-sorted |
48.31 ops/s (± 7.3%) |
43.39 ops/s (± 9.2%) |
0.90 |
data-client: update-entity-sorted |
322.58 ops/s (± 6.3%) |
263.16 ops/s (± 5.9%) |
0.82 |
data-client: update-entity-multi-view |
350.99 ops/s (± 8.0%) |
277.78 ops/s (± 7.0%) |
0.79 |
data-client: list-detail-switch-10 |
9.26 ops/s (± 9.2%) |
7.22 ops/s (± 5.2%) |
0.78 |
data-client: update-user-10000 |
99.5 ops/s (± 10.0%) |
71.43 ops/s (± 12.4%) |
0.72 |
data-client: invalidate-and-resolve |
40.65 ops/s (± 4.9%) |
32.95 ops/s (± 5.6%) |
0.81 |
data-client: unshift-item |
224.75 ops/s (± 7.7%) |
188.68 ops/s (± 5.9%) |
0.84 |
data-client: delete-item |
303.03 ops/s (± 4.1%) |
250 ops/s (± 4.4%) |
0.83 |
data-client: move-item |
196.08 ops/s (± 8.4%) |
158.77 ops/s (± 8.3%) |
0.81 |
This comment was automatically generated by workflow using github-action-benchmark.
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: a10c9f4 | Previous: 93555f8 | Ratio |
|---|---|---|---|
normalizeLong |
432 ops/sec (±4.64%) |
447 ops/sec (±4.75%) |
1.03 |
normalizeLong Values |
392 ops/sec (±1.02%) |
408 ops/sec (±1.51%) |
1.04 |
normalizeLong Scalar |
372 ops/sec (±3.49%) |
352 ops/sec (±3.69%) |
0.95 |
normalizeLong Scalar update |
889 ops/sec (±0.19%) |
895 ops/sec (±0.68%) |
1.01 |
denormalizeLong |
234 ops/sec (±5.81%) |
233 ops/sec (±6.00%) |
1.00 |
denormalizeLong Values |
224 ops/sec (±5.09%) |
213 ops/sec (±4.94%) |
0.95 |
denormalizeLong donotcache |
966 ops/sec (±0.73%) |
1002 ops/sec (±0.64%) |
1.04 |
denormalizeLong Values donotcache |
741 ops/sec (±0.37%) |
737 ops/sec (±0.59%) |
0.99 |
denormalizeLong Scalar donotcache |
1019 ops/sec (±0.34%) |
1073 ops/sec (±0.13%) |
1.05 |
denormalizeShort donotcache 500x |
1376 ops/sec (±0.14%) |
1437 ops/sec (±0.29%) |
1.04 |
denormalizeShort 500x |
630 ops/sec (±6.63%) |
639 ops/sec (±6.97%) |
1.01 |
denormalizeShort 500x withCache |
6848 ops/sec (±0.18%) |
6834 ops/sec (±5.52%) |
1.00 |
queryShort 500x withCache |
3081 ops/sec (±0.11%) |
3206 ops/sec (±0.97%) |
1.04 |
buildQueryKey All |
58838 ops/sec (±1.13%) |
58478 ops/sec (±1.39%) |
0.99 |
query All withCache |
6120 ops/sec (±2.05%) |
5828 ops/sec (±2.46%) |
0.95 |
denormalizeLong with mixin Entity |
205 ops/sec (±6.69%) |
209 ops/sec (±7.50%) |
1.02 |
denormalizeLong withCache |
6556 ops/sec (±0.21%) |
7517 ops/sec (±0.32%) |
1.15 |
denormalizeLong withCache (Scalar churn) |
6512 ops/sec (±0.90%) |
7491 ops/sec (±0.24%) |
1.15 |
denormalizeLong Values withCache |
5113 ops/sec (±2.03%) |
5132 ops/sec (±1.60%) |
1.00 |
denormalizeLong Scalar withCache |
7750 ops/sec (±0.63%) |
7648 ops/sec (±0.98%) |
0.99 |
denormalizeLong Scalar update withCache |
4085 ops/sec (±0.30%) |
4074 ops/sec (±0.24%) |
1.00 |
denormalizeLong All withCache |
6221 ops/sec (±0.23%) |
6058 ops/sec (±0.18%) |
0.97 |
denormalizeLong Query-sorted withCache |
6324 ops/sec (±1.63%) |
6098 ops/sec (±1.48%) |
0.96 |
denormalizeLongAndShort withEntityCacheOnly |
1688 ops/sec (±0.26%) |
1748 ops/sec (±0.19%) |
1.04 |
denormalize bidirectional 50 |
4546 ops/sec (±10.52%) |
4498 ops/sec (±10.41%) |
0.99 |
denormalize bidirectional 50 donotcache |
40892 ops/sec (±0.23%) |
42385 ops/sec (±1.43%) |
1.04 |
getResponse |
4442 ops/sec (±3.88%) |
4418 ops/sec (±4.05%) |
0.99 |
getResponse (null) |
9825777 ops/sec (±0.62%) |
10236651 ops/sec (±0.70%) |
1.04 |
getResponse (clear cache) |
203 ops/sec (±6.21%) |
203 ops/sec (±7.07%) |
1 |
getSmallResponse |
3383 ops/sec (±1.19%) |
3543 ops/sec (±0.24%) |
1.05 |
getSmallInferredResponse |
2852 ops/sec (±0.24%) |
2852 ops/sec (±1.79%) |
1 |
getResponse Collection |
4405 ops/sec (±1.98%) |
4306 ops/sec (±4.05%) |
0.98 |
get Collection |
3076 ops/sec (±0.32%) |
2707 ops/sec (±0.19%) |
0.88 |
get Query-sorted |
4993 ops/sec (±1.76%) |
5052 ops/sec (±1.47%) |
1.01 |
setLong |
434 ops/sec (±0.32%) |
467 ops/sec (±0.60%) |
1.08 |
setLongWithMerge |
246 ops/sec (±1.09%) |
257 ops/sec (±0.45%) |
1.04 |
setLongWithSimpleMerge |
260 ops/sec (±0.36%) |
272 ops/sec (±0.80%) |
1.05 |
setSmallResponse 500x |
906 ops/sec (±1.41%) |
926 ops/sec (±1.48%) |
1.02 |
This comment was automatically generated by workflow using github-action-benchmark.
|
CHANGE_THIS_PR The streaming handoff design is the right shape for this problem — baseline + deltas via What blocks Ready (adversarial pass kept these):
Not asking in this PR: managers factory (array was already unused for the server store; warn + factory is an improvement), soft-nav / Draft is fine; Ready needs green |
|
Size Change: +70 B (+0.09%) Total Size: 81.1 kB 📦 View Changed
ℹ️ View Unchanged
|
|
CHANGE_THIS_PR (updated for Prior note raced this push. Cleared on this head:
Still blocks Ready (adversarial pass kept these; CircleCI
Not asking: soft-nav streaming, Express 10ms path, CodeQL on the double-stringify / test regexes (false positives), website GHA Draft is fine; Ready needs green |
|
CHANGE_THIS_PR (updated for
Still blocks merge (adversarial pass kept these; CircleCI
Not asking: soft-nav streaming, Express 10ms path, CodeQL / GHA website typecheck (unrelated; CircleCI typecheck green), or redesign of overlay/merge/ Ready needs green |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4090 +/- ##
==========================================
- Coverage 97.84% 97.58% -0.26%
==========================================
Files 156 168 +12
Lines 3057 3229 +172
Branches 612 658 +46
==========================================
+ Hits 2991 3151 +160
- Misses 18 21 +3
- Partials 48 57 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Staff LGTM (updated for Prior matrix CHANGE_THIS_PR items are cleared on this head:
Adversarial pass on the R18 re-spec: KILL as CHANGE_THIS_PR — honest React 18 hydration scheduling, not a missing product path. Optional FOLLOW_UP only if we ever need stronger R18 guarantees around reset-during-streaming vs remount/snapshot fold — not this PR. Hold squash for Bugbot success. GHA “Typecheck website” / CodeQL remain the prior unrelated / false-positive set; CircleCI typecheck is green. No further changes requested on this PR. |
Staff review — LGTM / FOLLOW_UP only @
|
|
CHANGE_THIS_PR (updated for Prior Staff LGTM at Blocks Ready
Not asking: stale “unchanged” wording in Ready needs green |
114d7e5 to
f123190
Compare
|
Staff LGTM (updated after force-push back to
CHANGE_THIS_PR cleared
Prior LGTM at
Hold squash for
Optional FOLLOW_UP only (not this PR): browser No further changes requested on this PR. |
|
CHANGE_THIS_PR (updated for Prior Staff LGTM at Blocks Ready
Not asking: GHA website Ready needs green |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d7ecc8c. Configure here.
d7ecc8c to
f123190
Compare
|
Staff LGTM (updated after second force-push back to
CHANGE_THIS_PR cleared
Prior LGTM at
Hold squash for
Optional FOLLOW_UP only (not this PR): browser No further changes requested on this PR. |
|
CHANGE_THIS_PR (updated for Prior Staff LGTM at Blocks Ready
Not asking: GHA website Ready needs green |
|
Staff LGTM (updated for Prior CHANGE_THIS_PR at
Prior LGTM still stands for the streaming/hydration design (baseline + deltas, three-way Hold squash for
No further changes requested on this PR. Optional FOLLOW_UP only if we later want stronger R18 remount-from-snapshot guarantees — not this PR. |
|
Staff LGTM (updated for Prior CHANGE_THIS_PR at
No CHANGE_THIS_PR. Architecture LGTM unchanged: baseline + deltas, three-way Hold squash for
GHA No further changes requested on this PR. Optional FOLLOW_UP only if we later want stronger R18 remount-from-snapshot guarantees — not this PR. |
|
CHANGE_THIS_PR — real Next 16 consumer validation found a client handoff race on I tested packed artifacts from this exact head in the order-book app that originally reproduced the 10 ms bug:
Server side passes: production build is clean; 8 concurrent mixed-symbol requests each returned 87 data cells and a route-isolated cache. The baseline is the 101-byte initial state and 3–5 streamed deltas reconstruct the expected Client side does not meet the zero-refetch claim on this head. On a clean hard navigation, browser
(The later direct depth request from this app's Binance sequence synchronizer is expected and separate.) No hydration warning was required to trigger this; useful SSR HTML remained visible, but the Data Client requests were duplicated. The raw response ordering is correct: each delta script precedes its dependent Fizz HTML. The failing assumption is that this also precedes Client Component execution. In Next 16, Flight can start that client work before the inserted HTML delta script executes and before I validated the conservative fix below: const element = document.getElementById(BASELINE_ID);
-if (!element && document.readyState === 'loading') {
+if (document.readyState === 'loading') {
throw (queue.pending ??= new Promise<void>(resolve => {
- // whichever fires first wins; the loser must not clobber the receiver
const done = () => {
document.removeEventListener('DOMContentLoaded', done);
- if (queue.onDelta === done) queue.onDelta = undefined;
queue.pending = undefined;
resolve();
};
document.addEventListener('DOMContentLoaded', done);
- queue.onDelta = done;
}));
}In other words: while the initial document is still parsing, wait through all streamed deltas; do not let the first delta end bootstrap. At With only that runtime refinement:
The regression test needs the baseline installed before If preserving progressive interactivity is required, the alternative is a larger endpoint-aware client wait: missing I would not merge the current head with the zero-client-request claim unchanged. The |
…ive state useCacheState now uses useSyncExternalStore's getServerSnapshot in the browser so a Suspense boundary that hydrates after the store has already changed (manager updates, streamed data) still matches the server HTML. Updates keep flowing through StateContext, so transitions remain non-blocking. DataProvider provides the snapshot through the internal ServerSnapshotContext; React 16/17 and React Native keep the plain context read. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…et window The Next.js DataProvider decided the server store was complete after a fixed 10ms without in-flight fetches, so any async Server Component that resolved later left #data-client-data empty and the client refetched everything it had just received as HTML. The provider now registers useServerInsertedHTML: the shell carries the state so far as an inert JSON baseline and every later flush prepends a delta script describing what changed since. The client folds these into the hydration snapshot and merges them into the live store (HYDRATE) as they arrive, so each Suspense boundary hydrates against exactly what it was rendered from. Also: nonce prop for CSP, managers accepted as a factory so each request gets its own instances (arrays are ignored on the server with a warning), and a local next/navigation declaration under typings/ that is not shipped. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…gnored typings/ directory Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…er streaming hydration in jsdom A boundary whose data the server never sent (a delta lost to a serialization failure, or arriving after its boundary hydrated) would suspend on every hydration retry because the snapshot alone never gains that data. The hydration view now overlays the server snapshot on the live store, so such boundaries hydrate after exactly one client fetch while everything the server did send still wins. Adds jsdom coverage for both the plain DataProvider (late boundary hydrates against server state, StrictMode, transitions, legacy branch) and the Next.js client (deltas streaming into dehydrated boundaries, queued deltas, client reset, missing baseline, document still loading, managers factory). Test fixtures under __tests__/fixtures are no longer collected as suites. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…e and limitations Also exports NextDataProviderProps, lists react-dom as an optional peer for the nextjs entry, and makes the Next.js example exercise a Server Component that resolves after the shell. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…caping Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Manager instances cannot be shared between server requests, so the array form only ever applied to the browser while the server kept its defaults. The prop now takes a function, called once per request on the server and once in the browser; an array throws with a migration hint. BREAKING CHANGE: @data-client/react/nextjs DataProvider managers prop changes from Manager[] to () => Manager[]. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…kfile The nextjs managers change ships as a minor, so @data-client/img and @data-client/test must accept 0.19.x; also records the optional react-dom peer in yarn.lock. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
- a delta that ends the baseline wait before DOMContentLoaded no longer lets the stale load listener clear the receiver installed later; the pending wait is cleared once settled - the client managers factory result is cached on the per-document snapshot store so StrictMode's double useMemo cannot run it twice - Controller is honored on the server store and SSR controller; gcPolicy is documented as browser-only; store providers no longer receive props consumed at store creation - only StateDelta and StateBaseline are public; createServerStore drops its unused managers return Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Lazy-require react-dom/client after the LegacyReact gate so describe.skip can run. Gate DOM-preserving / no-recoverable-warning asserts to React 19 and check reset-vs-delta and mid-hydration Probe via the live store. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
On 18, setResponse while a sibling is dehydrated is deferred, and a client reset can remount from the snapshot. Gate those live-store claims to React 19; keep data, refetch, and stream liveTitle asserts. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Drop the unused-generality id helper; R19 probe covers the late-update store read, and reset asserts getState() inline. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Either form is resolved once at mount. A function lets one definition be shared with the Next.js provider and gives each DataProvider on a page its own manager instances instead of a hoisted array bound to the last store. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
…ional
Doc examples, skill guidance and the DataProvider TSDoc now show
managers={() => [...]}; DataProvider.md and Manager.md state that arrays
will be removed and recommend migrating now. Adds the v0.19 draft blog
post (streamed Next.js SSR, hydration snapshot, managers factory) with a
migration guide.
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Once-per-mount via useRef is correct. StrictMode 17/18 discards a render/useRef pass so the factory runs twice; React 19 keeps one. Assert stability across re-renders within a mount and keep init/cleanup pairing on the live instance. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
The browser DataProvider now accepts a factory; arrays are transitional. Drop the stale "unchanged / Manager[] only" line so release notes do not contradict the sibling changeset. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Rename escapeJsonForHtml to escapeJsonForScript: CodeQL treats any callee named like an HTML sanitizer as producing output unsafe for code construction, hiding the actual \u003c/U+2028 escaping behind the name. Test regexes that parse generated <script> tags are now case-insensitive and tolerate whitespace before the closing bracket. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Rewrite the SSR guide, changeset, blog, Next example README, and agent skills around baseline-plus-delta hydration. Inline the sequence once via the existing diagrams MDX extract. Keep Open questions honest: waiters and fold-on-script are the remaining client clock, not this commit. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Rewrite the streaming test HTML parser so CodeQL does not treat a </script> regex as a bad tag filter. Type homepage SVGs from the SVGR import to avoid dual @types/react conflict in website typecheck. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Lane B honesty pass: changeset, blog, SSR guide, Next example README, skill, and JSDoc now match the tip. Wire (G0 + StateDelta + HYDRATE) ships; per-key waiters, zero-refetch, and generic Fizz incremental emission do not. A DOMContentLoaded interim is not forbidden. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Split the SSR hydration sequence into an overview with black boxes and one zoom per box. Replace Fizz/Flight jargon with renderToPipeableStream and RSC in the guide, skill, changeset, blog, example README, and Next adapter comments. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
SSR is a reference inside data-client-react, not a standalone skill. Decision trees include an SSR column only where behavior differs; otherwise they are the same as client (streamed baseline+deltas instead of one initialState). Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Revert the previous fold (per-row "Same" tables and intended-behavior claims in SKILL.md). SSR now lives in references/ssr.md inside data-client-react and documents only what differs from the browser: entry per host, streamed baseline + StateDelta on Next.js App Router vs one initialState elsewhere, and what is not shipped (per-key waiters, fold-on-script, streamed generic renderToPipeableStream). No standalone data-client-ssr skill. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Add RSC as a trigger term in the data-client-react description, drop the docs-terminology rule from references/ssr.md (not a host difference), and state the SUBSCRIBE caveat without restating browser behavior. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Public DataProvider injects core createReducer and cache reads stay on StateContext. HYDRATE, hydrateReducer, and the snapshot receiver move behind DataProviderBase plus a private streaming reducer for ./nextjs and an unstable ./ssr composition seam. HOLD: revision visibility, schema-blind writeDelta, and overlay lastReset are unchanged. flushSync per-delta publication is preserved adapter behavior. Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Co-authored-by: Nathaniel Tucker <me@ntucker.me>
3be5158 to
a10c9f4
Compare

Motivation
A streaming HTML response is a sequence of committed revisions. A store serialized once — after a quiet window, or after
useReadyCacheState()— cannot describe that sequence. Late islands arrive as markup whose endpoint/meta never landed in the browser, souseSuspense()takes the fetch branch on an empty seed.PR 4090 already replaced the Next 10 ms
createPersistedStoreServersnapshot on the wire: an inert baseline plus per-flushStateDeltas. This tip also moves streamed hydration out of the public SPA store: publicDataProvider/@data-client/reactuse the master reducer andStateContextreads../nextjsand an unstable./ssr__INTERNAL__seam composecreateStreamingReducerand the adapter-owned snapshot.That is not streaming success. Head still folds into the live store from
StreamedStateReceiver’s layout effect (flushSyncplus a live write), has no per-key waiter, and has no render-pure revision-visibility protocol. GenericrenderToPipeableStream/ Anansi still emit one document snapshot by default.HOLD. Do not merge without Nathaniel. Green tests mean the SPA path was restored and Next behavior was relocated, not that duplicate fetches, concurrent streaming, or live merge integrity are solved.
Solution
Adapter-owned streaming (this tip):
createReducerhas noHYDRATEcase.HYDRATE/hydrateReducer/createHydratelive understate/streamand root__INTERNAL__only.HydrateActionis not a publicActionTypesmember.DataProvideris a thin wrapper over privateDataProviderBasewith corecreateReducer.DataStoreuses that same reducer for committed updates and optimistic replay.useCacheStateisuse(StateContext)again. TheSSRDataProviderserver external-store branch remains.ServerSnapshotContext/createServerSnapshotare deleted.packages/react/src/server/stream/ownscreateStreamingReducer, minimalSnapshotStore/DeltaQueue, andStreamedStateReceiver. Next keeps transport (global queue, baseline element, scripts, nonce, managers/pending extensions).createPersistedStoreClientcomposesDataProviderBase + createStreamingReducer + StreamedStateReceiver.flushSyncand per-delta live publication are preserved HOLD behavior.@data-client/react/ssrkeeps one-shot APIs and adds unstable__INTERNAL__.StreamingDataProviderfor hosts that already own a snapshot store. Next production does not depend on that namespace.Still shipped from earlier tips: the wire (inert baseline +
StateDelta), three-way merge helpers, Nextmanagersfactory, escaped JSON,nonce.Not in this release: revision visibility, per-key waiters, fold-on-script-arrival, schema-aware
writeDelta, reset-aware overlay, a stable generic streaming component,@data-client/core/stream.Open questions
use(StateContext)without a live identity change that client-renders pending siblings. Live per-delta publication (flushSync) is preserved, not the concurrent happy path.writeDeltawhole-slot skip/replace) and overlaylastResetremain HOLD. Overlay has no production React consumer after this split.initManagerdisposers remain discarded. Server managers stay inert.managers={array}.