Conversation
📝 WalkthroughWalkthroughAdded comprehensive React Server Components (RSC) support, RSC-Query integration, extensive RSC demo routes/utilities and server functions, new ESLint plugin docs and e2e tests, RawStream streaming fixtures/tests, RSC e2e infra (Playwright/Vite/configs), and removed the legacy basic-rsc suite. Documentation and .gitignore updates included. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser
participant Router as Router
participant QueryClient as QueryClient
participant ServerFn as Server Function
participant RSCRenderer as RSC Renderer
participant Flight as Flight/Stream
participant Client as Client Runtime
Browser->>Router: navigate to RSC route
Router->>QueryClient: ensure/prefetch query (optional)
Router->>ServerFn: call createServerFn in loader
ServerFn->>RSCRenderer: renderServerComponent / createCompositeComponent
RSCRenderer-->>Flight: produce Flight payload / ReadableStream
Flight-->>Router: loader resolves with RSC src or stream
Router-->>Browser: return route HTML + Flight placeholders
Browser->>Client: hydrate, mount CompositeComponent
Client->>Flight: consume promise/stream via React.use()/Suspense
Client-->>Client: render server content + client slots
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
🚀 Changeset Version Preview2 package(s) bumped directly, 1 bumped as dependents. 🟩 Patch bumps
|
Bundle Size Benchmarks
Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better. |
|
View your CI Pipeline Execution ↗ for commit 7b9a95a
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
e2e/react-start/basic/src/routes/raw-stream/client-call.tsx (1)
4-42:⚠️ Potential issue | 🟡 MinorFix named import ordering to satisfy lint.
Line 5 is currently out of order (
multipleRawStreamsFnshould come beforesingleRawStreamFn), and this can failsort-importsin CI. Please sort the full named import list alphabetically.Minimal start of the fix
- singleRawStreamFn, - multipleRawStreamsFn, + multipleRawStreamsFn, + singleRawStreamFn,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/basic/src/routes/raw-stream/client-call.tsx` around lines 4 - 42, The named imports in the import list are not alphabetically sorted (e.g., multipleRawStreamsFn should come before singleRawStreamFn), which triggers the sort-imports lint rule; reorder the entire named import list alphabetically (for example ensure multipleRawStreamsFn, singleRawStreamFn, then the rest sorted) so all identifiers like singleRawStreamFn, multipleRawStreamsFn, jsonEndsFirstFn, rawEndsFirstFn, largeBinaryFn, mixedStreamingFn, textHintPureTextFn, textHintPureBinaryFn, textHintMixedFn, binaryHintTextFn, binaryHintBinaryFn, interleavedStreamsFn, burstPauseBurstFn, threeStreamsFn, emptyStreamFn, errorStreamFn, lateRawStreamFn, multipleLateStreamsFn, mixedImmediateLateFn, createStreamConsumer, consumeBinaryStream, collectBytes, compareBytes, and all TEST*_EXPECTED symbols are in strict alphabetical order.
🟡 Minor comments (25)
e2e/react-start/rsc/src/routes/rsc-external.tsx-4-10 (1)
4-10:⚠️ Potential issue | 🟡 MinorFix ESLint import sorting errors.
ESLint flags
sort-importsviolations: members should be sorted alphabetically within each import statement.🔧 Proposed fix
import { - serverBox, serverBadge, + serverBox, serverHeader, timestamp, } from '~/utils/serverStyles' -import { pageStyles, formatTime } from '~/utils/styles' +import { formatTime, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-external.tsx` around lines 4 - 10, The import specifiers in the rsc-external module are not alphabetized which triggers ESLint's sort-imports rule; update the two import statements so the named members are sorted alphabetically within each import (e.g., for '~/utils/serverStyles' order: serverBadge, serverBox, serverHeader, timestamp and for '~/utils/styles' order: formatTime, pageStyles) while keeping the two separate import lines unchanged.e2e/react-start/rsc/src/routes/rsc-external.tsx-137-139 (1)
137-139:⚠️ Potential issue | 🟡 MinorTitle doesn't match the actual functionality.
The title says "Weather Data - RSC with External API" but the route fetches from a
/postsendpoint and displays generic external API data, not weather data. This appears to be placeholder text that should be updated for accuracy.✏️ Suggested fix
- <h1 data-testid="rsc-external-title" style={pageStyles.title}> - Weather Data - RSC with External API - </h1> + <h1 data-testid="rsc-external-title" style={pageStyles.title}> + External Data - RSC with External API + </h1>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-external.tsx` around lines 137 - 139, The h1 title text is inaccurate; update the element with data-testid "rsc-external-title" (the h1 using pageStyles.title) so it accurately describes the content (e.g., "Posts from External API - RSC" or "External API Data - RSC") instead of "Weather Data - RSC with External API"; ensure the displayed string reflects that the route fetches generic posts/external data.e2e/react-start/rsc/src/routes/rsc-caching.tsx-5-11 (1)
5-11:⚠️ Potential issue | 🟡 MinorFix the unsorted named imports.
These imports currently trip
sort-imports, so the file will keep failing lint until the named members are alphabetized.Suggested fix
import { - serverBox, serverBadge, + serverBox, serverHeader, timestamp, } from '~/utils/serverStyles' -import { pageStyles, clientStyles, formatTime } from '~/utils/styles' +import { clientStyles, formatTime, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-caching.tsx` around lines 5 - 11, The named imports are not alphabetized and trigger the sort-imports rule; reorder the members in each import so they are alphabetically sorted (for the first import alphabetical order: serverBadge, serverBox, serverHeader, timestamp; for the second import alphabetical order: clientStyles, formatTime, pageStyles) ensuring import lines for serverBox/serverBadge/serverHeader/timestamp and pageStyles/clientStyles/formatTime are updated accordingly.e2e/react-start/rsc/src/utils/styles.ts-142-145 (1)
142-145:⚠️ Potential issue | 🟡 MinorAdd the missing
@keyframes loadingdefinition or remove the animation reference.The
animation: 'loading 1.5s ease-in-out infinite'style on line 145 references aloadingkeyframe animation that does not exist anywhere in the repository. Without the corresponding@keyframes loadingrule, this animation will not execute and the progress indicator will remain static.Define the keyframes (e.g., in a global stylesheet or CSS module) or apply a different animation that already exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/utils/styles.ts` around lines 142 - 145, The loadingProgress style references an undefined animation name "loading"; add a corresponding `@keyframes` loading rule (e.g., keyframes that animate width or transform to show progress) in your global stylesheet or CSS module, or remove/replace the animation property on the loadingProgress object so it uses an existing animation; locate the loadingProgress object in styles.ts and either create the `@keyframes` loading definition in the same stylesheet scope or change the animation string to a defined keyframe name.e2e/react-start/rsc/src/routes/rsc-caching.tsx-45-46 (1)
45-46:⚠️ Potential issue | 🟡 MinorUse the deterministic time formatter here too.
This route imports
formatTimebut the server-rendered timestamp at line 46 usesnew Date(serverTimestamp).toLocaleTimeString()instead. The inconsistency introduces locale-dependent output that can cause e2e test flakiness. UseformatTime(serverTimestamp)for deterministic UTC formatting, as done elsewhere in this file.Suggested fix
<span style={timestamp} data-testid="rsc-cached-timestamp"> - {new Date(serverTimestamp).toLocaleTimeString()} + {formatTime(serverTimestamp)} </span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-caching.tsx` around lines 45 - 46, Replace the locale-dependent rendering new Date(serverTimestamp).toLocaleTimeString() inside the span with data-testid="rsc-cached-timestamp" by calling the deterministic formatter formatTime(serverTimestamp); update the server-rendered timestamp in the rsc-caching route to use formatTime (the same function already imported and used elsewhere) so tests get consistent UTC-formatted output.e2e/react-start/rsc/src/utils/basicServerComponent.tsx-10-12 (1)
10-12:⚠️ Potential issue | 🟡 MinorUnused
labelparameter in input validator.The
inputValidatoraccepts{ label?: string }butdata.labelis never used in the handler. The AI summary indicatesrsc-basic.tsxcalls this with{ label: 'test label' }, but the label is not rendered anywhere in the component.If this is intentional for future use, consider adding a comment. Otherwise, either use the label in the rendered output or remove it from the input type.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/utils/basicServerComponent.tsx` around lines 10 - 12, The inputValidator for getBasicServerComponent declares a possibly-unused label ({ label?: string }) but the handler does not use data.label; either remove label from the input shape in inputValidator to avoid dead/incorrect typing, or update the handler used by getBasicServerComponent to include data.label in the returned rendered output (or add a comment next to inputValidator explaining the label is intentionally unused for future use). Locate getBasicServerComponent, its inputValidator and handler and apply one of these fixes so the declared input type matches actual usage.e2e/react-start/rsc/src/routes/rsc-error.tsx-2-2 (1)
2-2:⚠️ Potential issue | 🟡 MinorRemove unused
ErrorComponentimport.
ErrorComponentis imported but never used in this file. The route uses a customRouteErrorComponentfunction instead.🧹 Proposed fix
-import { createFileRoute, ErrorComponent } from '@tanstack/react-router' +import { createFileRoute } from '@tanstack/react-router'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-error.tsx` at line 2, The file imports ErrorComponent but never uses it; remove ErrorComponent from the import list so only createFileRoute is imported from '@tanstack/react-router'. Locate the import line that references createFileRoute and ErrorComponent and delete the unused ErrorComponent identifier, leaving createFileRoute (and any other actually used symbols) to avoid the unused-import lint error.e2e/react-start/rsc/src/routes/rsc-invalidation.tsx-179-196 (1)
179-196:⚠️ Potential issue | 🟡 MinorKeep
searchInputin sync with the URL.
searchInputis only seeded fromqonce. After back/forward navigation or any external search-param update, the input can show stale text while the results reflect a different query.Suggested fix
const { q, page } = Route.useSearch() const navigate = useNavigate() const [searchInput, setSearchInput] = React.useState(q || '') + + React.useEffect(() => { + setSearchInput(q || '') + }, [q])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-invalidation.tsx` around lines 179 - 196, searchInput is only initialized from q once, so it becomes stale on back/forward or external URL updates; add a React.useEffect that watches q (from Route.useSearch()) and calls setSearchInput(q ?? '') to keep the input controlled and in sync, leaving handleSearch, handlePageChange, useSearch and navigate unchanged; ensure the input component uses value={searchInput} and onChange to update setSearchInput.docs/start/framework/react/guide/server-components.md-50-50 (1)
50-50:⚠️ Potential issue | 🟡 MinorUpdate Vite version requirement to reflect current baseline.
The documentation states "Vite 7+" but the project actively uses and tests with Vite 8.0.0. While Vite 7+ is technically valid, the requirement should be updated to "Vite 8+" to align with the current Vite version (8.0.8) and the project's baseline dependency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/start/framework/react/guide/server-components.md` at line 50, Update the requirements line that currently reads "Requirements: React 19+, Vite 7+" so it reflects the project baseline by changing "Vite 7+" to "Vite 8+"; locate the "Requirements:" header text (the line containing "React 19+, Vite 7+") and replace the Vite version to "Vite 8+".e2e/react-start/rsc/src/routes/rsc-deferred-component.tsx-57-91 (1)
57-91:⚠️ Potential issue | 🟡 MinorAvoid async Promise executor - errors may be silently swallowed.
Both Biome and ESLint flag this pattern. If an error occurs inside the async executor (e.g.,
renderServerComponentthrows), the Promise won't reject - the error will be swallowed. Additionally,Promise<any>loses type safety.🛠️ Proposed fix using async helper function
- // Deferred RSC - wrapped in Promise that resolves after 500ms - // This tests that RSCs in streaming Promises are decoded before resolution - const deferredRscPromise = new Promise<any>(async (resolve) => { - await new Promise((r) => setTimeout(r, 500)) - const deferredTimestamp = Date.now() - - const DeferredRsc = await renderServerComponent( - <div style={serverBox} data-testid="deferred-streamed-rsc"> - {/* ... JSX content ... */} - </div>, - ) - resolve(DeferredRsc) - }) + // Deferred RSC - wrapped in Promise that resolves after 500ms + // This tests that RSCs in streaming Promises are decoded before resolution + const deferredRscPromise = (async () => { + await new Promise((r) => setTimeout(r, 500)) + const deferredTimestamp = Date.now() + + return renderServerComponent( + <div style={serverBox} data-testid="deferred-streamed-rsc"> + {/* ... JSX content ... */} + </div>, + ) + })()Note: The IIFE pattern is already used successfully in
rsc-async-bundle.tsxfor the same purpose.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-deferred-component.tsx` around lines 57 - 91, The Promise executor in deferredRscPromise is declared async which can swallow thrown errors and uses Promise<any>; replace it with a non-async executor or an async IIFE so errors propagate and typing is preserved. For example, remove new Promise<any>(async (resolve) => { ... }) and instead either use (async () => { await new Promise(r => setTimeout(r, 500)); const DeferredRsc = await renderServerComponent(...); return DeferredRsc; })() (with a proper return type instead of any) or use new Promise<JSX.Element | string>((resolve, reject) => { setTimeout(() => renderServerComponent(...).then(resolve).catch(reject), 500) }); ensure you reference deferredRscPromise and renderServerComponent and keep deferredTimestamp/deferredId usage unchanged while tightening the Promise generic from any to an appropriate type.e2e/react-start/rsc/src/routes/rsc-forms.tsx-4-15 (1)
4-15:⚠️ Potential issue | 🟡 MinorFix import member ordering to satisfy
sort-imports.This file currently has lint errors from unsorted named imports (Line 6, Line 10, Line 14).
🔧 Suggested fix
import { - createCompositeComponent, CompositeComponent, + createCompositeComponent, } from '@tanstack/react-start/rsc' import { - serverBox, serverBadge, + serverBox, serverHeader, timestamp, } from '~/utils/serverStyles' -import { pageStyles, clientStyles, formatTime } from '~/utils/styles' +import { clientStyles, formatTime, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-forms.tsx` around lines 4 - 15, Reorder the named imports so each import's specifiers are sorted alphabetically to satisfy the `sort-imports` rule: in the first import ensure "CompositeComponent" comes after "createCompositeComponent"; in the second import sort "serverBadge", "serverBox", "serverHeader", "timestamp" alphabetically; and in the third import sort "clientStyles", "formatTime", "pageStyles" alphabetically — update the import lines that reference createCompositeComponent, CompositeComponent, serverBox, serverBadge, serverHeader, timestamp, pageStyles, clientStyles, and formatTime accordingly.docs/start/framework/react/comparison.md-270-270 (1)
270-270:⚠️ Potential issue | 🟡 MinorUse a docs-relative internal link here.
Line 270 uses an absolute docs URL; this should be a relative internal link for consistency and guideline compliance.
🔧 Suggested fix
-TanStack Start's RSC implementation takes a client-led composition approach: server components are treated as data that the client can fetch, cache, and compose. See the [Server Components guide](/start/latest/docs/framework/react/guide/server-components) for details. +TanStack Start's RSC implementation takes a client-led composition approach: server components are treated as data that the client can fetch, cache, and compose. See the [Server Components guide](./guide/server-components) for details.As per coding guidelines,
**/*.md: Use internal docs links relative to docs/ folder (e.g., ./guide/data-loading).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/start/framework/react/comparison.md` at line 270, The sentence currently uses an absolute docs URL for the Server Components guide; replace the absolute link "/start/latest/docs/framework/react/guide/server-components" with a docs-relative internal link (e.g., ./guide/server-components) so the markdown uses a relative path to the docs folder for the "Server Components guide" reference.e2e/react-start/rsc/src/routes/api.rsc-flight.tsx-4-4 (1)
4-4:⚠️ Potential issue | 🟡 MinorFix named-import ordering to satisfy lint rule.
Line 4 has non-alphabetical members in the import list, which triggers
sort-imports.🧹 Suggested patch
-import { serverStyles, formatTime } from '~/utils/styles' +import { formatTime, serverStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/api.rsc-flight.tsx` at line 4, The import list is not alphabetized which breaks the sort-imports rule; update the named import from the statement importing serverStyles and formatTime so the members are ordered alphabetically (formatTime, serverStyles) in the import declaration to satisfy the linter and preserve behavior.e2e/react-start/rsc/src/routes/rsc-param/index.tsx-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorFix import member order to satisfy
sort-imports.
Linkshould be sorted beforecreateFileRouteper the active lint rule.Suggested fix
-import { createFileRoute, Link } from '@tanstack/react-router' +import { Link, createFileRoute } from '@tanstack/react-router'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-param/index.tsx` at line 1, The import member order violates the sort-imports rule: swap the named imports so that Link comes before createFileRoute in the import statement (i.e., change the import declaration that currently lists createFileRoute then Link to list Link then createFileRoute) to satisfy the linter; update the import containing the symbols createFileRoute and Link accordingly.e2e/react-start/rsc-query/src/routes/index.tsx-1-2 (1)
1-2:⚠️ Potential issue | 🟡 MinorFix import member ordering to satisfy
sort-imports.Current member order in both imports violates the configured sorter and will fail lint.
Proposed fix
-import { createFileRoute, Link, linkOptions } from '@tanstack/react-router' -import { pageStyles, colors } from '~/utils/styles' +import { Link, createFileRoute, linkOptions } from '@tanstack/react-router' +import { colors, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc-query/src/routes/index.tsx` around lines 1 - 2, Reorder the named imports so they are alphabetically sorted per the linter: for the first import ensure the members from '@tanstack/react-router' are ordered as "createFileRoute, linkOptions, Link" (or alphabetically whichever your linter enforces) and for the second import order members from '~/utils/styles' as "colors, pageStyles"; update the import lines that include createFileRoute, Link, linkOptions and the one with pageStyles, colors accordingly so they satisfy sort-imports.e2e/react-start/rsc/src/routes/index.tsx-1-2 (1)
1-2:⚠️ Potential issue | 🟡 MinorResolve
sort-importsviolations in import declarations.The current member order will fail lint.
Suggested fix
-import { createFileRoute, Link, linkOptions } from '@tanstack/react-router' -import { pageStyles, colors } from '~/utils/styles' +import { Link, createFileRoute, linkOptions } from '@tanstack/react-router' +import { colors, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/index.tsx` around lines 1 - 2, The import declarations violate the linter's sort-imports rule; reorder the named imports alphabetically to satisfy linting: in the first import adjust the member order for createFileRoute, linkOptions, Link (use the exact symbols createFileRoute, linkOptions, Link), and in the second import alphabetize pageStyles and colors (use colors, pageStyles or pageStyles, colors depending on your project's configured alphabetical direction) so both import lines have their members sorted consistently.e2e/react-start/rsc/src/routes/rsc-css-conditional.$branch.tsx-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorFix import member order to satisfy
sort-imports.This currently fails lint; reorder the named imports alphabetically per the active rule.
Suggested fix
-import { ClientOnly, createFileRoute, Link } from '@tanstack/react-router' +import { ClientOnly, Link, createFileRoute } from '@tanstack/react-router'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-css-conditional`.$branch.tsx at line 1, The named imports in the import statement (ClientOnly, createFileRoute, Link) are out of alphabetical order and trigger the sort-imports lint rule; update the import from '@tanstack/react-router' to list the members alphabetically (ClientOnly, createFileRoute, Link) so the named import order satisfies the linter (locate the import line that currently imports ClientOnly, createFileRoute, Link and reorder the identifiers accordingly).e2e/react-start/rsc-query/src/routes/__root.tsx-1-11 (1)
1-11:⚠️ Potential issue | 🟡 MinorFix import ordering to satisfy
import/order.Line 1 currently violates the configured order (
typeimport placement).💡 Suggested fix
-import type { QueryClient } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { HeadContent, Link, Outlet, Scripts, createRootRouteWithContext, } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { colors } from '~/utils/styles' +import type { QueryClient } from '@tanstack/react-query'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc-query/src/routes/__root.tsx` around lines 1 - 11, The type import for QueryClient is out of order; adjust the imports so type-only imports follow the configured import/order (move "import type { QueryClient } from '@tanstack/react-query'" to be grouped with or placed after the non-type external imports such as "ReactQueryDevtools" and the other `@tanstack` imports like HeadContent/Link/Outlet/Scripts/createRootRouteWithContext and TanStackRouterDevtools, or consolidate into proper grouped imports) so the file-level import ordering rule is satisfied.e2e/react-start/rsc/src/routes/rsc-streaming.tsx-1-2 (1)
1-2:⚠️ Potential issue | 🟡 MinorResolve
sort-importsordering errors in imports.The import members are not alphabetically ordered per the configured lint rule.
💡 Suggested fix
-import { createFileRoute, Link } from '@tanstack/react-router' -import { pageStyles, colors } from '~/utils/styles' +import { Link, createFileRoute } from '@tanstack/react-router' +import { colors, pageStyles } from '~/utils/styles'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-streaming.tsx` around lines 1 - 2, Reorder the named imports to satisfy the sort-imports rule: in rsc-streaming.tsx change the second import from "import { pageStyles, colors } from '~/utils/styles'" to import the members alphabetically as "colors, pageStyles" (keep the first import from '@tanstack/react-router' as-is with createFileRoute and Link).e2e/react-start/rsc/tests/rsc-bundle.spec.ts-141-162 (1)
141-162:⚠️ Potential issue | 🟡 MinorThis test never verifies the behavior named in its title.
It only asserts that both IDs exist, so it still passes if repeated visits reuse the same bundle. Either compare the two visits directly, or rename the test to match what it actually proves.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-bundle.spec.ts` around lines 141 - 162, The test "Multiple bundle page visits get new bundle IDs" currently only checks existence of firstBundleId and secondBundleId; update it to actually validate the intended behavior by asserting the IDs differ (e.g., expect(firstBundleId).not.toEqual(secondBundleId)) or, if server caching can legitimately reuse IDs, change the test title to reflect that it only verifies presence; modify the assertions around the variables firstBundleId and secondBundleId accordingly (or instead compare a timestamp field returned from the page if available) so the test matches its name.e2e/react-start/rsc/src/routes/__root.tsx-52-59 (1)
52-59:⚠️ Potential issue | 🟡 MinorAdd
activeOptions={{ exact: true }}to the home nav link.The home link pointing to "/" will remain active on all child routes without exact matching. This is a standard pattern across the entire TanStack Router codebase—virtually every e2e and example root route uses
activeOptions={{ exact: true }}for the home navigation link.Suggested fix
<Link to="/" + activeOptions={{ exact: true }} className="nav-link" activeProps={{ className: 'nav-link active' }} data-testid="nav-home" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/__root.tsx` around lines 52 - 59, The Home navigation Link currently uses activeProps but lacks exact matching; update the Link element (the one with data-testid="nav-home" and className="nav-link") to include activeOptions with exact true (i.e., add activeOptions={{ exact: true }}) so the "/" route only marks Home active on the exact root path, matching the project's TanStack Router pattern.e2e/react-start/rsc/src/routes/rsc-flight-api.tsx-66-67 (1)
66-67:⚠️ Potential issue | 🟡 MinorDon't resolve the Flight tree before handing it to
use.These
awaits mean state only receives an already-resolved promise, so theSuspensefallbacks never render and the example stops exercising the streaming path it is meant to demonstrate.Suggested fix
- const tree = await createFromFetch(Promise.resolve(response)) - setResponsePromise(Promise.resolve(tree as React.ReactNode)) + setResponsePromise( + createFromFetch(Promise.resolve(response)) as Promise<React.ReactNode>, + ) … - const tree = await createFromReadableStream(response.body) - setApiPromise(Promise.resolve(tree as React.ReactNode)) + setApiPromise( + createFromReadableStream(response.body) as Promise<React.ReactNode>, + )Also applies to: 89-90
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx` around lines 66 - 67, The code awaits the Flight tree and then wraps the resolved value in a resolved promise before calling setResponsePromise, which defeats streaming; instead, do not await createFromFetch and pass its returned promise directly to setResponsePromise (i.e., use the promise returned by createFromFetch(Promise.resolve(response)) rather than awaiting and re-wrapping the tree). Apply the same change for the other occurrence that mirrors this pattern so setResponsePromise receives the original createFromFetch promise and lets use/Suspense handle streaming.e2e/eslint-plugin-start/tests/eslint-rules.test.ts-18-23 (1)
18-23:⚠️ Potential issue | 🟡 MinorFail fast when ESLint returns no result.
If a fixture path is wrong or ignored, this returns
undefinedand the test fails later withundefined.messages, which hides the actual setup problem. Throw an explicit error here instead.Suggested fix
async function lintFile(relativePath: string) { const results = await eslint.lintFiles([ path.join(rootDir, 'src', relativePath), ]) - return results[0] + if (!results[0]) { + throw new Error(`No ESLint result for fixture: ${relativePath}`) + } + return results[0] }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/eslint-plugin-start/tests/eslint-rules.test.ts` around lines 18 - 23, The helper lintFile should fail fast when ESLint returns no result: after awaiting eslint.lintFiles(...) check whether results[0] is undefined and throw a clear error (mentioning the provided relativePath and that the file may be missing or ignored) instead of returning undefined; update the lintFile function to validate results[0] and throw a descriptive exception so callers (tests) see the real setup problem rather than later undefined.messages errors.e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx-21-22 (1)
21-22:⚠️ Potential issue | 🟡 MinorAvoid
anyfor the streamed RSC payload.This drops the type boundary around the readable stream chunks and the
CompositeComponentcontract, so regressions in the streamed payload shape become runtime-only failures. The proper type is available fromcreateNotificationRSC's return type. As per coding guidelines,**/*.{ts,tsx}must use TypeScript strict mode with extensive type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx` around lines 21 - 22, Replace the unsafe any type for NotificationRSC with the actual type derived from createNotificationRSC's return type: import (or reference) createNotificationRSC and define NotificationRSC = ReturnType<typeof createNotificationRSC> (or if createNotificationRSC is async use Awaited<ReturnType<typeof createNotificationRSC>>); remove the eslint disable comment and ensure the new type matches the streamed RSC payload shape so the readable stream chunks and CompositeComponent contract are statically typed.e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx-21-22 (1)
21-22:⚠️ Potential issue | 🟡 MinorUse proper type inference instead of
anyfor strict mode compliance.Replace
type NotificationRSC = anywithtype NotificationRSC = Awaited<ReturnType<typeof createNotificationRSC>>. This preserves type safety and automatically infers the correct return type from thecreateNotificationRSCfunction, ensuring the type stays in sync if the function signature changes. Per coding guidelines,**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx` around lines 21 - 22, Replace the unsafe any alias by inferring the actual return type from createNotificationRSC: change the NotificationRSC type to use Awaited<ReturnType<typeof createNotificationRSC>> so the type is strict and stays in sync with the createNotificationRSC function; locate the type alias named NotificationRSC and update it to use Awaited<ReturnType<typeof createNotificationRSC>>.
🧹 Nitpick comments (13)
e2e/react-start/rsc/src/routes/rsc-external.tsx (1)
31-31: Consider adding runtime type validation for the JSON response.The
response.json()call returnsanyand is assigned to a typed variable without validation. If the external server returns an unexpected shape, this could cause runtime issues downstream.For e2e tests with a controlled mock server this is low risk, but for robustness you could add basic validation.
💡 Optional: Add basic shape validation
- externalData = await response.json() + const json = await response.json() + if (json && typeof json.message === 'string' && typeof json.timestamp === 'number') { + externalData = json + } else { + throw new Error('Invalid response shape') + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-external.tsx` at line 31, The assignment externalData = await response.json() assumes a specific shape; add runtime validation after the fetch to ensure the JSON matches the expected type (e.g., check required properties and their types or use a lightweight validator like a simple type guard or zod) and throw or return a safe default if validation fails. Locate the response.json() call and validate the resulting externalData object (verify keys/types the rest of the module expects) before using it elsewhere (so add a validateExternalData or isValidExternalData guard and call it immediately after response.json()).e2e/react-start/rsc/src/routes/rsc-caching.tsx (1)
17-39: NarrowdataTypeto the supported literals.Using
stringplusRecord<string, ...>throws away most of the type safety here: unsupported keys compile cleanly, and the fallback path can hide bad inputs in the test fixture. A literal union keeps the server function and the map in sync.Suggested fix
+type CachedDataType = 'users' | 'orders' | 'revenue' + const getCachedDataServerComponent = createServerFn({ method: 'GET' }) - .inputValidator((data: { dataType: string }) => data) + .inputValidator((data: { dataType: CachedDataType }) => data) .handler(async ({ data }) => { @@ - const dataMap: Record<string, { label: string; value: number }> = { + const dataMap: Record<CachedDataType, { label: string; value: number }> = { users: { label: 'Active Users', value: Math.floor(Math.random() * 10000), @@ - const result = dataMap[data.dataType] || { - label: 'Unknown', - value: 0, - } + const result = dataMap[data.dataType]As per coding guidelines, "Use TypeScript strict mode with extensive type safety".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-caching.tsx` around lines 17 - 39, The handler currently types data.dataType as a plain string which loses safety; update the createServerFn inputValidator and related types so dataType is a literal union of supported keys (e.g., 'users' | 'orders' | 'revenue'), and type dataMap as Record<ThatUnion, { label: string; value: number }>; then remove or narrow the fallback branch in getCachedDataServerComponent.handler (or throw/return a typed error for unknown keys) so unsupported inputs fail at compile/runtime rather than silently using the generic fallback.e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx (1)
4-9: Use type-only import forAnyCompositeComponent.ESLint correctly identifies that
AnyCompositeComponentis only used as a type. Use a type-only import to clarify intent and potentially improve tree-shaking.♻️ Proposed fix
-import { - renderServerComponent, - createCompositeComponent, - CompositeComponent, - AnyCompositeComponent, -} from '@tanstack/react-start/rsc' +import { + renderServerComponent, + createCompositeComponent, + CompositeComponent, +} from '@tanstack/react-start/rsc' +import type { AnyCompositeComponent } from '@tanstack/react-start/rsc'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx` around lines 4 - 9, The import for AnyCompositeComponent should be a type-only import to reflect it is used only as a TypeScript type and aid bundling; update the import statement that currently brings in renderServerComponent, createCompositeComponent, CompositeComponent, and AnyCompositeComponent from '@tanstack/react-start/rsc' so that AnyCompositeComponent is imported with the TypeScript-only syntax (e.g., import type { AnyCompositeComponent } from '...') while keeping renderServerComponent, createCompositeComponent and CompositeComponent as value imports.e2e/react-start/rsc/src/routes/rsc-ssr-false.tsx (1)
421-424: Redundant state update insaveDrawing.Line 423
setDrawingName(drawingName)sets state to its current value, which is a no-op. This doesn't cause issues but is unnecessary.♻️ Proposed fix
const saveDrawing = () => { localStorage.setItem('drawing-name', drawingName) - setDrawingName(drawingName) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-ssr-false.tsx` around lines 421 - 424, The saveDrawing function redundantly calls setDrawingName(drawingName) after storing the name in localStorage; remove the no-op state update and keep only localStorage.setItem('drawing-name', drawingName) (or, if the intent was to clear or change state, replace the call with the correct value), referencing saveDrawing and the state setter setDrawingName.e2e/react-start/rsc/src/routes/rsc-stream-loader.tsx (1)
33-33: Consider adding a more specific type forNotificationRSC.Using
anyloses type safety. Since this is an e2e test file, it's acceptable, but for better documentation of the expected shape, consider importing or defining the actual composite component type.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-stream-loader.tsx` at line 33, NotificationRSC is currently typed as any which loses type safety; replace it with a specific type declaration that matches the expected component shape (e.g., a React component type or the composite props/interface used by the notification component) by importing the actual component type or defining an interface for its props and element type and using React.FC<Props> or ComponentType<Props> instead of any (update the NotificationRSC alias and any usages in this file to the concrete type).docs/start/framework/react/guide/server-components.md (1)
897-897: Consider hyphenating compound adjectives for consistency.Per standard grammar conventions, compound adjectives like "route-level" and "component-level" are typically hyphenated when used attributively.
📝 Suggested changes
-### Route Level Errors +### Route-Level Errors-### Component Level Errors +### Component-Level ErrorsAlso applies to: 912-912
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/start/framework/react/guide/server-components.md` at line 897, Update the compound adjectives to use hyphens for consistency: change the heading "Route Level Errors" to "Route-level Errors" and any occurrences like "component level" (notably the instance around the earlier reference) to "component-level"; search for and replace other attributive uses of "route level" and "component level" in this document to follow the hyphenated form.docs/start/eslint/eslint-plugin-start.md (1)
106-107: Use extensionless internal docs links for consistency with docs routing.Line 106 and Line 107 should follow the docs-relative link style without
.mdsuffix.✏️ Suggested patch
-- [`@tanstack/start/no-client-code-in-server-component`](./no-client-code-in-server-component.md) -- [`@tanstack/start/no-async-client-component`](./no-async-client-component.md) +- [`@tanstack/start/no-client-code-in-server-component`](./no-client-code-in-server-component) +- [`@tanstack/start/no-async-client-component`](./no-async-client-component)As per coding guidelines
**/*.md: Use internal docs links relative to docs/ folder (e.g., ./guide/data-loading).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/start/eslint/eslint-plugin-start.md` around lines 106 - 107, Update the two docs links to use extensionless, docs-relative links: replace the link targets for the anchor texts "@tanstack/start/no-client-code-in-server-component" and "@tanstack/start/no-async-client-component" by removing the ".md" suffix (e.g., change "./no-client-code-in-server-component.md" -> "./no-client-code-in-server-component") so they follow the docs routing guideline for extensionless internal links.e2e/react-start/rsc/src/utils/CssModulesContent.tsx (1)
58-60: Prefer deterministic output forComponent ID.Line 59 uses
Math.random(), which makes server-rendered content nondeterministic and can make assertions/debugging noisier in e2e flows. Consider a stable ID for this demo surface.♻️ Proposed change
export function CssModulesContent({ data }: { data: { title?: string } }) { const serverTimestamp = Date.now() + const componentId = 'css-mod-rsc' @@ <div className={styles.footer}> - Component ID: css-mod-{Math.random().toString(36).slice(2, 8)} + Component ID: {componentId} </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/utils/CssModulesContent.tsx` around lines 58 - 60, The footer currently renders a nondeterministic string via Math.random in CssModulesContent.tsx (the div with className styles.footer and text "Component ID: css-mod-..."); replace the random generator with a deterministic ID—either a fixed demo ID (e.g. "css-mod-0001") or React's built-in stable useId hook—to ensure server-rendered output is repeatable across runs; update the JSX to use that stable id value instead of Math.random().e2e/react-start/rsc/package.json (1)
15-17: Useworkspace:*for internal package references.Internal dependencies should follow the repository’s workspace protocol convention.
💡 Suggested fix
"dependencies": { - "@tanstack/react-router": "workspace:^", - "@tanstack/react-router-devtools": "workspace:^", - "@tanstack/react-start": "workspace:^", + "@tanstack/react-router": "workspace:*", + "@tanstack/react-router-devtools": "workspace:*", + "@tanstack/react-start": "workspace:*", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@playwright/test": "^1.50.1", - "@tanstack/eslint-plugin-start": "workspace:^", - "@tanstack/router-e2e-utils": "workspace:^", + "@tanstack/eslint-plugin-start": "workspace:*", + "@tanstack/router-e2e-utils": "workspace:*",Based on learnings: "Applies to package.json : Use workspace protocol for internal dependencies (workspace:*)."
Also applies to: 23-24
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/package.json` around lines 15 - 17, Update internal workspace dependencies in package.json to use the workspace protocol with a wildcard: replace the version strings for "@tanstack/react-router", "@tanstack/react-router-devtools", and "@tanstack/react-start" (and the other internal entries referenced around lines 23-24) from "workspace:^" to "workspace:*" so they follow the repository's workspace convention; ensure you only change internal package entries and leave external versions untouched.e2e/react-start/rsc/tests/rsc-basic.spec.ts (1)
4-5: Prefer condition-based waiting over fixed sleep.
HYDRATION_WAITat Line 4 andwaitForTimeoutat Line 34 can make tests flaky/slow; waiting on a concrete UI readiness condition is more reliable.Also applies to: 34-34
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-basic.spec.ts` around lines 4 - 5, Replace the fixed sleep (HYDRATION_WAIT constant and its use with page.waitForTimeout) with a condition-based wait: remove or stop using HYDRATION_WAIT and instead wait for a concrete readiness signal (e.g., page.waitForSelector, page.waitForFunction, or waitForResponse) that corresponds to the hydrated UI element or network request used by the test; update the test in rsc-basic.spec.ts to target a stable DOM selector or response used after hydration (reference HYDRATION_WAIT and the site of page.waitForTimeout) so the test proceeds as soon as the app is ready rather than a fixed timeout.e2e/react-start/rsc/src/routes/rsc-flight-api.tsx (1)
68-69: Avoidanyin the catch paths.Use
unknownand narrow toErrorinstead. That keeps the demo aligned with the repo's strict TypeScript guidance.Suggested fix
- } catch (e: any) { - setResponseError(e?.message || String(e)) + } catch (e: unknown) { + setResponseError(e instanceof Error ? e.message : String(e)) } … - } catch (e: any) { - setApiError(e?.message || String(e)) + } catch (e: unknown) { + setApiError(e instanceof Error ? e.message : String(e)) }As per coding guidelines,
**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.Also applies to: 91-92
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx` around lines 68 - 69, Replace the use of `any` in the two catch blocks with `unknown` and narrow to `Error` before reading message: in the catch that calls `setResponseError(...)` (and the other catch at the later location), change the parameter to `e: unknown` and then use an `instanceof Error` guard to pass `e.message` to `setResponseError`; if it’s not an Error, fall back to `String(e)` so you avoid unsafe `any` usage while preserving the original error text.e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts (2)
17-27: Unused variableslowLoading.The
slowLoadinglocator is declared but never used in this test. Consider removing it or adding an assertion if the loading state visibility was intended to be tested.♻️ Proposed fix
// Loading states should be visible initially or briefly // Note: The fast component (100ms) may load too quickly to catch, // but we should see the slow one loading - const slowLoading = page.getByTestId('loading-slow') // Wait for the slow component to finish loading (1500ms + buffer) await expect(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 17 - 27, The variable slowLoading (locator page.getByTestId('loading-slow')) is declared but never used; either remove the slowLoading declaration or assert its visibility—e.g., add an expectation using slowLoading.toBeVisible({ timeout: 5000 })—so the intended loading-state check is exercised alongside the existing await expect(...getByTestId('rsc-async-slow')).toBeVisible call that targets async-slow-wrapper/rsc-async-slow.
257-285: Test assertion may not validate the intended behavior.The comment on line 284 acknowledges that bundle IDs "might be same if cached," which means this test doesn't definitively verify that new server requests generate new bundle IDs. The test currently only validates that the structure works and bundle IDs are truthy.
If the intent is to verify new bundle IDs on each visit, consider adding
{ headers: { 'Cache-Control': 'no-cache' } }to the page requests or accepting that this test validates structure rather than uniqueness.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 257 - 285, The test 'Multiple page visits get new bundle IDs' currently only checks bundle IDs are truthy and comments that they may be the same if cached; to ensure a new server request and new bundle ID, update the two page.goto calls to force uncached requests by passing headers: { 'Cache-Control': 'no-cache' } (i.e. call page.goto('/rsc-async-bundle', { headers: { 'Cache-Control': 'no-cache' } }) for the first and second visit) and then replace the final assertions to assert the IDs are different (compare firstBundleId and secondBundleId with expect(firstBundleId).not.toEqual(secondBundleId)) so the test validates uniqueness rather than just truthiness.
| import * as fs from 'node:fs/promises' | ||
| import * as path from 'node:path' | ||
|
|
||
| const rootDir = path.resolve(import.meta.dirname, '..') |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify the minimum Node version declared by the repo / CI.
rg -n --glob 'package.json' --glob '.nvmrc' --glob '.node-version' --glob '*.yml' --glob '*.yaml' \
'engines|node-version|setup-node|volta'Repository: TanStack/router
Length of output: 50371
🏁 Script executed:
#!/bin/bash
# Get root package.json engines
echo "=== ROOT package.json engines ==="
cat package.json | grep -A 2 '"engines"' | head -10
echo -e "\n=== e2e/eslint-plugin-start package.json engines ==="
if [ -f "e2e/eslint-plugin-start/package.json" ]; then
cat e2e/eslint-plugin-start/package.json | grep -A 2 '"engines"' | head -10
fi
echo -e "\n=== .nvmrc file ==="
if [ -f ".nvmrc" ]; then
cat .nvmrc
else
echo "No .nvmrc found at root"
fi
echo -e "\n=== .node-version file ==="
if [ -f ".node-version" ]; then
cat .node-version
else
echo "No .node-version found at root"
fi
echo -e "\n=== GitHub workflows Node version setup ==="
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -l "node-version" 2>/dev/null | head -5Repository: TanStack/router
Length of output: 351
🏁 Script executed:
#!/bin/bash
# Check GitHub workflows for Node version setup
echo "=== GitHub workflow node-version setup ==="
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep "node-version" 2>/dev/null | head -20
echo -e "\n=== Full e2e/eslint-plugin-start/package.json ==="
cat e2e/eslint-plugin-start/package.jsonRepository: TanStack/router
Length of output: 5138
Ensure CI Node version supports import.meta.dirname.
The script uses import.meta.dirname, which requires Node >=20.11.0 or >=21.2.0, but the GitHub workflow (check-skills.yml, validate-skills.yml) specifies only node-version: 20 without pinning to a minimum patch version. This means CI could run Node 20.0–20.10.x, which lacks import.meta.dirname support and will fail when generating fixtures.
Either update the workflows to enforce node-version: 20.11.0 (or higher), or derive the directory from import.meta.url instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/eslint-plugin-start/scripts/generate-perf-fixtures.mjs` at line 4, The
script uses import.meta.dirname to set rootDir (const rootDir =
path.resolve(import.meta.dirname, '..')), which fails on older Node 20.x;
replace that logic to derive the directory from import.meta.url instead so it
works across Node >= 12+ and doesn't require a patched Node 20 — locate the
rootDir assignment in generate-perf-fixtures.mjs and compute the directory using
import.meta.url (e.g., converting the URL to a file path and resolving '..') so
CI won't break on lower patch versions.
| const writes = [] | ||
| for (let i = 0; i < count; i++) { | ||
| const id = String(i).padStart(5, '0') | ||
| const file = path.join(noiseDir, `noise-${id}.ts`) | ||
| // No route options, no server roots, no 'use client' | ||
| const contents = | ||
| noiseComplexity === 'complex' | ||
| ? generateComplexNoise(id, i) | ||
| : `export const noise_${id} = ${i} as const\n` | ||
| writes.push(fs.writeFile(file, contents, 'utf8')) | ||
| } | ||
| await Promise.all(writes) |
There was a problem hiding this comment.
Cap write concurrency in the fixture generators.
Huge mode can enqueue thousands of fs.writeFile calls at once here, which risks EMFILE in CI and turns the perf run into a file-descriptor stress test instead of an ESLint benchmark. Flush in batches or write sequentially.
Suggested fix
+async function flushWrites(writes, batchSize = 100) {
+ for (let i = 0; i < writes.length; i += batchSize) {
+ await Promise.all(writes.slice(i, i + batchSize))
+ }
+}
+
async function writeNoise(baseDir, count) {
const noiseDir = path.join(baseDir, 'noise')
await fs.mkdir(noiseDir, { recursive: true })
const writes = []
for (let i = 0; i < count; i++) {
@@
writes.push(fs.writeFile(file, contents, 'utf8'))
}
- await Promise.all(writes)
+ await flushWrites(writes)
}
@@
- await Promise.all(writes)
+ await flushWrites(writes)
}
@@
- await Promise.all(writes)
+ await flushWrites(writes)
}Also applies to: 235-260, 275-300
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/eslint-plugin-start/scripts/generate-perf-fixtures.mjs` around lines 69 -
80, The loop that pushes thousands of fs.writeFile promises (writes array in
generate-perf-fixtures.mjs) can hit EMFILE; change the implementation to limit
concurrency by flushing writes in bounded batches (or run writes sequentially).
Replace the current pattern of collecting all promises then awaiting
Promise.all(writes) with either (a) chunked Promise.all calls using a fixed
BATCH_SIZE (e.g., 50–200) and awaiting each batch before continuing, or (b) an
async for-loop that awaits each fs.writeFile before continuing; update the loops
that build writes (the three sections around the shown block and the other
ranges 235-260 and 275-300) to use the same batched/sequential write strategy so
file descriptors are capped. Ensure you still write files with correct contents
generated by generateComplexNoise(id, i) or the simple export string.
| .inputValidator((data: { title?: string; delayMs?: number }) => data) | ||
| .handler(async ({ data }) => { | ||
| await new Promise((resolve) => setTimeout(resolve, data.delayMs ?? 150)) |
There was a problem hiding this comment.
Validate and clamp delayMs before using it in setTimeout.
Line 10 uses request data directly as timer duration. Add runtime coercion + bounds to avoid invalid or abusive values.
🛡️ Proposed fix
export const getNoLoaderCssServerComponent = createServerFn({
method: 'GET',
})
- .inputValidator((data: { title?: string; delayMs?: number }) => data)
+ .inputValidator((data: { title?: string; delayMs?: number }) => {
+ const title = typeof data.title === 'string' ? data.title : undefined
+ const delayMs =
+ typeof data.delayMs === 'number' && Number.isFinite(data.delayMs)
+ ? Math.min(Math.max(data.delayMs, 0), 5_000)
+ : 150
+
+ return { title, delayMs }
+ })
.handler(async ({ data }) => {
- await new Promise((resolve) => setTimeout(resolve, data.delayMs ?? 150))
+ await new Promise((resolve) => setTimeout(resolve, data.delayMs))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc-query/src/utils/noLoaderCssServerComponent.tsx` around
lines 8 - 10, The handler currently passes data.delayMs directly into
setTimeout; coerce and clamp delayMs inside the .handler callback before
awaiting the Promise: read delayMs from the incoming data (from the
.inputValidator/.handler scope), convert it to a finite number (Number(...) or
parseInt), fallback to 150 when missing/NaN, and clamp into safe bounds (e.g.,
min 0, max 5000) to prevent negative, NaN or abusive durations, then use that
sanitized value in setTimeout.
| import { expect } from '@playwright/test' | ||
| import { test } from '@tanstack/router-e2e-utils' | ||
|
|
||
| const HYDRATION_WAIT = 1000 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n e2e/react-start/rsc-query/tests/rsc-query.spec.ts | head -100Repository: TanStack/router
Length of output: 4216
🏁 Script executed:
cat -n e2e/react-start/rsc-query/tests/rsc-query.spec.ts | tail -n +100Repository: TanStack/router
Length of output: 4001
Replace the fixed hydration sleeps with state-based waits.
The repeated waitForTimeout(HYDRATION_WAIT) calls (lines 34, 47, 82, 109, 147, 183) make this suite slower on fast runs and flaky on slower CI. Wait for a deterministic hydrated control or post-hydration assertion instead of sleeping.
Example adjustment
-const HYDRATION_WAIT = 1000
-
test('client slot renders interactive add-to-cart widget', async ({ page }) => {
await page.goto('/rsc-query')
await page.waitForURL('/rsc-query')
- await page.waitForTimeout(HYDRATION_WAIT)
// Client slot should render
await expect(page.getByTestId('client-slot')).toBeVisible()
+ await expect(page.getByTestId('add-to-cart-btn')).toBeEnabled()
await expect(page.getByTestId('quantity-value')).toHaveText('1')
await expect(page.getByTestId('add-to-cart-btn')).toContainText(
'Add to Cart',
)
})Also, the "preserves React Query cache" test (lines 142–176) only verifies both timestamps contain 'Fetched:' but doesn't assert they are equal. A full refetch would pass this assertion. Compare the timestamps directly to confirm cache reuse.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc-query/tests/rsc-query.spec.ts` at line 4, Replace the
fixed HYDRATION_WAIT sleep strategy: remove usages of HYDRATION_WAIT and
waitForTimeout in the tests and instead wait for deterministic post-hydration
signals (e.g., use page.waitForSelector / waitForFunction or expect on a
specific hydrated control/DOM text change used by your app) where those waits
currently occur (search for HYDRATION_WAIT and waitForTimeout in the spec). For
the "preserves React Query cache" test, update the assertions so you capture
both timestamp values (the two elements currently checked for containing
'Fetched:') and assert they are equal to ensure the cache was reused rather than
just refetched. Ensure you target the specific test block named "preserves React
Query cache" and replace the sleep-based timings with state-based waits and a
direct equality assertion of the two timestamps.
| // RSC timestamp should be the SAME (React Query cache hit) | ||
| // Loosen to seconds to avoid spurious diffs. | ||
| const secondTimestamp = await page | ||
| .getByTestId('rsc-server-timestamp') | ||
| .textContent() | ||
|
|
||
| // If cache hit, we should not see dramatic changes; clock tick ok. | ||
| expect(secondTimestamp).toContain('Fetched:') | ||
| expect(firstTimestamp).toContain('Fetched:') |
There was a problem hiding this comment.
This test no longer proves the cache was reused.
The current assertions only show that both renders expose a Fetched: label; a full refetch would still pass. Please compare a stable server-side token/timestamp across the round-trip so this actually fails on cache misses.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc-query/tests/rsc-query.spec.ts` around lines 167 - 175,
The test currently only asserts both renders contain 'Fetched:' which doesn't
prove cache reuse; change it to capture and compare a stable server-side value
(e.g. the element with data-testid 'rsc-server-timestamp' or preferably a
dedicated 'rsc-server-token') between the first and second render: read
firstTimestamp/token into a variable (firstTimestamp), perform the round-trip
action, read the secondTimestamp/token (secondTimestamp), and assert that
firstTimestamp === secondTimestamp (or contains the same stable token), rather
than only checking for the 'Fetched:' label, so the test will fail on cache
misses; update references to the variables firstTimestamp and secondTimestamp
and the testid string 'rsc-server-timestamp' (or add/use 'rsc-server-token')
accordingly.
| const pageSize = 3 | ||
| const startIndex = (data.page - 1) * pageSize | ||
| const results = filtered.slice(startIndex, startIndex + pageSize) | ||
| const totalPages = Math.ceil(filtered.length / pageSize) | ||
|
|
There was a problem hiding this comment.
Clamp and sanitize page before paginating.
Right now a URL like ?page=0, ?page=-1, ?page=2.5, or any zero-result query can produce negative slice indexes and metadata such as Showing page 1 of 0. Normalize the search param to a positive integer first, then clamp the effective page against at least one total page before computing startIndex.
Suggested fix
- const pageSize = 3
- const startIndex = (data.page - 1) * pageSize
- const results = filtered.slice(startIndex, startIndex + pageSize)
- const totalPages = Math.ceil(filtered.length / pageSize)
+ const pageSize = 3
+ const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize))
+ const page = Math.min(Math.max(1, data.page), totalPages)
+ const startIndex = (page - 1) * pageSize
+ const results = filtered.slice(startIndex, startIndex + pageSize)
@@
- Showing page {data.page} of {totalPages} ({filtered.length} total
+ Showing page {page} of {totalPages} ({filtered.length} total
results)
@@
- validateSearch: (search: Record<string, unknown>): SearchParams => ({
- q: (search.q as string) || '',
- page: Number(search.page) || 1,
- }),
+ validateSearch: (search: Record<string, unknown>): SearchParams => ({
+ q: typeof search.q === 'string' ? search.q : '',
+ page:
+ typeof search.page === 'string' &&
+ Number.isInteger(Number(search.page)) &&
+ Number(search.page) > 0
+ ? Number(search.page)
+ : 1,
+ }),As per coding guidelines, **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.
Also applies to: 99-100, 157-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-invalidation.tsx` around lines 55 - 59,
Normalize and clamp the incoming page value before using it for pagination:
parse and coerce data.page into a positive integer (e.g., via Number/parseInt
and Math.floor or equivalent) then compute totalPages = Math.max(1,
Math.ceil(filtered.length / pageSize)); clamp the normalized page to the range
[1, totalPages]; only then compute startIndex = (clampedPage - 1) * pageSize and
slice filtered to produce results. Update references to data.page to use the
sanitized/clamped page variable so metadata (e.g., "Showing page X of Y") and
slice bounds cannot become negative or fractional.
| {values.map((item) => ( | ||
| <div | ||
| key={item.label} | ||
| data-testid={`bar-${item.label.toLowerCase()}`} | ||
| onClick={() => setSelectedReferrer(item.label)} | ||
| style={{ | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| gap: '12px', | ||
| marginBottom: '8px', | ||
| cursor: 'pointer', | ||
| padding: '8px', | ||
| borderRadius: '6px', | ||
| backgroundColor: | ||
| selectedReferrer === item.label | ||
| ? '#bbf7d0' | ||
| : 'transparent', | ||
| transition: 'background-color 0.2s', | ||
| }} | ||
| > | ||
| <div | ||
| style={{ | ||
| width: '80px', | ||
| fontSize: '13px', | ||
| color: '#166534', | ||
| fontWeight: | ||
| selectedReferrer === item.label ? 'bold' : 'normal', | ||
| }} | ||
| > | ||
| {item.label} | ||
| </div> | ||
| <div | ||
| style={{ | ||
| height: '24px', | ||
| width: `${(item.value / 100) * maxBarWidth}px`, | ||
| backgroundColor: | ||
| selectedReferrer === item.label ? '#16a34a' : '#86efac', | ||
| borderRadius: '4px', | ||
| transition: 'width 0.3s, background-color 0.2s', | ||
| }} | ||
| /> | ||
| <div style={{ fontSize: '13px', color: '#166534' }}> | ||
| {item.value}% | ||
| </div> | ||
| </div> | ||
| ))} |
There was a problem hiding this comment.
Use a semantic button for the selectable referrer bars.
These rows are click-only <div>s, so keyboard users cannot focus them or open the details panel. Since selecting a referrer is the main interaction in this demo, this should be a real control.
Suggested fix
- {values.map((item) => (
- <div
+ {values.map((item) => (
+ <button
+ type="button"
key={item.label}
data-testid={`bar-${item.label.toLowerCase()}`}
onClick={() => setSelectedReferrer(item.label)}
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
marginBottom: '8px',
cursor: 'pointer',
padding: '8px',
borderRadius: '6px',
+ border: 'none',
+ width: '100%',
+ textAlign: 'left',
backgroundColor:
selectedReferrer === item.label
? '#bbf7d0'
: 'transparent',
transition: 'background-color 0.2s',
}}
>
@@
- </div>
+ </button>
))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-ssr-data-only.tsx` around lines 275 - 320,
Rows rendered inside values.map use clickable <div>s; replace the outer
clickable div with a semantic <button type="button"> (keeping key={item.label}
and data-testid={`bar-${item.label.toLowerCase()}`}) so keyboard users can focus
and activate it, wire its onClick to setSelectedReferrer(item.label), add
aria-pressed={selectedReferrer === item.label} to reflect state, preserve the
inline styles (or move them to a class) but ensure the button resets default
browser styles and retains the visual focus state, and keep the inner label, bar
and percent elements as-is so the interaction and visuals remain identical.
| try { | ||
| const generator = await streamNotificationsGenerator({ | ||
| data: { initialCount: 3, streamCount: 4, delayMs: 800 }, | ||
| }) | ||
|
|
||
| for await (const notification of generator) { | ||
| setNotifications((prev) => [...prev, notification as NotificationRSC]) | ||
| } | ||
| } finally { | ||
| setIsStreaming(false) | ||
| setStreamComplete(true) |
There was a problem hiding this comment.
Don't mark errored streams as complete.
streamComplete is set in finally, so a thrown generator or aborted request still renders the success banner. Track successful exhaustion separately and reserve the final state for a real completion path.
Suggested fix
+ const [streamError, setStreamError] = React.useState<string | null>(null)
+
const startStreaming = React.useCallback(async () => {
+ let completed = false
setNotifications([])
setIsStreaming(true)
setStreamComplete(false)
+ setStreamError(null)
setDismissedIds(new Set())
setStartTime(Date.now())
try {
const generator = await streamNotificationsGenerator({
data: { initialCount: 3, streamCount: 4, delayMs: 800 },
})
for await (const notification of generator) {
setNotifications((prev) => [...prev, notification as NotificationRSC])
}
+ completed = true
+ } catch (err) {
+ setStreamError(
+ err instanceof Error ? err.message : 'Streaming notifications failed',
+ )
} finally {
setIsStreaming(false)
- setStreamComplete(true)
+ setStreamComplete(completed)
}
}, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx` around lines 40 -
50, The current finally block sets setStreamComplete(true) even when the
generator throws or the request is aborted; change the control flow so only a
successful, natural exhaustion of the async generator marks completion: keep
setIsStreaming(false) in a finally, but move setStreamComplete(true) out of
finally and into the normal completion path (e.g., immediately after the
for-await loop inside the try or after detecting the generator finished without
error), and add a catch to handle errors/aborts (do not call setStreamComplete
there; optionally set an error state). Refer to streamNotificationsGenerator,
the for-await loop that iterates notifications, and state setters
setIsStreaming, setStreamComplete, and setNotifications to make the change.
| const stream = await streamNotificationsReadable({ | ||
| data: { initialCount: 3, streamCount: 4, delayMs: 800 }, | ||
| }) | ||
|
|
||
| if (!stream) return | ||
|
|
||
| const reader = stream.getReader() | ||
| let done = false | ||
|
|
||
| while (!done) { | ||
| const result = await reader.read() | ||
| done = result.done | ||
| if (result.value) { | ||
| setNotifications((prev) => [...prev, result.value as NotificationRSC]) | ||
| } | ||
| } | ||
| } finally { | ||
| setIsStreaming(false) | ||
| setStreamComplete(true) |
There was a problem hiding this comment.
A missing or failed stream currently shows as a successful completion.
The !stream early return and any read error both still flow through finally, which sets streamComplete to true. That makes the UI claim the stream finished even when nothing was read or the read failed.
Suggested fix
+ const [streamError, setStreamError] = React.useState<string | null>(null)
+
const startStreaming = React.useCallback(async () => {
+ let completed = false
setNotifications([])
setIsStreaming(true)
setStreamComplete(false)
+ setStreamError(null)
setExpandedIds(new Set())
setStartTime(Date.now())
try {
const stream = await streamNotificationsReadable({
data: { initialCount: 3, streamCount: 4, delayMs: 800 },
})
- if (!stream) return
+ if (!stream) {
+ throw new Error('No stream returned')
+ }
const reader = stream.getReader()
let done = false
while (!done) {
const result = await reader.read()
done = result.done
if (result.value) {
setNotifications((prev) => [...prev, result.value as NotificationRSC])
}
}
+ completed = true
+ } catch (err) {
+ setStreamError(
+ err instanceof Error ? err.message : 'Streaming notifications failed',
+ )
} finally {
setIsStreaming(false)
- setStreamComplete(true)
+ setStreamComplete(completed)
}
}, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx` around lines 41 - 59,
The stream early-return and read errors still hit the finally block which
unconditionally sets setStreamComplete(true); change this so
setStreamComplete(true) is only called when a stream existed and was fully read
without errors: create a local flag (e.g., completedSuccessfully) initialized
false, set it to true after the while loop finishes normally (after
reader.read() returns done), move setStreamComplete(true) out of the finally
into the success path, and in finally only call setIsStreaming(false); also
ensure the !stream early return does not mark completion and any catch block
marks completion as false or leaves it unset.
| const streamingExamples = [ | ||
| { | ||
| path: '/rsc-stream-readable', | ||
| title: 'ReadableStream Pattern', | ||
| description: | ||
| 'Stream RSCs using the ReadableStream API. Click a button to start streaming notifications from the server. Uses reader.read() loop pattern.', | ||
| icon: '📖', | ||
| pattern: 'ReadableStream + reader.read()', | ||
| }, | ||
| { | ||
| path: '/rsc-stream-generator', | ||
| title: 'Async Generator Pattern', | ||
| description: | ||
| 'Stream RSCs using async generator functions. Cleaner syntax with for-await-of loops. Same result, more readable code.', | ||
| icon: '🔄', | ||
| pattern: 'async function* + for-await-of', | ||
| }, | ||
| { | ||
| path: '/rsc-stream-loader', | ||
| title: 'Loader Streaming', | ||
| description: | ||
| 'Stream RSCs from the route loader. The stream is returned from the loader and consumed progressively in the component - works for both SSR and client-side navigation.', | ||
| icon: '📦', | ||
| pattern: 'loader + for-await-of', | ||
| }, | ||
| ] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the mismatch between declared paths and the forced single-literal assertion.
rg -n "path: '/rsc-stream-(readable|generator|loader)'|to=\{example\.path as '/rsc-stream-readable'\}" --type ts --type tsxRepository: TanStack/router
Length of output: 86
🏁 Script executed:
# First, locate the file
find . -name "rsc-streaming.tsx" -type fRepository: TanStack/router
Length of output: 109
🏁 Script executed:
cat -n ./e2e/react-start/rsc/src/routes/rsc-streaming.tsxRepository: TanStack/router
Length of output: 9369
🏁 Script executed:
# Check for ESLint config files and sort-imports rules
find . -maxdepth 3 -name ".eslintrc*" -o -name "eslint.config.*" | head -5Repository: TanStack/router
Length of output: 246
🏁 Script executed:
# Check the main eslint config for sort-imports rules
cat ./eslint.config.js | head -100Repository: TanStack/router
Length of output: 818
🏁 Script executed:
# Check what's in tanstackConfig - look for imports-related rules
rg "sort-imports" --type jsRepository: TanStack/router
Length of output: 41
🏁 Script executed:
# Verify the type safety issue: check if the cast bypasses proper typing
# by looking for route definitions in the router
fd -t f "rsc-stream" -e tsx -e ts | head -10Repository: TanStack/router
Length of output: 483
🏁 Script executed:
# Double-check: verify the Link component type signature to understand the type safety impact
rg -A 10 "interface.*Link.*props" --type ts --type tsx | head -30Repository: TanStack/router
Length of output: 86
🏁 Script executed:
# Search for Link component type definition without type constraints
rg -A 10 "type.*LinkProps|interface.*LinkProps" --type ts | head -40Repository: TanStack/router
Length of output: 2664
🏁 Script executed:
# Check React router Link type signature in packages/react-router
rg -A 5 "interface LinkProps|type LinkProps" packages/react-router/src --type ts | head -50Repository: TanStack/router
Length of output: 924
🏁 Script executed:
# Get more details on the LinkProps and to prop typing
rg -B 5 -A 20 "export type LinkProps<" packages/react-router/src/link.tsxRepository: TanStack/router
Length of output: 930
🏁 Script executed:
# Check the ActiveLinkOptions to understand the to prop typing
rg -B 5 -A 30 "type ActiveLinkOptions<" packages/react-router/src/link.tsx | head -60Repository: TanStack/router
Length of output: 1360
🏁 Script executed:
# Check LinkOptions to understand the to prop type constraints
rg -B 5 -A 30 "type LinkOptions<" packages/react-router/src/link.tsx | head -80Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Search for LinkOptions definition across all packages
rg "type LinkOptions<" packages/react-router/src --type ts | head -10Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Search for LinkOptions in router-core
rg "type LinkOptions" packages/ --type ts | head -10Repository: TanStack/router
Length of output: 535
🏁 Script executed:
# Check LinkOptions in router-core
rg -A 25 "export type LinkOptions<" packages/router-core/src/link.tsRepository: TanStack/router
Length of output: 412
🏁 Script executed:
# Check NavigateOptions to see the to prop typing
rg -B 5 -A 30 "export type NavigateOptions<" packages/router-core/src/ --type ts | head -100Repository: TanStack/router
Length of output: 3237
Remove the unsafe single-literal cast on Link.to.
Line 179 narrows every route path to '/rsc-stream-readable' even though the array contains three distinct paths: '/rsc-stream-readable', '/rsc-stream-generator', and '/rsc-stream-loader'. This defeats strict typing and violates the TypeScript strict mode requirement.
Suggested fix
+type StreamingExamplePath =
+ | '/rsc-stream-readable'
+ | '/rsc-stream-generator'
+ | '/rsc-stream-loader'
+
+type StreamingExample = {
+ path: StreamingExamplePath
+ title: string
+ description: string
+ icon: string
+ pattern: string
+}
+
-const streamingExamples = [
+const streamingExamples: ReadonlyArray<StreamingExample> = [
@@
- to={example.path as '/rsc-stream-readable'}
+ to={example.path}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-streaming.tsx` around lines 12 - 37, The
code narrows every route path to the single literal '/rsc-stream-readable' via
an unsafe cast; fix by typing the streamingExamples array so each item’s path is
a general string (e.g., annotate streamingExamples as Array<{ path: string;
title: string; description: string; icon: string; pattern: string }>) and remove
the literal cast where you set Link.to — use example.path directly (reference:
streamingExamples, the path property, and Link.to) so TypeScript preserves the
actual three distinct paths.
There was a problem hiding this comment.
Important
At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.
Nx Cloud is proposing a fix for your failed CI:
These changes fix three CI failures introduced by the new RSC/ESLint plugin work. We add the missing vite/types.ts build entry so publint can find the exported file, raise the ESLint rule test timeout to accommodate TypeScript program initialisation overhead, and correct a closure bug where needsDevBaseRewrite was always false at plugin construction time, breaking the split-base-and-basepath dev server middleware.
Tip
✅ We verified this fix by re-running @tanstack/start-plugin-core:test:build, @tanstack/eslint-plugin-start:test:unit.
Suggested Fix changes
diff --git a/packages/eslint-plugin-start/vite.config.ts b/packages/eslint-plugin-start/vite.config.ts
index 8769e2779e..2a58ae4d7f 100644
--- a/packages/eslint-plugin-start/vite.config.ts
+++ b/packages/eslint-plugin-start/vite.config.ts
@@ -8,6 +8,7 @@ const config = defineConfig({
dir: './src',
watch: false,
globals: true,
+ testTimeout: 30000,
typecheck: { enabled: true },
restoreMocks: true,
},
diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json
index 00148a0303..192414ec16 100644
--- a/packages/start-plugin-core/package.json
+++ b/packages/start-plugin-core/package.json
@@ -45,16 +45,22 @@
"module": "dist/esm/index.js",
"exports": {
".": {
- "types": "./dist/esm/index.d.ts",
- "default": "./dist/esm/index.js"
+ "import": {
+ "types": "./dist/esm/index.d.ts",
+ "default": "./dist/esm/index.js"
+ }
},
"./utils": {
- "types": "./dist/esm/utils.d.ts",
- "default": "./dist/esm/utils.js"
+ "import": {
+ "types": "./dist/esm/utils.d.ts",
+ "default": "./dist/esm/utils.js"
+ }
},
"./vite/types": {
- "types": "./dist/esm/vite/types.d.ts",
- "default": "./dist/esm/vite/types.js"
+ "import": {
+ "types": "./dist/esm/vite/types.d.ts",
+ "default": "./dist/esm/vite/types.js"
+ }
},
"./package.json": "./package.json"
},
diff --git a/packages/start-plugin-core/src/vite/plugin.ts b/packages/start-plugin-core/src/vite/plugin.ts
index 37407560d2..957d0b9ec2 100644
--- a/packages/start-plugin-core/src/vite/plugin.ts
+++ b/packages/start-plugin-core/src/vite/plugin.ts
@@ -248,7 +248,7 @@ export function tanStackStartVite(
// Registered BEFORE devServerPlugin so this middleware is added to the Connect stack first,
// ensuring all subsequent middlewares (CSS, SSR, etc.) see the rewritten URL.
createDevBaseRewritePlugin({
- needsDevBaseRewrite,
+ needsDevBaseRewrite: () => needsDevBaseRewrite,
resolvedStartConfig,
}),
devServerPlugin({
diff --git a/packages/start-plugin-core/src/vite/plugins.ts b/packages/start-plugin-core/src/vite/plugins.ts
index 7d1eb2ccec..9226caf36f 100644
--- a/packages/start-plugin-core/src/vite/plugins.ts
+++ b/packages/start-plugin-core/src/vite/plugins.ts
@@ -29,13 +29,13 @@ export function createPostBuildPlugin(opts: {
}
export function createDevBaseRewritePlugin(opts: {
- needsDevBaseRewrite: boolean
+ needsDevBaseRewrite: () => boolean
resolvedStartConfig: ResolvedStartConfig
}): PluginOption {
return {
name: 'tanstack-start-core:dev-base-rewrite',
configureServer(server) {
- if (!opts.needsDevBaseRewrite) {
+ if (!opts.needsDevBaseRewrite()) {
return
}
diff --git a/packages/start-plugin-core/vite.config.ts b/packages/start-plugin-core/vite.config.ts
index ee363461f3..24c9feb60d 100644
--- a/packages/start-plugin-core/vite.config.ts
+++ b/packages/start-plugin-core/vite.config.ts
@@ -15,7 +15,7 @@ export default mergeConfig(
config,
tanstackViteConfig({
tsconfigPath: './tsconfig.build.json',
- entry: ['./src/index.ts', './src/utils.ts'],
+ entry: ['./src/index.ts', './src/utils.ts', './src/vite/types.ts'],
srcDir: './src',
outDir: './dist',
cjs: false,
Or Apply changes locally with:
npx nx-cloud apply-locally gFKP-UVxX
Apply fix locally with your editor ↗ View interactive diff ↗
🎓 Learn more about Self-Healing CI on nx.dev
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (2)
e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx (1)
34-61:⚠️ Potential issue | 🟠 MajorOnly mark stream completion after a successful full read.
Line 46 (
if (!stream) return) and read failures still go throughfinally, but Line 60 always sets completion totrue. This can show a false “COMPLETE” state.Proposed fix
const startStreaming = React.useCallback(async () => { + let completedSuccessfully = false setNotifications([]) setIsStreaming(true) setStreamComplete(false) setExpandedIds(new Set()) setStartTime(Date.now()) @@ while (!done) { const result = await reader.read() done = result.done if (result.value) { setNotifications((prev) => [...prev, result.value as NotificationRSC]) } } + completedSuccessfully = true } finally { setIsStreaming(false) - setStreamComplete(true) + setStreamComplete(completedSuccessfully) } }, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx` around lines 34 - 61, The code currently sets setStreamComplete(true) in the finally block of startStreaming, which marks completion even if the stream is null or reading failed; remove setStreamComplete(true) from the finally block and instead set setStreamComplete(true) only after a successful full read (i.e., after the while loop that reads from reader completes without errors) inside the try block; keep setIsStreaming(false) in finally so the streaming flag is always cleared, but only flip setStreamComplete(true) in startStreaming once done === true and no exceptions occurred.e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx (1)
41-52:⚠️ Potential issue | 🟠 MajorOnly mark the stream complete after successful exhaustion.
setStreamComplete(true)still runs fromfinally, so errored or aborted streams render the success banner. Keep the teardown infinally, but move completion into the normal success path and handle failures separately.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx` around lines 41 - 52, The current for-await loop always sets setStreamComplete(true) in the finally block, which marks aborted/errored streams as successful; change the control flow so the teardown (setIsStreaming(false)) remains in finally but move setStreamComplete(true) into the successful path after the for-await loop completes normally, and add a catch block to handle errors/aborts (using streamNotificationsGenerator and the generator variable) so failures do not call setStreamComplete; reference the existing setIsStreaming, setStreamComplete, streamNotificationsGenerator, and the for-await (for await (const notification of generator)) when applying the change.
🧹 Nitpick comments (7)
e2e/react-start/rsc/src/routes/rsc-flight-api.tsx (1)
68-69: Remove unnecessaryReactNodeassertions.Line 69 and Line 92 cast to
React.ReactNodeafter decode; these casts are redundant and reduce type-safety clarity.Proposed fix
- setResponsePromise(Promise.resolve(tree as React.ReactNode)) + setResponsePromise(Promise.resolve(tree))- setApiPromise(Promise.resolve(tree as React.ReactNode)) + setApiPromise(Promise.resolve(tree))As per coding guidelines "
**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety".Also applies to: 91-92
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx` around lines 68 - 69, Remove the redundant React.ReactNode type assertions after decoding: when calling createFromFetch and assigning to state use the decoded value directly (e.g., omit "as React.ReactNode" in the setResponsePromise call and the other similar cast). Locate the createFromFetch call and the setResponsePromise invocation (and the second instance near the later decode) and change them to pass the resulting value without casting so TypeScript's inferred types are preserved and strict-mode type-safety is maintained.e2e/react-start/rsc/tests/rsc-bundle.spec.ts (2)
18-18: Unused variableslowLoading.The variable
slowLoadingis declared but never used. It appears to be leftover from incomplete test logic.🧹 Remove unused variable or add assertion
Either remove the unused variable:
- const slowLoading = page.getByTestId('loading-slow') - // Wait for the slow component to finish loading (1500ms + buffer)Or use it to verify the loading state appears before resolution:
const slowLoading = page.getByTestId('loading-slow') + // Optionally verify loading state is visible initially + // await expect(slowLoading).toBeVisible() // Wait for the slow component to finish loading (1500ms + buffer)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-bundle.spec.ts` at line 18, Remove the unused variable slowLoading from the rsc-bundle.spec.ts test or actually use it in an assertion; locate the declaration of slowLoading in the test (near the test that queries page.getByTestId('rsc-bundle-logo')) and either delete that variable declaration or replace its usage with a meaningful assertion that verifies the loading state (e.g., assert the loading indicator appears/disappears using the same test IDs) so no unused variables remain.
139-160: Test name doesn't match assertion behavior.The test is named "Multiple bundle page visits get new bundle IDs" but only asserts that bundle IDs are truthy, not that they differ. The comment on line 158 acknowledges this limitation. Consider either:
- Renaming the test to reflect what it actually verifies (e.g., "Bundle structure persists across page visits")
- Adding an assertion that the IDs differ (if that's the intended behavior)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-bundle.spec.ts` around lines 139 - 160, The test named "Multiple bundle page visits get new bundle IDs" (the test block creating firstBundleId and secondBundleId) doesn't assert they differ—only truthiness—so either rename the test to match its current checks (e.g., "Bundle structure persists across page visits") or add a clear assertion that the IDs are different by asserting expect(firstBundleId).not.toBe(secondBundleId) (or a timestamp difference assertion if the intended invariant is a fresh timestamp on reload); update the test title or add the not-to-be assertion accordingly and keep the existing navigation/variable names (firstBundleId, secondBundleId) unchanged.e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx (1)
233-239: Inline keyframes may cause duplicate style injection.Each
LoadingFallbackinstance injects the same@keyframes spinrule. For demo/test code this is fine, but in production consider extracting to a shared stylesheet or using CSS modules.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx` around lines 233 - 239, The inline `@keyframes` spin is being injected by each LoadingFallback instance causing duplicate style rules; modify LoadingFallback to remove the inline <style> block and instead reference a single shared definition (e.g., move the `@keyframes` spin and associated .spinner class into a global stylesheet or a CSS module and import it into rsc-async-bundle.tsx), or scope it to a uniquely named keyframe in a module to avoid duplicates; update LoadingFallback to use the shared CSS class (the spinner element) so the keyframes are defined once rather than per instance.e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts (3)
28-94: Consider consolidating overlapping tests.Tests "All three RSCs render after loading completes" (lines 28-59) and "All RSCs eventually load and are visible" (lines 61-94) have significant overlap - both navigate to the page, wait for all three RSCs, and verify their content. The main difference is the icon verification in the second test.
Consider merging these into a single comprehensive test or making their purposes more distinct.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 28 - 94, The two tests "All three RSCs render after loading completes" and "All RSCs eventually load and are visible" duplicate navigation and visibility checks for the same test ids (async-fast-wrapper/getByTestId('rsc-async-fast'), async-medium-wrapper/getByTestId('rsc-async-medium'), async-slow-wrapper/getByTestId('rsc-async-slow')) — consolidate them into one comprehensive test that does a single page.goto('/rsc-async-bundle'), waits for each wrapper/rsc toBeVisible, then asserts both titles (rsc-async-fast-title, rsc-async-medium-title, rsc-async-slow-title) and icons (rsc-async-fast-icon, rsc-async-medium-icon, rsc-async-slow-icon); alternatively, if you want two tests, make one specifically about load ordering/visibility and the other only about icon content and remove the duplicated visibility waits from the icon-only test.
255-283: Test name doesn't match assertion behavior (same as rsc-bundle.spec.ts).Similar to the corresponding test in
rsc-bundle.spec.ts, this test is named "Multiple page visits get new bundle IDs" but only asserts that bundle IDs are truthy, not that they differ between visits.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 255 - 283, The test named "Multiple page visits get new bundle IDs" currently only checks truthiness of firstBundleId and secondBundleId; update it so the behavior matches the name by asserting the two values differ (e.g., after the existing truthy checks add expect(firstBundleId).not.toEqual(secondBundleId)), or alternatively rename the test to reflect that it only verifies presence (e.g., "Multiple page visits have bundle IDs"); refer to the test block declaration and the variables firstBundleId and secondBundleId when making the change.
53-54: Remove unnecessary.first()calls on test ID selectors.The route file contains each test ID exactly once (
rsc-async-medium-title,rsc-async-medium-timestamp,rsc-async-medium-icon,rsc-async-medium-slot). The.first()calls on these locators are unnecessary and add needless complexity. Since there are no duplicate elements with the same test IDs, simply usepage.getByTestId('rsc-async-medium-title')instead ofpage.getByTestId('rsc-async-medium-title').first().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 53 - 54, The tests use unnecessary .first() on Playwright locators like page.getByTestId('rsc-async-medium-title').first(); remove the .first() and call page.getByTestId('rsc-async-medium-title') (and do the same for 'rsc-async-medium-timestamp', 'rsc-async-medium-icon', 'rsc-async-medium-slot') in the rsc-async-bundle.spec.ts assertions so each expectation directly uses the locator without .first().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx`:
- Around line 70-72: Replace the catch parameter type from any to unknown in
both catch blocks that call setResponseError, and add a type guard to safely
extract the error message: check if the caught value is an instance of Error (or
has a string message) and pass that to setResponseError, otherwise call
setResponseError(String(e)); update the catch signatures (e: unknown) and the
body where setResponseError is set (references: setResponseError in
rsc-flight-api.tsx) accordingly.
In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx`:
- Around line 21-22: Replace the any-based NotificationRSC type with the
concrete notification type exported by `@tanstack/react-start/rsc`: import the
appropriate type (e.g., Notification or NotificationRSCType) from
'@tanstack/react-start/rsc' and use it in place of the current "type
NotificationRSC = any"; then ensure streamNotificationsGenerator's output and
the CompositeComponent props are annotated to that imported type so the compiler
will check compatibility between streamNotificationsGenerator and
CompositeComponent.
In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx`:
- Around line 6-11: The import specifiers from ~/utils/styles are out of
alphabetical order and trigger the sort-imports lint; reorder the named imports
(pageStyles, clientStyles, serverStyles, asyncStyles, formatTime) into
alphabetical order — e.g. asyncStyles, clientStyles, formatTime, pageStyles,
serverStyles — so the import line in rsc-stream-readable.tsx conforms to the
linter.
- Around line 21-23: Replace the explicit any by inferring the stream item type
from streamNotificationsReadable: remove "type NotificationRSC = any" and
instead declare NotificationRSC using TypeScript conditional types that extract
the readable stream's item type from ReturnType<typeof
streamNotificationsReadable> (e.g., use Awaited<ReturnType<typeof
streamNotificationsReadable>> extends ReadableStream<infer T> ? T : unknown),
then remove the manual cast at the consumption site (where the stream item is
cast on line 55) so the compiler uses the inferred NotificationRSC type
everywhere.
In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts`:
- Around line 227-229: The test currently uses a full page load via
page.goto('/rsc-async-bundle') which doesn't exercise client-side navigation;
change the navigation to perform an in-app click (use
page.getByTestId('nav-async-bundle').click() or the existing nav selector used
in rsc-bundle.spec.ts) and then await page.waitForURL(/\/rsc-async-bundle/);
ensure the app includes a link with data-testid="nav-async-bundle" in the
navigation component so the click triggers client-side routing and the test
"Async RSCs render correctly after client-side navigation" validates SPA
navigation behavior.
---
Duplicate comments:
In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx`:
- Around line 41-52: The current for-await loop always sets
setStreamComplete(true) in the finally block, which marks aborted/errored
streams as successful; change the control flow so the teardown
(setIsStreaming(false)) remains in finally but move setStreamComplete(true) into
the successful path after the for-await loop completes normally, and add a catch
block to handle errors/aborts (using streamNotificationsGenerator and the
generator variable) so failures do not call setStreamComplete; reference the
existing setIsStreaming, setStreamComplete, streamNotificationsGenerator, and
the for-await (for await (const notification of generator)) when applying the
change.
In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx`:
- Around line 34-61: The code currently sets setStreamComplete(true) in the
finally block of startStreaming, which marks completion even if the stream is
null or reading failed; remove setStreamComplete(true) from the finally block
and instead set setStreamComplete(true) only after a successful full read (i.e.,
after the while loop that reads from reader completes without errors) inside the
try block; keep setIsStreaming(false) in finally so the streaming flag is always
cleared, but only flip setStreamComplete(true) in startStreaming once done ===
true and no exceptions occurred.
---
Nitpick comments:
In `@e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx`:
- Around line 233-239: The inline `@keyframes` spin is being injected by each
LoadingFallback instance causing duplicate style rules; modify LoadingFallback
to remove the inline <style> block and instead reference a single shared
definition (e.g., move the `@keyframes` spin and associated .spinner class into a
global stylesheet or a CSS module and import it into rsc-async-bundle.tsx), or
scope it to a uniquely named keyframe in a module to avoid duplicates; update
LoadingFallback to use the shared CSS class (the spinner element) so the
keyframes are defined once rather than per instance.
In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx`:
- Around line 68-69: Remove the redundant React.ReactNode type assertions after
decoding: when calling createFromFetch and assigning to state use the decoded
value directly (e.g., omit "as React.ReactNode" in the setResponsePromise call
and the other similar cast). Locate the createFromFetch call and the
setResponsePromise invocation (and the second instance near the later decode)
and change them to pass the resulting value without casting so TypeScript's
inferred types are preserved and strict-mode type-safety is maintained.
In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts`:
- Around line 28-94: The two tests "All three RSCs render after loading
completes" and "All RSCs eventually load and are visible" duplicate navigation
and visibility checks for the same test ids
(async-fast-wrapper/getByTestId('rsc-async-fast'),
async-medium-wrapper/getByTestId('rsc-async-medium'),
async-slow-wrapper/getByTestId('rsc-async-slow')) — consolidate them into one
comprehensive test that does a single page.goto('/rsc-async-bundle'), waits for
each wrapper/rsc toBeVisible, then asserts both titles (rsc-async-fast-title,
rsc-async-medium-title, rsc-async-slow-title) and icons (rsc-async-fast-icon,
rsc-async-medium-icon, rsc-async-slow-icon); alternatively, if you want two
tests, make one specifically about load ordering/visibility and the other only
about icon content and remove the duplicated visibility waits from the icon-only
test.
- Around line 255-283: The test named "Multiple page visits get new bundle IDs"
currently only checks truthiness of firstBundleId and secondBundleId; update it
so the behavior matches the name by asserting the two values differ (e.g., after
the existing truthy checks add
expect(firstBundleId).not.toEqual(secondBundleId)), or alternatively rename the
test to reflect that it only verifies presence (e.g., "Multiple page visits have
bundle IDs"); refer to the test block declaration and the variables
firstBundleId and secondBundleId when making the change.
- Around line 53-54: The tests use unnecessary .first() on Playwright locators
like page.getByTestId('rsc-async-medium-title').first(); remove the .first() and
call page.getByTestId('rsc-async-medium-title') (and do the same for
'rsc-async-medium-timestamp', 'rsc-async-medium-icon', 'rsc-async-medium-slot')
in the rsc-async-bundle.spec.ts assertions so each expectation directly uses the
locator without .first().
In `@e2e/react-start/rsc/tests/rsc-bundle.spec.ts`:
- Line 18: Remove the unused variable slowLoading from the rsc-bundle.spec.ts
test or actually use it in an assertion; locate the declaration of slowLoading
in the test (near the test that queries page.getByTestId('rsc-bundle-logo')) and
either delete that variable declaration or replace its usage with a meaningful
assertion that verifies the loading state (e.g., assert the loading indicator
appears/disappears using the same test IDs) so no unused variables remain.
- Around line 139-160: The test named "Multiple bundle page visits get new
bundle IDs" (the test block creating firstBundleId and secondBundleId) doesn't
assert they differ—only truthiness—so either rename the test to match its
current checks (e.g., "Bundle structure persists across page visits") or add a
clear assertion that the IDs are different by asserting
expect(firstBundleId).not.toBe(secondBundleId) (or a timestamp difference
assertion if the intended invariant is a fresh timestamp on reload); update the
test title or add the not-to-be assertion accordingly and keep the existing
navigation/variable names (firstBundleId, secondBundleId) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a6db7ac9-cf45-454e-be78-784eb6bdd3af
📒 Files selected for processing (39)
e2e/react-start/rsc/src/routes/__root.tsxe2e/react-start/rsc/src/routes/rsc-async-bundle.tsxe2e/react-start/rsc/src/routes/rsc-flight-api.tsxe2e/react-start/rsc/src/routes/rsc-stream-generator.tsxe2e/react-start/rsc/src/routes/rsc-stream-loader.tsxe2e/react-start/rsc/src/routes/rsc-stream-readable.tsxe2e/react-start/rsc/src/utils/ClientWidgetA.tsxe2e/react-start/rsc/src/utils/ClientWidgetB.tsxe2e/react-start/rsc/tests/rsc-async-bundle.spec.tse2e/react-start/rsc/tests/rsc-basic.spec.tse2e/react-start/rsc/tests/rsc-bundle.spec.tse2e/react-start/rsc/tests/rsc-caching.spec.tse2e/react-start/rsc/tests/rsc-client-preload.spec.tse2e/react-start/rsc/tests/rsc-component-slot.spec.tse2e/react-start/rsc/tests/rsc-context.spec.tse2e/react-start/rsc/tests/rsc-css-modules.spec.tse2e/react-start/rsc/tests/rsc-css-preload-complex.spec.tse2e/react-start/rsc/tests/rsc-deferred.spec.tse2e/react-start/rsc/tests/rsc-flight-api.spec.tse2e/react-start/rsc/tests/rsc-forms.spec.tse2e/react-start/rsc/tests/rsc-global-css.spec.tse2e/react-start/rsc/tests/rsc-hydration.spec.tse2e/react-start/rsc/tests/rsc-link.spec.tse2e/react-start/rsc/tests/rsc-nested-structure.spec.tse2e/react-start/rsc/tests/rsc-nested.spec.tse2e/react-start/rsc/tests/rsc-parallel-flash.spec.tse2e/react-start/rsc/tests/rsc-param-remount.spec.tse2e/react-start/rsc/tests/rsc-react-cache.spec.tse2e/react-start/rsc/tests/rsc-slot-jsx-args.spec.tse2e/react-start/rsc/tests/rsc-slots.spec.tse2e/react-start/rsc/tests/rsc-ssr-data-only.spec.tse2e/react-start/rsc/tests/rsc-ssr-false.spec.tse2e/react-start/rsc/tests/rsc-stream-flash.spec.tse2e/react-start/rsc/tests/rsc-stream-generator.spec.tse2e/react-start/rsc/tests/rsc-stream-loader.spec.tse2e/react-start/rsc/tests/rsc-stream-readable.spec.tse2e/react-start/rsc/tests/rsc-suspense.spec.tse2e/react-start/rsc/tests/rsc-tree.spec.tspackages/eslint-plugin-start/src/__tests__/no-async-client-component.rule.test.ts
✅ Files skipped from review due to trivial changes (2)
- e2e/react-start/rsc/src/routes/__root.tsx
- e2e/react-start/rsc/tests/rsc-basic.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- e2e/react-start/rsc/src/utils/ClientWidgetA.tsx
- e2e/react-start/rsc/src/routes/rsc-stream-loader.tsx
| } catch (e: any) { | ||
| setResponseError(e?.message || String(e)) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -100 e2e/react-start/rsc/src/routes/rsc-flight-api.tsx | tail -35Repository: TanStack/router
Length of output: 996
🏁 Script executed:
sed -n '65,100p' e2e/react-start/rsc/src/routes/rsc-flight-api.tsxRepository: TanStack/router
Length of output: 1006
Use unknown instead of any in error handlers for strict TypeScript compliance.
Lines 70 and 93 use any in catch handlers, which bypasses strict typing. Replace with unknown and add proper type guards:
Proposed fix
- } catch (e: any) {
- setResponseError(e?.message || String(e))
+ } catch (e: unknown) {
+ setResponseError(e instanceof Error ? e.message : String(e))
}- } catch (e: any) {
- setApiError(e?.message || String(e))
+ } catch (e: any) {
+ setApiError(e instanceof Error ? e.message : String(e))
}Per coding guideline **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-flight-api.tsx` around lines 70 - 72,
Replace the catch parameter type from any to unknown in both catch blocks that
call setResponseError, and add a type guard to safely extract the error message:
check if the caught value is an instance of Error (or has a string message) and
pass that to setResponseError, otherwise call setResponseError(String(e));
update the catch signatures (e: unknown) and the body where setResponseError is
set (references: setResponseError in rsc-flight-api.tsx) accordingly.
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type NotificationRSC = any |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# Read the specific file to see lines 21-22 and surrounding context
head -50 e2e/react-start/rsc/src/routes/rsc-stream-generator.tsxRepository: TanStack/router
Length of output: 1703
🏁 Script executed:
# Search for CompositeComponent definition/import
rg "CompositeComponent" e2e/react-start/rsc/src/routes/Repository: TanStack/router
Length of output: 8497
🏁 Script executed:
# Get more context around CompositeComponent usage in the file
sed -n '60,110p' e2e/react-start/rsc/src/routes/rsc-stream-generator.tsxRepository: TanStack/router
Length of output: 1888
🏁 Script executed:
# Check for CompositeComponent definition in the react-start package
find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "export.*CompositeComponent" | head -5Repository: TanStack/router
Length of output: 475
🏁 Script executed:
# Look for how CompositeComponent is used with src prop in other files
rg -A 2 "CompositeComponent\s+src=" e2e/react-start/rsc/src/routes/rsc-stream-generator.tsxRepository: TanStack/router
Length of output: 41
🏁 Script executed:
# Get the full usage of CompositeComponent in the stream-generator file
rg "CompositeComponent" e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx -A 3Repository: TanStack/router
Length of output: 393
🏁 Script executed:
# Check the CompositeComponent definition to understand its props
cat packages/react-start-rsc/src/CompositeComponent.tsxRepository: TanStack/router
Length of output: 3886
🏁 Script executed:
# Find AnyCompositeComponent definition
rg "type AnyCompositeComponent|export.*AnyCompositeComponent" packages/react-start-rsc/src/ -A 2Repository: TanStack/router
Length of output: 1405
🏁 Script executed:
# Check what needs to be imported to get the proper type
rg "import.*AnyCompositeComponent" e2e/react-start/rsc/src/routes/ -B 1 -A 1Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Check if there are other similar patterns in the codebase for typing notifications
rg "type.*=.*CompositeComponent" e2e/react-start/rsc/src/routes/Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Find the streamNotificationsGenerator to see what it yields
find . -name "*stream*" -type f | grep -E "\.(ts|tsx)$" | xargs grep -l "streamNotificationsGenerator" | head -5Repository: TanStack/router
Length of output: 233
🏁 Script executed:
# Check the return type/yield type of streamNotificationsGenerator
rg -A 10 "export.*streamNotificationsGenerator|function streamNotificationsGenerator" --type tsx --type tsRepository: TanStack/router
Length of output: 86
🏁 Script executed:
# Check the streamNotificationsGenerator implementation
cat e2e/react-start/rsc/src/utils/streamingServerComponents.tsxRepository: TanStack/router
Length of output: 5589
Replace the any escape hatch with proper type from @tanstack/react-start/rsc.
This opts the route out of the strict typing required for *.tsx files and can hide breakage between streamNotificationsGenerator output and CompositeComponent.
Suggested fix
+import { AnyCompositeComponent } from '@tanstack/react-start/rsc'
+
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-type NotificationRSC = any
+type NotificationRSC = AnyCompositeComponentAs per coding guidelines, **/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx` around lines 21 -
22, Replace the any-based NotificationRSC type with the concrete notification
type exported by `@tanstack/react-start/rsc`: import the appropriate type (e.g.,
Notification or NotificationRSCType) from '@tanstack/react-start/rsc' and use it
in place of the current "type NotificationRSC = any"; then ensure
streamNotificationsGenerator's output and the CompositeComponent props are
annotated to that imported type so the compiler will check compatibility between
streamNotificationsGenerator and CompositeComponent.
| pageStyles, | ||
| clientStyles, | ||
| serverStyles, | ||
| asyncStyles, | ||
| formatTime, | ||
| } from '~/utils/styles' |
There was a problem hiding this comment.
Fix import specifier order to satisfy lint.
The ~/utils/styles import members are not alphabetically sorted, which matches the reported sort-imports error.
Proposed fix
import {
- pageStyles,
+ asyncStyles,
clientStyles,
- serverStyles,
- asyncStyles,
formatTime,
+ pageStyles,
+ serverStyles,
} from '~/utils/styles'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pageStyles, | |
| clientStyles, | |
| serverStyles, | |
| asyncStyles, | |
| formatTime, | |
| } from '~/utils/styles' | |
| import { | |
| asyncStyles, | |
| clientStyles, | |
| formatTime, | |
| pageStyles, | |
| serverStyles, | |
| } from '~/utils/styles' |
🧰 Tools
🪛 ESLint
[error] 7-7: Member 'clientStyles' of the import declaration should be sorted alphabetically.
(sort-imports)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx` around lines 6 - 11,
The import specifiers from ~/utils/styles are out of alphabetical order and
trigger the sort-imports lint; reorder the named imports (pageStyles,
clientStyles, serverStyles, asyncStyles, formatTime) into alphabetical order —
e.g. asyncStyles, clientStyles, formatTime, pageStyles, serverStyles — so the
import line in rsc-stream-readable.tsx conforms to the linter.
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type NotificationRSC = any | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx"
echo "Expect no matches after applying the fix:"
rg -n '\bany\b|as\s+NotificationRSC' "$FILE" || trueRepository: TanStack/router
Length of output: 280
Replace explicit any in stream payload typing with inferred types.
Type NotificationRSC = any on line 22 and the cast on line 55 violate TypeScript strict mode requirements. Infer the stream item type from streamNotificationsReadable instead.
Proposed fix
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-type NotificationRSC = any
+type NotificationStream = Awaited<
+ ReturnType<typeof streamNotificationsReadable>
+>
+type NotificationRSC = NotificationStream extends ReadableStream<infer T>
+ ? T
+ : never
@@
- if (result.value) {
- setNotifications((prev) => [...prev, result.value as NotificationRSC])
+ if (!result.done) {
+ setNotifications((prev) => [...prev, result.value])
}Also applies to: 26-28, 55-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx` around lines 21 - 23,
Replace the explicit any by inferring the stream item type from
streamNotificationsReadable: remove "type NotificationRSC = any" and instead
declare NotificationRSC using TypeScript conditional types that extract the
readable stream's item type from ReturnType<typeof streamNotificationsReadable>
(e.g., use Awaited<ReturnType<typeof streamNotificationsReadable>> extends
ReadableStream<infer T> ? T : unknown), then remove the manual cast at the
consumption site (where the stream item is cast on line 55) so the compiler uses
the inferred NotificationRSC type everywhere.
| // Navigate to async bundle page via link (if nav exists) or direct navigation | ||
| await page.goto('/rsc-async-bundle') | ||
| await page.waitForURL(/\/rsc-async-bundle/) |
There was a problem hiding this comment.
Test uses full page load instead of client-side navigation.
The test is named "Async RSCs render correctly after client-side navigation" but uses page.goto('/rsc-async-bundle') which triggers a full page load, not client-side navigation. Compare with rsc-bundle.spec.ts which uses page.getByTestId('nav-bundle').click() for actual client-side navigation.
For a true client-side navigation test, consider clicking a navigation link instead:
🔧 Suggested fix for client-side navigation
// Navigate to async bundle page via link (if nav exists) or direct navigation
- await page.goto('/rsc-async-bundle')
- await page.waitForURL(/\/rsc-async-bundle/)
+ // Use actual client-side navigation if nav link exists
+ await page.getByTestId('nav-async-bundle').click()
+ await page.waitForURL('/rsc-async-bundle')Note: This requires a corresponding data-testid="nav-async-bundle" link in the app's navigation component.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts` around lines 227 - 229,
The test currently uses a full page load via page.goto('/rsc-async-bundle')
which doesn't exercise client-side navigation; change the navigation to perform
an in-app click (use page.getByTestId('nav-async-bundle').click() or the
existing nav selector used in rsc-bundle.spec.ts) and then await
page.waitForURL(/\/rsc-async-bundle/); ensure the app includes a link with
data-testid="nav-async-bundle" in the navigation component so the click triggers
client-side routing and the test "Async RSCs render correctly after client-side
navigation" validates SPA navigation behavior.
PR TanStack#7144 added pluginAdapters to VIRTUAL_MODULES in start-server-core but the changeset only bumped start-plugin-core. Published start-plugin-core@1.167.19 crashes because start-server-core@1.167.10 doesn't export pluginAdapters yet.
TanStack#7144 added pluginAdapters to VIRTUAL_MODULES in start-server-core but the changeset only bumped start-plugin-core, so start-server-core never got published with the new export.
Summary by CodeRabbit
New Features
Documentation