Skip to content

efp#7144

Merged
schiller-manuel merged 10 commits intomainfrom
efp
Apr 10, 2026
Merged

efp#7144
schiller-manuel merged 10 commits intomainfrom
efp

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel commented Apr 10, 2026

Summary by CodeRabbit

  • New Features

    • React Server Components (RSC) now available as experimental feature with comprehensive guides covering basic usage, caching, streaming, advanced patterns, and integration with React Query
    • ESLint plugin for enforcing React Server Component boundaries and preventing incompatible code patterns
    • Late RawStream support for promise-based streaming data scenarios
  • Documentation

    • Added detailed RSC guides including server components setup, caching strategies, streaming patterns, error handling, and slot-based composition
    • Added ESLint plugin configuration and rule documentation

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Added 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

Cohort / File(s) Summary
Docs - RSC & ESLint
docs/start/framework/react/guide/server-components.md, docs/start/framework/react/overview.md, docs/start/framework/react/comparison.md, docs/start/config.json, docs/start/eslint/*
Added Server Components guide, marked RSC experimental in overview/comparison, added ESLint plugin docs and per-rule pages, and updated nav config.
ESLint plugin e2e infra
e2e/eslint-plugin-start/*
New e2e package with package.json, multiple ESLint flat configs (perf/baseline variants), fixture generator script, fixtures, Vitest tests, perf scripts, and README.
RSC e2e package & root
e2e/react-start/rsc/{package.json,playwright.config.ts,vite.config.ts,eslint.config.js}, e2e/react-start/rsc/src/routeTree.gen.ts, e2e/react-start/rsc/src/routes/__root.tsx, e2e/react-start/rsc/src/routes/index.tsx
New RSC e2e package: build/test/config files, autogenerated route tree, root layout and home index with navigation to many RSC demos.
RSC demo routes (many)
e2e/react-start/rsc/src/routes/*
Large set of new RSC demo routes (basic, bundle, async bundle, deferred, streaming, caching, invalidation, hydration, params, forms, error/external, slots, SSR modes, etc.).
RSC server utilities & streaming
e2e/react-start/rsc/src/utils/*ServerComponent*, e2e/react-start/rsc/src/utils/streamingServerComponents.tsx, e2e/react-start/rsc/src/utils/cssModulesServerComponent.tsx, e2e/react-start/rsc/src/utils/globalCssServerComponent.tsx
Many createServerFn handlers returning renderServerComponent/createCompositeComponent, ReadableStream and async-generator streaming endpoints, CSS preloading, and input validation.
RSC client utilities, widgets & styles
e2e/react-start/rsc/src/utils/{ClientWidget*,ClientPreloadContent,ComplexPreloadContent*,CssModulesContent,GlobalCssContent,NestedAccentContent}, e2e/react-start/rsc/src/utils/*.module.css, e2e/react-start/rsc/src/utils/serverComponent.css
Client widgets, CSS modules/global CSS, content components, and styling helpers for tests and demos.
RSC helpers & shared utils
e2e/react-start/rsc/src/utils/{styles,serverStyles}, e2e/react-start/rsc/src/utils/rsc-param/*
Shared style objects, formatTime helper, and param/slot client helpers.
RSC e2e tests & setup
e2e/react-start/rsc/tests/*, e2e/react-start/rsc/tests/setup/*
Comprehensive Playwright suites and global setup/teardown for many RSC scenarios (streaming, bundles, hydration, CSS preload, caching, forms, etc.).
RSC-Query demo suite
e2e/react-start/rsc-query/{package.json,playwright.config.ts,vite.config.ts}, e2e/react-start/rsc-query/src/{router.tsx,routes/*,utils/*}, e2e/react-start/rsc-query/tests/*
New RSC+React-Query e2e package: router wired with QueryClient and SSR-Query integration, routes demonstrating RSC+Query patterns, client pages, server helpers, styles, and Playwright tests.
RawStream & basic updates
e2e/react-start/basic/src/raw-stream-fns.ts, e2e/react-start/basic/src/routes/raw-stream/client-call.tsx, e2e/react-start/basic-test-suite/src/raw-stream.spec.ts
Added late-resolution RawStream fixtures/server functions and corresponding client tests and Playwright spec.
Removed legacy basic-rsc
e2e/react-start/basic-rsc/*
Deleted legacy basic-rsc package files: package.json, generated route tree, routes, components, and utilities.
Ignores & small config tweaks
.gitignore (root), benchmarks/bundle-size/.gitignore, e2e/react-start/{rsc,rsc-query}/.gitignore, .prettierignore additions
Broadened test-results ignore pattern, added perf fixture ignore, new .prettierignore entries, and other small config edits.

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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • brenelz

"🐰
I hopped through streams and server light,
Composed the pieces, day and night.
Slots and suspense I stitched with care,
Tests and docs now fill the air.
Hop on—RSC blooms everywhere!"

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch efp

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

🚀 Changeset Version Preview

2 package(s) bumped directly, 1 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/eslint-plugin-start 0.0.0 → 0.0.1 Changeset
@tanstack/react-start-rsc 0.0.0 → 0.0.1 Changeset
@tanstack/react-start 1.167.20 → 1.167.21 Dependent

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Bundle Size Benchmarks

  • Commit: 06f7f8de5fb0
  • Measured at: 2026-04-10T23:35:31.852Z
  • Baseline source: history:70ec1da1ed8a
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.51 KiB 0 B (0.00%) 275.69 KiB 76.08 KiB ▁▁▁▁▂▃▃▅██▆
react-router.full 90.77 KiB 0 B (0.00%) 286.88 KiB 78.95 KiB ▁▁▂▂▄▆▆▆██▄
solid-router.minimal 35.60 KiB 0 B (0.00%) 107.36 KiB 31.94 KiB ▁▁▁▁▁▅▅▆███
solid-router.full 40.07 KiB 0 B (0.00%) 120.90 KiB 35.96 KiB ▁▁▁▁▁▅▅▆███
vue-router.minimal 53.46 KiB 0 B (0.00%) 153.16 KiB 48.00 KiB ▁▁▁▁▂▃▃▆███
vue-router.full 58.36 KiB 0 B (0.00%) 168.62 KiB 52.25 KiB ▁▁▁▁▁▂▂▆███
react-start.minimal 101.90 KiB -103 B (-0.10%) 323.57 KiB 88.17 KiB ▆▆▇▇▇▇▇▆██▇▁
react-start.full 105.35 KiB -52 B (-0.05%) 333.92 KiB 90.99 KiB ▂▂▃▃▄▅▅▆██▇▁
solid-start.minimal 49.62 KiB -85 B (-0.17%) 153.24 KiB 43.76 KiB ▁▁▁▁▂▅▅▆███▂
solid-start.full 55.15 KiB -65 B (-0.11%) 169.48 KiB 48.46 KiB ▁▁▁▁▂▅▅▆███▄

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Apr 10, 2026

View your CI Pipeline Execution ↗ for commit 7b9a95a

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 6m 59s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 4s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-10 23:41:24 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 10, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7144

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7144

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7144

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7144

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7144

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7144

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7144

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7144

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7144

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7144

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7144

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7144

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7144

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7144

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7144

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7144

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7144

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7144

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7144

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7144

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7144

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7144

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7144

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7144

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7144

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7144

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7144

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7144

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7144

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7144

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7144

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7144

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7144

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7144

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7144

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7144

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7144

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7144

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7144

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7144

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7144

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7144

commit: 7b9a95a

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 | 🟡 Minor

Fix named import ordering to satisfy lint.

Line 5 is currently out of order (multipleRawStreamsFn should come before singleRawStreamFn), and this can fail sort-imports in 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 | 🟡 Minor

Fix ESLint import sorting errors.

ESLint flags sort-imports violations: 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 | 🟡 Minor

Title doesn't match the actual functionality.

The title says "Weather Data - RSC with External API" but the route fetches from a /posts endpoint 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Add the missing @keyframes loading definition or remove the animation reference.

The animation: 'loading 1.5s ease-in-out infinite' style on line 145 references a loading keyframe animation that does not exist anywhere in the repository. Without the corresponding @keyframes loading rule, 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 | 🟡 Minor

Use the deterministic time formatter here too.

This route imports formatTime but the server-rendered timestamp at line 46 uses new Date(serverTimestamp).toLocaleTimeString() instead. The inconsistency introduces locale-dependent output that can cause e2e test flakiness. Use formatTime(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 | 🟡 Minor

Unused label parameter in input validator.

The inputValidator accepts { label?: string } but data.label is never used in the handler. The AI summary indicates rsc-basic.tsx calls 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 | 🟡 Minor

Remove unused ErrorComponent import.

ErrorComponent is imported but never used in this file. The route uses a custom RouteErrorComponent function 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 | 🟡 Minor

Keep searchInput in sync with the URL.

searchInput is only seeded from q once. 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 | 🟡 Minor

Update 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 | 🟡 Minor

Avoid 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., renderServerComponent throws), 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.tsx for 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Use 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Fix import member order to satisfy sort-imports.

Link should be sorted before createFileRoute per 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Resolve sort-imports violations 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 | 🟡 Minor

Fix 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 | 🟡 Minor

Fix import ordering to satisfy import/order.

Line 1 currently violates the configured order (type import 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 | 🟡 Minor

Resolve sort-imports ordering 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 | 🟡 Minor

This 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 | 🟡 Minor

Add 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 | 🟡 Minor

Don't resolve the Flight tree before handing it to use.

These awaits mean state only receives an already-resolved promise, so the Suspense fallbacks 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 | 🟡 Minor

Fail fast when ESLint returns no result.

If a fixture path is wrong or ignored, this returns undefined and the test fails later with undefined.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 | 🟡 Minor

Avoid any for the streamed RSC payload.

This drops the type boundary around the readable stream chunks and the CompositeComponent contract, so regressions in the streamed payload shape become runtime-only failures. The proper type is available from createNotificationRSC'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 | 🟡 Minor

Use proper type inference instead of any for strict mode compliance.

Replace type NotificationRSC = any with type NotificationRSC = Awaited<ReturnType<typeof createNotificationRSC>>. This preserves type safety and automatically infers the correct return type from the createNotificationRSC function, 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 returns any and 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: Narrow dataType to the supported literals.

Using string plus Record<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 for AnyCompositeComponent.

ESLint correctly identifies that AnyCompositeComponent is 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 in saveDrawing.

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 for NotificationRSC.

Using any loses 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 Errors

Also 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 .md suffix.

✏️ 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 for Component 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: Use workspace:* 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_WAIT at Line 4 and waitForTimeout at 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: Avoid any in the catch paths.

Use unknown and narrow to Error instead. 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 variable slowLoading.

The slowLoading locator 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, '..')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -5

Repository: 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.json

Repository: 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.

Comment on lines +69 to +80
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +8 to +10
.inputValidator((data: { title?: string; delayMs?: number }) => data)
.handler(async ({ data }) => {
await new Promise((resolve) => setTimeout(resolve, data.delayMs ?? 150))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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))
As 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-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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n e2e/react-start/rsc-query/tests/rsc-query.spec.ts | head -100

Repository: TanStack/router

Length of output: 4216


🏁 Script executed:

cat -n e2e/react-start/rsc-query/tests/rsc-query.spec.ts | tail -n +100

Repository: 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.

Comment on lines +167 to +175
// 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:')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +55 to +59
const pageSize = 3
const startIndex = (data.page - 1) * pageSize
const results = filtered.slice(startIndex, startIndex + pageSize)
const totalPages = Math.ceil(filtered.length / pageSize)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +275 to +320
{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>
))}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +40 to +50
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +41 to +59
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +12 to +37
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',
},
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 tsx

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

# First, locate the file
find . -name "rsc-streaming.tsx" -type f

Repository: TanStack/router

Length of output: 109


🏁 Script executed:

cat -n ./e2e/react-start/rsc/src/routes/rsc-streaming.tsx

Repository: 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 -5

Repository: TanStack/router

Length of output: 246


🏁 Script executed:

# Check the main eslint config for sort-imports rules
cat ./eslint.config.js | head -100

Repository: TanStack/router

Length of output: 818


🏁 Script executed:

# Check what's in tanstackConfig - look for imports-related rules
rg "sort-imports" --type js

Repository: 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 -10

Repository: 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 -30

Repository: 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 -40

Repository: 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 -50

Repository: 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.tsx

Repository: 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 -60

Repository: 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 -80

Repository: 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 -10

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Search for LinkOptions in router-core
rg "type LinkOptions" packages/ --type ts | head -10

Repository: TanStack/router

Length of output: 535


🏁 Script executed:

# Check LinkOptions in router-core
rg -A 25 "export type LinkOptions<" packages/router-core/src/link.ts

Repository: 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 -100

Repository: 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.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 10, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing efp (7b9a95a) with main (70ec1da)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (06f7f8d) during the generation of this report, so 70ec1da was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

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

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,

Apply fix via Nx Cloud  Reject fix via Nx Cloud


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx (1)

34-61: ⚠️ Potential issue | 🟠 Major

Only mark stream completion after a successful full read.

Line 46 (if (!stream) return) and read failures still go through finally, but Line 60 always sets completion to true. 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 | 🟠 Major

Only mark the stream complete after successful exhaustion.

setStreamComplete(true) still runs from finally, so errored or aborted streams render the success banner. Keep the teardown in finally, 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 unnecessary ReactNode assertions.

Line 69 and Line 92 cast to React.ReactNode after 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 variable slowLoading.

The variable slowLoading is 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:

  1. Renaming the test to reflect what it actually verifies (e.g., "Bundle structure persists across page visits")
  2. 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 LoadingFallback instance injects the same @keyframes spin rule. 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 use page.getByTestId('rsc-async-medium-title') instead of page.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

📥 Commits

Reviewing files that changed from the base of the PR and between 58bc1c5 and 7b9a95a.

📒 Files selected for processing (39)
  • e2e/react-start/rsc/src/routes/__root.tsx
  • e2e/react-start/rsc/src/routes/rsc-async-bundle.tsx
  • e2e/react-start/rsc/src/routes/rsc-flight-api.tsx
  • e2e/react-start/rsc/src/routes/rsc-stream-generator.tsx
  • e2e/react-start/rsc/src/routes/rsc-stream-loader.tsx
  • e2e/react-start/rsc/src/routes/rsc-stream-readable.tsx
  • e2e/react-start/rsc/src/utils/ClientWidgetA.tsx
  • e2e/react-start/rsc/src/utils/ClientWidgetB.tsx
  • e2e/react-start/rsc/tests/rsc-async-bundle.spec.ts
  • e2e/react-start/rsc/tests/rsc-basic.spec.ts
  • e2e/react-start/rsc/tests/rsc-bundle.spec.ts
  • e2e/react-start/rsc/tests/rsc-caching.spec.ts
  • e2e/react-start/rsc/tests/rsc-client-preload.spec.ts
  • e2e/react-start/rsc/tests/rsc-component-slot.spec.ts
  • e2e/react-start/rsc/tests/rsc-context.spec.ts
  • e2e/react-start/rsc/tests/rsc-css-modules.spec.ts
  • e2e/react-start/rsc/tests/rsc-css-preload-complex.spec.ts
  • e2e/react-start/rsc/tests/rsc-deferred.spec.ts
  • e2e/react-start/rsc/tests/rsc-flight-api.spec.ts
  • e2e/react-start/rsc/tests/rsc-forms.spec.ts
  • e2e/react-start/rsc/tests/rsc-global-css.spec.ts
  • e2e/react-start/rsc/tests/rsc-hydration.spec.ts
  • e2e/react-start/rsc/tests/rsc-link.spec.ts
  • e2e/react-start/rsc/tests/rsc-nested-structure.spec.ts
  • e2e/react-start/rsc/tests/rsc-nested.spec.ts
  • e2e/react-start/rsc/tests/rsc-parallel-flash.spec.ts
  • e2e/react-start/rsc/tests/rsc-param-remount.spec.ts
  • e2e/react-start/rsc/tests/rsc-react-cache.spec.ts
  • e2e/react-start/rsc/tests/rsc-slot-jsx-args.spec.ts
  • e2e/react-start/rsc/tests/rsc-slots.spec.ts
  • e2e/react-start/rsc/tests/rsc-ssr-data-only.spec.ts
  • e2e/react-start/rsc/tests/rsc-ssr-false.spec.ts
  • e2e/react-start/rsc/tests/rsc-stream-flash.spec.ts
  • e2e/react-start/rsc/tests/rsc-stream-generator.spec.ts
  • e2e/react-start/rsc/tests/rsc-stream-loader.spec.ts
  • e2e/react-start/rsc/tests/rsc-stream-readable.spec.ts
  • e2e/react-start/rsc/tests/rsc-suspense.spec.ts
  • e2e/react-start/rsc/tests/rsc-tree.spec.ts
  • packages/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

Comment on lines +70 to +72
} catch (e: any) {
setResponseError(e?.message || String(e))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

head -100 e2e/react-start/rsc/src/routes/rsc-flight-api.tsx | tail -35

Repository: TanStack/router

Length of output: 996


🏁 Script executed:

sed -n '65,100p' e2e/react-start/rsc/src/routes/rsc-flight-api.tsx

Repository: 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.

Comment on lines +21 to +22
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type NotificationRSC = any
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.tsx

Repository: 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.tsx

Repository: 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 -5

Repository: 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.tsx

Repository: 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 3

Repository: TanStack/router

Length of output: 393


🏁 Script executed:

# Check the CompositeComponent definition to understand its props
cat packages/react-start-rsc/src/CompositeComponent.tsx

Repository: TanStack/router

Length of output: 3886


🏁 Script executed:

# Find AnyCompositeComponent definition
rg "type AnyCompositeComponent|export.*AnyCompositeComponent" packages/react-start-rsc/src/ -A 2

Repository: 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 1

Repository: 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 -5

Repository: 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 ts

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

# Check the streamNotificationsGenerator implementation
cat e2e/react-start/rsc/src/utils/streamingServerComponents.tsx

Repository: 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 = AnyCompositeComponent

As 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.

Comment on lines +6 to +11
pageStyles,
clientStyles,
serverStyles,
asyncStyles,
formatTime,
} from '~/utils/styles'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +21 to +23
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type NotificationRSC = any

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ 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" || true

Repository: 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.

Comment on lines +227 to +229
// 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/)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

@schiller-manuel schiller-manuel merged commit f920527 into main Apr 10, 2026
16 checks passed
@schiller-manuel schiller-manuel deleted the efp branch April 10, 2026 23:43
ramonclaudio added a commit to ramonclaudio/router that referenced this pull request Apr 11, 2026
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.
ramonclaudio added a commit to ramonclaudio/router that referenced this pull request Apr 11, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants