From 23171792d50c0dd1312d02e5c8ad4325d4da3781 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 05:25:24 +0200 Subject: [PATCH 1/5] perf(start-client-core): compact frame decoder result keys --- .../src/client-rpc/frame-decoder.ts | 6 +-- .../src/client-rpc/serverFnFetcher.ts | 9 ++-- .../tests/frame-decoder.test.ts | 44 +++++++++++-------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/packages/start-client-core/src/client-rpc/frame-decoder.ts b/packages/start-client-core/src/client-rpc/frame-decoder.ts index 9834b10a1b..6820afcf81 100644 --- a/packages/start-client-core/src/client-rpc/frame-decoder.ts +++ b/packages/start-client-core/src/client-rpc/frame-decoder.ts @@ -25,9 +25,9 @@ const MAX_FRAMES = 100_000 // Limit total frames to prevent CPU DoS */ export interface FrameDecoderResult { /** Gets or creates a raw stream by ID (for use by deserialize plugin) */ - getOrCreateStream: (id: number) => ReadableStream + getStream: (id: number) => ReadableStream /** Stream of JSON strings (NDJSON lines) */ - jsonChunks: ReadableStream + chunks: ReadableStream } /** @@ -404,5 +404,5 @@ export function createFrameDecoder( } })() - return { getOrCreateStream, jsonChunks } + return { getStream: getOrCreateStream, chunks: jsonChunks } } diff --git a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts index 2e993205bc..84968d7b89 100644 --- a/packages/start-client-core/src/client-rpc/serverFnFetcher.ts +++ b/packages/start-client-core/src/client-rpc/serverFnFetcher.ts @@ -274,18 +274,15 @@ async function getResponse(fn: () => Promise) { throw new Error('No response body for framed response') } - const { getOrCreateStream, jsonChunks } = createFrameDecoder( - response.body, - ) + const { getStream, chunks } = createFrameDecoder(response.body) // Create deserialize plugin that wires up the raw streams - const rawStreamPlugin = - createRawStreamDeserializePlugin(getOrCreateStream) + const rawStreamPlugin = createRawStreamDeserializePlugin(getStream) const plugins = [rawStreamPlugin, ...(serovalPlugins || [])] const refs = new Map() result = await processFramedResponse({ - jsonStream: jsonChunks, + jsonStream: chunks, onMessage: (msg: any) => fromCrossJSON(msg, { refs, plugins }), onError(msg, error) { console.error(msg, error) diff --git a/packages/start-client-core/tests/frame-decoder.test.ts b/packages/start-client-core/tests/frame-decoder.test.ts index 3a05611392..375024bff7 100644 --- a/packages/start-client-core/tests/frame-decoder.test.ts +++ b/packages/start-client-core/tests/frame-decoder.test.ts @@ -50,7 +50,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Unknown frame type') @@ -65,7 +65,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Invalid raw frame streamId') @@ -84,7 +84,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Invalid JSON frame streamId') @@ -105,7 +105,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Frame payload too large') @@ -125,7 +125,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Incomplete frame') @@ -140,7 +140,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await reader.cancel() @@ -169,7 +169,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('Too many raw streams') @@ -186,7 +186,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() await expect(reader.read()).rejects.toThrow('buffer exceeded') @@ -207,7 +207,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() const chunks: Array = [] @@ -240,7 +240,8 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks, getOrCreateStream } = createFrameDecoder(input) + const { chunks: jsonChunks, getStream: getOrCreateStream } = + createFrameDecoder(input) // Pre-create the stream before consuming const stream5 = getOrCreateStream(5) @@ -276,7 +277,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() const chunks: Array = [] @@ -301,7 +302,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() const { value } = await reader.read() @@ -326,7 +327,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() const { value } = await reader.read() @@ -353,7 +354,7 @@ describe('frame-decoder', () => { }, }) - const { jsonChunks } = createFrameDecoder(input) + const { chunks: jsonChunks } = createFrameDecoder(input) const reader = jsonChunks.getReader() const { value } = await reader.read() @@ -387,7 +388,8 @@ describe('frame-decoder', () => { }, }) - const { getOrCreateStream, jsonChunks } = createFrameDecoder(input) + const { getStream: getOrCreateStream, chunks: jsonChunks } = + createFrameDecoder(input) // Pre-create streams before consuming const stream1 = getOrCreateStream(1) @@ -428,7 +430,8 @@ describe('frame-decoder', () => { }, }) - const { getOrCreateStream, jsonChunks } = createFrameDecoder(input) + const { getStream: getOrCreateStream, chunks: jsonChunks } = + createFrameDecoder(input) // Pre-create stream 3 const stream3 = getOrCreateStream(3) @@ -480,7 +483,8 @@ describe('frame-decoder', () => { }, }) - const { getOrCreateStream, jsonChunks } = createFrameDecoder(input) + const { getStream: getOrCreateStream, chunks: jsonChunks } = + createFrameDecoder(input) // First, fully consume JSON stream (this processes all frames) const jsonReader = jsonChunks.getReader() @@ -536,7 +540,8 @@ describe('frame-decoder', () => { }, }) - const { getOrCreateStream, jsonChunks } = createFrameDecoder(input) + const { getStream: getOrCreateStream, chunks: jsonChunks } = + createFrameDecoder(input) // Drain JSON (processes all frames) const jsonReader = jsonChunks.getReader() @@ -588,7 +593,8 @@ describe('frame-decoder', () => { }, }) - const { getOrCreateStream, jsonChunks } = createFrameDecoder(input) + const { getStream: getOrCreateStream, chunks: jsonChunks } = + createFrameDecoder(input) const stream11 = getOrCreateStream(11) const jsonReader = jsonChunks.getReader() From ba3e2bdde060270a923583d8843bda48bdd30563 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 05:38:21 +0200 Subject: [PATCH 2/5] docs: record compact frame result evidence --- ...timization-frame-decoder-compact-result.md | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 RESULT-optimization-frame-decoder-compact-result.md diff --git a/RESULT-optimization-frame-decoder-compact-result.md b/RESULT-optimization-frame-decoder-compact-result.md new file mode 100644 index 0000000000..728b73d5dc --- /dev/null +++ b/RESULT-optimization-frame-decoder-compact-result.md @@ -0,0 +1,66 @@ +# Compact private frame-decoder result keys + +## Principle + +Shorten meaningful keys on private producer/consumer contracts while preserving descriptive names and the existing runtime data structure. + +This change renames the private `createFrameDecoder` result from `{ getOrCreateStream, jsonChunks }` to `{ getStream, chunks }`. The producer and its sole production consumer change together. The result remains an ordinary two-property object. + +## Scope and API boundary + +- `createFrameDecoder` and `FrameDecoderResult` have one production reference, a relative import in `serverFnFetcher.ts`, plus direct tests. +- `src/client-rpc/index.ts` exports only `createClientRpc`. +- The package export map has no `frame-decoder` subpath. +- Public entry declarations contain neither symbol. +- The preserve-modules build emits an internal file, but package exports prevent consumers from importing it. + +No public API, wire format, stream behavior, or error path changes. + +## Bundle-size result + +Exact clean artifacts: + +- base `697ebb6ddbd433d052b6b4707938a5c595865d58`: `/private/tmp/frame-compact-base-full.json` +- candidate `23171792d50c0dd1312d02e5c8ad4325d4da3781`: `/private/tmp/frame-compact-candidate-full.json` + +Before each matrix, an offline frozen install relinked dependencies to the intended worktree. All seven direct benchmark `@tanstack` links and all nine package-internal `start-client-core` links were verified before measurement. + +| Scenario | Raw | Initial gzip | Gzip | Brotli | +| -------------------------------- | ----: | -----------: | ----: | -----: | +| react-router.minimal | 0 B | 0 B | 0 B | 0 B | +| react-router.full | 0 B | 0 B | 0 B | 0 B | +| solid-router.minimal | 0 B | 0 B | 0 B | 0 B | +| solid-router.full | 0 B | 0 B | 0 B | 0 B | +| vue-router.minimal | 0 B | 0 B | 0 B | 0 B | +| vue-router.full | 0 B | 0 B | 0 B | 0 B | +| react-start.minimal | -24 B | -7 B | -8 B | +207 B | +| react-start.deferred-hydration | -24 B | -9 B | -11 B | +2 B | +| react-start.full | -24 B | -6 B | -8 B | +60 B | +| react-start.rsbuild.minimal | -24 B | -9 B | -9 B | -26 B | +| react-start.rsbuild.minimal-iife | -24 B | -10 B | -10 B | +93 B | +| react-start.rsbuild.full | -24 B | -12 B | -12 B | -56 B | +| solid-start.minimal | -24 B | -5 B | -6 B | +66 B | +| solid-start.deferred-hydration | -24 B | -7 B | -7 B | -33 B | +| solid-start.full | -24 B | -8 B | -9 B | -53 B | +| vue-start.minimal | -24 B | -7 B | -7 B | +188 B | +| vue-start.full | -24 B | -6 B | -4 B | +48 B | + +All eleven Start scenarios improve the primary gzip metric by 4–12 B and raw size by 24 B. The six router-only scenarios are byte-identical. Brotli is mixed and disclosed rather than used to select the candidate. + +This is one inseparable producer/consumer contract group. Tests and types do not ship, and the uniform 24 B raw reduction across every consumer provides direct attribution. + +## Runtime validation + +Node 25.8.1 microbenchmarks exercised the full factory-return, destructure, and consume path. Five independent rounds each used 15 warmups, 25 paired samples of 800,000 operations, alternating AB/BA order, and periodic garbage collection. + +Escaping-result paired median deltas by round were `+0.16%`, `-1.84%`, `-0.36%`, `+0.89%`, and `-0.82%`. A 4,096-slot retention variant produced `+4.28%`, `-1.51%`, `+1.21%`, `-0.73%`, and `-0.20%`. The distributions broadly overlap and show no directional effect, so the meaningful string-key rename is runtime-neutral. + +The nearby numeric-key object variant was rejected before implementation: integer-index properties made escaping allocation roughly 95–123% slower across five rounds. A tuple variant was also rejected after showing a roughly 29% tight-loop slowdown. + +## Validation + +- focused frame-decoder unit suite: 19 passed with no Vitest type errors +- package type matrix: TypeScript 5.6, 5.7, 5.8, 5.9, 6.0, and 7.0 passed +- ESLint: no errors; 44 pre-existing warnings +- full 17-scenario bundle matrix: passed +- formatting and `git diff --check`: passed From b0c4ab1542da70d6d34f6b28056f865369bfff55 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 6 Aug 2026 05:49:37 +0200 Subject: [PATCH 3/5] docs: record frame result attribution and e2e --- ...timization-frame-decoder-compact-result.md | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/RESULT-optimization-frame-decoder-compact-result.md b/RESULT-optimization-frame-decoder-compact-result.md index 728b73d5dc..dcf3c5d6be 100644 --- a/RESULT-optimization-frame-decoder-compact-result.md +++ b/RESULT-optimization-frame-decoder-compact-result.md @@ -47,7 +47,25 @@ Before each matrix, an offline frozen install relinked dependencies to the inten All eleven Start scenarios improve the primary gzip metric by 4–12 B and raw size by 24 B. The six router-only scenarios are byte-identical. Brotli is mixed and disclosed rather than used to select the candidate. -This is one inseparable producer/consumer contract group. Tests and types do not ship, and the uniform 24 B raw reduction across every consumer provides direct attribution. +### Per-key attribution + +Each producer/consumer key rename was independently applied to an exact-base worktree and measured with the official `react-start.minimal` scenario. Each attribution branch was committed before measurement so its artifact records a clean exact SHA. Before every run, an offline frozen install relinked dependencies and all seven benchmark links plus all nine package-internal `start-client-core` links were verified to resolve inside that run's worktree. + +| Isolated change | Commit | Raw | Initial raw | Gzip | Initial gzip | Brotli | Initial Brotli | +| ---------------------------------- | ------------------------------------------ | ----: | ----------: | ---: | -----------: | -----: | -------------: | +| `getOrCreateStream` -> `getStream` | `8bf715221eacece8098ef894b16509a2ec8c846c` | -16 B | -16 B | -6 B | -4 B | +80 B | +82 B | +| `jsonChunks` -> `chunks` | `5bf6808e9d7ba792fb3fba51fdc393c8a493424a` | -8 B | -8 B | -1 B | -1 B | +69 B | +69 B | +| Final composition | `23171792d50c0dd1312d02e5c8ad4325d4da3781` | -24 B | -24 B | -8 B | -7 B | +207 B | +207 B | + +Both independent changes improve raw and primary gzip size, so both production hunks remain. Compressed deltas are not expected to add linearly; the final composition is the source of truth for publication. + +Exact targeted artifacts: + +- base: `/private/tmp/frame-key-base-react-start-minimal.json` +- `getStream` only: `/private/tmp/frame-get-stream-only-react-start-minimal.json` +- `chunks` only: `/private/tmp/frame-chunks-only-react-start-minimal.json` +- build logs: `/private/tmp/frame-key-base-react-start-minimal-build.log`, `/private/tmp/frame-get-stream-only-react-start-minimal-build.log`, and `/private/tmp/frame-chunks-only-react-start-minimal-build.log` +- recorded exact diffs: `/private/tmp/frame-get-stream-only.patch` and `/private/tmp/frame-chunks-only.patch` ## Runtime validation @@ -62,5 +80,14 @@ The nearby numeric-key object variant was rejected before implementation: intege - focused frame-decoder unit suite: 19 passed with no Vitest type errors - package type matrix: TypeScript 5.6, 5.7, 5.8, 5.9, 6.0, and 7.0 passed - ESLint: no errors; 44 pre-existing warnings +- React Start Vite/SSR raw-stream client-RPC e2e: 6 passed in 10.4 seconds - full 17-scenario bundle matrix: passed - formatting and `git diff --check`: passed + +The raw-stream e2e ran against the final candidate after verifying the React Start and `start-client-core` workspace links resolved inside the candidate worktree: + +```sh +CI=1 NX_DAEMON=false pnpm nx run tanstack-react-start-e2e-basic:test:e2e--vite-ssr --outputStyle=stream --skipRemoteCache --skipNxCache -- tests/raw-stream.spec.ts --grep 'RawStream - Client RPC Tests' +``` + +All six client-RPC cases passed, covering single and multiple binary streams, both JSON/raw completion orders, a 3 KiB payload, and mixed Promise/RawStream delivery. The exact log is `/private/tmp/frame-compact-raw-stream-e2e.log` (SHA-256 `087a40354c5a638d6b40b07a6c047a0c2dda503d5bc1610a9d79fc762d3a77d8`). From dade2ddfe192d6c6e690f4df3ab95159e11c3103 Mon Sep 17 00:00:00 2001 From: Flo Date: Fri, 7 Aug 2026 12:00:14 +0200 Subject: [PATCH 4/5] Delete RESULT-optimization-frame-decoder-compact-result.md --- ...timization-frame-decoder-compact-result.md | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 RESULT-optimization-frame-decoder-compact-result.md diff --git a/RESULT-optimization-frame-decoder-compact-result.md b/RESULT-optimization-frame-decoder-compact-result.md deleted file mode 100644 index dcf3c5d6be..0000000000 --- a/RESULT-optimization-frame-decoder-compact-result.md +++ /dev/null @@ -1,93 +0,0 @@ -# Compact private frame-decoder result keys - -## Principle - -Shorten meaningful keys on private producer/consumer contracts while preserving descriptive names and the existing runtime data structure. - -This change renames the private `createFrameDecoder` result from `{ getOrCreateStream, jsonChunks }` to `{ getStream, chunks }`. The producer and its sole production consumer change together. The result remains an ordinary two-property object. - -## Scope and API boundary - -- `createFrameDecoder` and `FrameDecoderResult` have one production reference, a relative import in `serverFnFetcher.ts`, plus direct tests. -- `src/client-rpc/index.ts` exports only `createClientRpc`. -- The package export map has no `frame-decoder` subpath. -- Public entry declarations contain neither symbol. -- The preserve-modules build emits an internal file, but package exports prevent consumers from importing it. - -No public API, wire format, stream behavior, or error path changes. - -## Bundle-size result - -Exact clean artifacts: - -- base `697ebb6ddbd433d052b6b4707938a5c595865d58`: `/private/tmp/frame-compact-base-full.json` -- candidate `23171792d50c0dd1312d02e5c8ad4325d4da3781`: `/private/tmp/frame-compact-candidate-full.json` - -Before each matrix, an offline frozen install relinked dependencies to the intended worktree. All seven direct benchmark `@tanstack` links and all nine package-internal `start-client-core` links were verified before measurement. - -| Scenario | Raw | Initial gzip | Gzip | Brotli | -| -------------------------------- | ----: | -----------: | ----: | -----: | -| react-router.minimal | 0 B | 0 B | 0 B | 0 B | -| react-router.full | 0 B | 0 B | 0 B | 0 B | -| solid-router.minimal | 0 B | 0 B | 0 B | 0 B | -| solid-router.full | 0 B | 0 B | 0 B | 0 B | -| vue-router.minimal | 0 B | 0 B | 0 B | 0 B | -| vue-router.full | 0 B | 0 B | 0 B | 0 B | -| react-start.minimal | -24 B | -7 B | -8 B | +207 B | -| react-start.deferred-hydration | -24 B | -9 B | -11 B | +2 B | -| react-start.full | -24 B | -6 B | -8 B | +60 B | -| react-start.rsbuild.minimal | -24 B | -9 B | -9 B | -26 B | -| react-start.rsbuild.minimal-iife | -24 B | -10 B | -10 B | +93 B | -| react-start.rsbuild.full | -24 B | -12 B | -12 B | -56 B | -| solid-start.minimal | -24 B | -5 B | -6 B | +66 B | -| solid-start.deferred-hydration | -24 B | -7 B | -7 B | -33 B | -| solid-start.full | -24 B | -8 B | -9 B | -53 B | -| vue-start.minimal | -24 B | -7 B | -7 B | +188 B | -| vue-start.full | -24 B | -6 B | -4 B | +48 B | - -All eleven Start scenarios improve the primary gzip metric by 4–12 B and raw size by 24 B. The six router-only scenarios are byte-identical. Brotli is mixed and disclosed rather than used to select the candidate. - -### Per-key attribution - -Each producer/consumer key rename was independently applied to an exact-base worktree and measured with the official `react-start.minimal` scenario. Each attribution branch was committed before measurement so its artifact records a clean exact SHA. Before every run, an offline frozen install relinked dependencies and all seven benchmark links plus all nine package-internal `start-client-core` links were verified to resolve inside that run's worktree. - -| Isolated change | Commit | Raw | Initial raw | Gzip | Initial gzip | Brotli | Initial Brotli | -| ---------------------------------- | ------------------------------------------ | ----: | ----------: | ---: | -----------: | -----: | -------------: | -| `getOrCreateStream` -> `getStream` | `8bf715221eacece8098ef894b16509a2ec8c846c` | -16 B | -16 B | -6 B | -4 B | +80 B | +82 B | -| `jsonChunks` -> `chunks` | `5bf6808e9d7ba792fb3fba51fdc393c8a493424a` | -8 B | -8 B | -1 B | -1 B | +69 B | +69 B | -| Final composition | `23171792d50c0dd1312d02e5c8ad4325d4da3781` | -24 B | -24 B | -8 B | -7 B | +207 B | +207 B | - -Both independent changes improve raw and primary gzip size, so both production hunks remain. Compressed deltas are not expected to add linearly; the final composition is the source of truth for publication. - -Exact targeted artifacts: - -- base: `/private/tmp/frame-key-base-react-start-minimal.json` -- `getStream` only: `/private/tmp/frame-get-stream-only-react-start-minimal.json` -- `chunks` only: `/private/tmp/frame-chunks-only-react-start-minimal.json` -- build logs: `/private/tmp/frame-key-base-react-start-minimal-build.log`, `/private/tmp/frame-get-stream-only-react-start-minimal-build.log`, and `/private/tmp/frame-chunks-only-react-start-minimal-build.log` -- recorded exact diffs: `/private/tmp/frame-get-stream-only.patch` and `/private/tmp/frame-chunks-only.patch` - -## Runtime validation - -Node 25.8.1 microbenchmarks exercised the full factory-return, destructure, and consume path. Five independent rounds each used 15 warmups, 25 paired samples of 800,000 operations, alternating AB/BA order, and periodic garbage collection. - -Escaping-result paired median deltas by round were `+0.16%`, `-1.84%`, `-0.36%`, `+0.89%`, and `-0.82%`. A 4,096-slot retention variant produced `+4.28%`, `-1.51%`, `+1.21%`, `-0.73%`, and `-0.20%`. The distributions broadly overlap and show no directional effect, so the meaningful string-key rename is runtime-neutral. - -The nearby numeric-key object variant was rejected before implementation: integer-index properties made escaping allocation roughly 95–123% slower across five rounds. A tuple variant was also rejected after showing a roughly 29% tight-loop slowdown. - -## Validation - -- focused frame-decoder unit suite: 19 passed with no Vitest type errors -- package type matrix: TypeScript 5.6, 5.7, 5.8, 5.9, 6.0, and 7.0 passed -- ESLint: no errors; 44 pre-existing warnings -- React Start Vite/SSR raw-stream client-RPC e2e: 6 passed in 10.4 seconds -- full 17-scenario bundle matrix: passed -- formatting and `git diff --check`: passed - -The raw-stream e2e ran against the final candidate after verifying the React Start and `start-client-core` workspace links resolved inside the candidate worktree: - -```sh -CI=1 NX_DAEMON=false pnpm nx run tanstack-react-start-e2e-basic:test:e2e--vite-ssr --outputStyle=stream --skipRemoteCache --skipNxCache -- tests/raw-stream.spec.ts --grep 'RawStream - Client RPC Tests' -``` - -All six client-RPC cases passed, covering single and multiple binary streams, both JSON/raw completion orders, a 3 KiB payload, and mixed Promise/RawStream delivery. The exact log is `/private/tmp/frame-compact-raw-stream-e2e.log` (SHA-256 `087a40354c5a638d6b40b07a6c047a0c2dda503d5bc1610a9d79fc762d3a77d8`). From b775949d8ffd256adceb9392d3b43ece1c773ac9 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Fri, 7 Aug 2026 14:16:41 +0200 Subject: [PATCH 5/5] changeset --- .changeset/kind-ghosts-win.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/kind-ghosts-win.md diff --git a/.changeset/kind-ghosts-win.md b/.changeset/kind-ghosts-win.md new file mode 100644 index 0000000000..7c71ff82a1 --- /dev/null +++ b/.changeset/kind-ghosts-win.md @@ -0,0 +1,5 @@ +--- +'@tanstack/start-client-core': patch +--- + +compact private frame decoder result keys