Skip to content

fix react-router shared route css persistence on nav#7186

Merged
schiller-manuel merged 6 commits intomainfrom
fix-7172
Apr 15, 2026
Merged

fix react-router shared route css persistence on nav#7186
schiller-manuel merged 6 commits intomainfrom
fix-7172

Conversation

@schiller-manuel
Copy link
Copy Markdown
Contributor

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

Summary by CodeRabbit

  • Bug Fixes

    • Improved stylesheet handling so shared and route CSS persist across client-side navigation and stylesheet ordering is respected.
  • New Features

    • Added demo routes, navigation entries, and reusable shared UI components across React, Solid, and Vue test apps.
    • Link elements now include stylesheet precedence to enforce proper CSS ordering.
  • Tests

    • Added and expanded end-to-end suites validating SSR, client navigation, lazy/static loading, and CSS persistence.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 317f4f0e-8cba-4024-b2bc-7e46f7369a3a

📥 Commits

Reviewing files that changed from the base of the PR and between be47735 and 5b0be55.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts

📝 Walkthrough

Walkthrough

Updates manifest asset resolution/deduplication and React Asset link rendering (adds stylesheet precedence), plus large e2e additions for React/Solid/Vue start-manifest apps and Playwright tests validating shared stylesheet persistence across SSR and client navigation.

Changes

Cohort / File(s) Summary
Release metadata
\.changeset/tough-bears-tease.md
New changeset marking a patch release for @tanstack/react-router documenting stylesheet-preservation behavior.
React Asset rendering
packages/react-router/src/Asset.tsx
<link> elements now include a precedence prop (stylesheets default to 'default').
Manifest builder core
packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
Removed dynamic-import CSS hashing/collection, changed createManifestAssetResolvers signature, rewrote asset identity/deduping logic and removed related helpers/outputs.
Manifest & SSR tests
packages/start-plugin-core/tests/.../manifestBuilder.test.ts, packages/react-router/tests/Scripts.test.tsx
Tests updated to match new manifest semantics: removed hashed-css expectations, parameterized stylesheet/preload hrefs, and removed tests for deleted helpers.
React e2e start-manifest
e2e/react-start/start-manifest/src/components/..., e2e/react-start/start-manifest/src/routes/..., e2e/react-start/start-manifest/src/styles/*, e2e/react-start/start-manifest/src/routeTree.gen.ts
Added SharedCard, new routes (/a,/b,/lazy-css-static,/lazy-css-lazy), CSS modules, routeTree updates, and AppShell nav entries.
React e2e — CSS modules changes
e2e/react-start/css-modules/src/routeTree.gen.ts, e2e/react-start/css-modules/src/routes/__root.tsx, e2e/react-start/css-modules/src/styles/shared-widget.module.css, e2e/react-start/css-modules/tests/css.prod.spec.ts, e2e/react-start/rsc/tests/rsc-css-modules.spec.ts
Removed two lazy-css routes and an older CSS e2e suite; deleted a shared-widget CSS module; added an RSC Playwright test asserting CSS module styles persist across sibling client-side navigation.
React start-manifest tests
e2e/react-start/start-manifest/tests/start-manifest.spec.ts
Expanded Playwright scenarios and helpers to poll for stylesheet links and computed styles; validates shared stylesheet identity and persistence across static/lazy navigation.
Solid start-manifest project
e2e/solid-start/start-manifest/* (package.json, playwright.config.ts, vite.config.ts, tsconfig.json, src/**)
New Solid e2e start-manifest app with components (AppShell, SharedCard, SharedWidget, SharedNestedLayout), many routes, generated routeTree, router factory, styles, and comprehensive Playwright tests.
Vue start-manifest project
e2e/vue-start/start-manifest/* (package.json, playwright.config.ts, vite.config.ts, tsconfig.json, src/**)
New Vue e2e start-manifest app mirroring React/Solid: AppShell, SharedCard, SharedWidget, SharedNestedLayout, routes, generated routeTree, router factory, styles, and Playwright tests.
Shared UI components & styles
e2e/*/start-manifest/src/components/SharedWidget*.tsx, e2e/*/start-manifest/src/components/SharedCard.tsx, e2e/*/start-manifest/src/styles/shared-*.module.css
Added/renamed shared widget/card components and corresponding CSS modules across React/Solid/Vue manifests; adjusted lazy imports/casing.
Large E2E test suites
e2e/solid-start/start-manifest/tests/start-manifest.spec.ts, e2e/vue-start/start-manifest/tests/start-manifest.spec.ts
Two extensive Playwright suites added validating SSR, manifest assets, client hydration, and CSS-module/shared-chunk identity across many navigation scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Router
    participant Manifest
    participant Asset
    participant Head

    Browser->>Router: navigate(path)
    Router->>Manifest: request route assets
    Manifest-->>Router: respond with assets (styles/scripts)
    Router->>Asset: render assets
    Asset->>Head: inject <link rel="stylesheet" href=... precedence='default'>
    Head->>Browser: stylesheet applied
    Browser->>Router: client-side sibling navigation
    Router->>Manifest: resolve new route assets
    Manifest-->>Router: assets (may include shared stylesheet)
    Router->>Asset: reconcile/inject assets (preserve shared identity)
    Asset->>Head: add/update <link> nodes
    Head->>Browser: styles remain consistent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • tannerlinsley

Poem

🐰
I hopped through manifests with links held tight,
Kept stylesheet threads from vanishing in flight,
No flicker on navigation, no style plight,
Shared CSS tucked in head all through the night,
A rabbit cheers: the routes look right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix react-router shared route css persistence on nav' directly describes the main change: preserving shared route CSS across client-side navigation in react-router.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix-7172

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Apr 13, 2026

View your CI Pipeline Execution ↗ for commit 5b0be55

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

☁️ Nx Cloud last updated this comment at 2026-04-15 00:18:51 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

🚀 Changeset Version Preview

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

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.168.21 → 1.168.22 Changeset
@tanstack/react-start 1.167.39 → 1.167.40 Dependent
@tanstack/react-start-client 1.166.38 → 1.166.39 Dependent
@tanstack/react-start-rsc 0.0.18 → 0.0.19 Dependent
@tanstack/react-start-server 1.166.39 → 1.166.40 Dependent

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Bundle Size Benchmarks

  • Commit: df611998cb69
  • Measured at: 2026-04-14T23:56:47.092Z
  • Baseline source: history:8caa20223e87
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.35 KiB 0 B (0.00%) 274.60 KiB 75.97 KiB ▅▅▅▁▁▁▁▁▁██
react-router.full 90.63 KiB +28 B (+0.03%) 285.74 KiB 78.87 KiB ▅▅▅▁▁▁▁▁▁▂▂█
solid-router.minimal 35.51 KiB 0 B (0.00%) 106.60 KiB 31.91 KiB ███▁▁▁▁▁▁▃▃
solid-router.full 39.99 KiB 0 B (0.00%) 120.09 KiB 35.93 KiB ███▁▁▁▁▁▁▆▆
vue-router.minimal 53.30 KiB 0 B (0.00%) 152.01 KiB 47.88 KiB ███▁▁▁▁▁▁▄▄
vue-router.full 58.20 KiB 0 B (0.00%) 167.43 KiB 52.06 KiB ███▁▁▁▁▁▁▄▄
react-start.minimal 101.77 KiB +25 B (+0.02%) 322.39 KiB 88.05 KiB ▄▄▄▁▁▁▁▁▁▃▃█
react-start.full 105.21 KiB +28 B (+0.03%) 332.72 KiB 90.89 KiB ▅▅▅▁▁▁▁▁▁▃▃█
solid-start.minimal 49.52 KiB 0 B (0.00%) 152.41 KiB 43.66 KiB ███▁▁▁▁▁▁▄▄
solid-start.full 55.04 KiB 0 B (0.00%) 168.61 KiB 48.44 KiB ███▁▁▁▁▁▁▅▅

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 13, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 5b0be55

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 13, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing fix-7172 (5b0be55) with main (8caa202)1

Open in CodSpeed

Footnotes

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

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: 2

🧹 Nitpick comments (7)
e2e/react-start/start-manifest/tests/start-manifest.spec.ts (1)

335-345: Avoid hard-coding the emitted CSS chunk name in this assertion.

Matching href*="SharedCard" makes the test depend on Vite's asset naming instead of the behavior under test. A harmless chunk-renaming change would fail this even if CSS persistence still works. Capture the actual shared stylesheet href from the manifest or from the first render, then assert that exact href stays mounted once after navigating to /b.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/start-manifest/tests/start-manifest.spec.ts` around lines 335
- 345, The test currently hard-codes the CSS chunk match by using
page.locator('head link[rel="stylesheet"][href*="SharedCard"]'), which couples
the test to Vite's generated name; change it to capture the actual stylesheet
href from the first render (e.g., read the href attribute from the locator used
before navigation) and store it in a variable, then after
getByTestId('nav-/b').click() and waitForURL('**/b') assert that a locator
matching head link[rel="stylesheet"][href="{capturedHref}"] exists exactly once
(use the capturedHref string in the second expect instead of 'SharedCard'); this
ensures you assert the exact emitted href persists rather than matching a
brittle partial name.
packages/react-router/tests/Scripts.test.tsx (1)

30-35: Widen the helper inputs so later cases stay typed.

This options shape only models string href overrides, which is why the cross-origin case later has to bypass manifest typing with an inline as any. Let createTestManifest accept the real preload/asset shapes instead of only string hrefs.

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 `@packages/react-router/tests/Scripts.test.tsx` around lines 30 - 35, The
helper createTestManifest currently types options.stylesheetHref and
options.preloadHref as plain strings which forces callers to cast for real
manifest entries; change createTestManifest's options to accept the actual
manifest asset/preload shapes used across the codebase (e.g. the same types used
for stylesheet and preload entries in your runtime manifest such as
ManifestStylesheet/ManifestPreload or the AssetDescriptor type) instead of
string hrefs, update the function to read href from those objects (e.g.
option.stylesheet.href / option.preload.href) and update tests that passed
strings to pass the proper shaped objects so the cross-origin test no longer
needs an inline as any.
packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts (1)

230-261: Assert the collector returns the cached stylesheet objects.

This currently proves only that getStylesheetAsset() memoizes. It does not prove getChunkCssAssets() reuses those same instances. If the collector starts cloning link tags, this test still passes while the shared-link reuse behavior can regress.

Proposed assertion tightening
     expect(assets).toEqual([
       {
         tag: 'link',
         attrs: {
           rel: 'stylesheet',
           href: '/assets/entry.css',
           type: 'text/css',
         },
       },
       {
         tag: 'link',
         attrs: {
           rel: 'stylesheet',
           href: '/assets/shared.css',
           type: 'text/css',
         },
       },
     ])

-    expect(resolvers.getStylesheetAsset('shared.css')).toBe(
-      resolvers.getStylesheetAsset('shared.css'),
-    )
+    expect(assets[0]).toBe(resolvers.getStylesheetAsset('entry.css'))
+    expect(assets[1]).toBe(resolvers.getStylesheetAsset('shared.css'))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`
around lines 230 - 261, The test only checks getStylesheetAsset() memoization
but not that createChunkCssAssetCollector.getChunkCssAssets() returns the exact
same object instances; update the assertions to verify identity: after calling
cssAssetCollector.getChunkCssAssets(...) capture the returned assets array and
assert that specific entries (e.g., the shared stylesheet link) are the exact
same instance as returned by
createManifestAssetResolvers().getStylesheetAsset('shared.css') using strict
identity checks (toBe) for both the shared and entry stylesheet entries so the
collector cannot pass by cloning link objects; reference cssAssetCollector,
getChunkCssAssets, createManifestAssetResolvers, and getStylesheetAsset in the
assertion.
e2e/solid-start/start-manifest/tests/start-manifest.spec.ts (2)

1-6: Reorder node imports before external packages.

Same as the Vue test file—ESLint expects node:* imports to appear before external package imports.

🔧 Suggested fix
+import { readdir } from 'node:fs/promises'
+import path from 'node:path'
+import { pathToFileURL } from 'node:url'
 import { expect } from '@playwright/test'
 import { test } from '@tanstack/router-e2e-utils'
 import type { Page } from '@playwright/test'
-import { readdir } from 'node:fs/promises'
-import path from 'node:path'
-import { pathToFileURL } from 'node:url'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/solid-start/start-manifest/tests/start-manifest.spec.ts` around lines 1 -
6, Move the Node built-in imports (readdir from 'node:fs/promises', path from
'node:path', pathToFileURL from 'node:url') to appear before external package
imports; update the import order in start-manifest.spec.ts so the three node:*
imports are listed above imports like '@playwright/test' and
'@tanstack/router-e2e-utils' (check the top-of-file import block containing
expect, test, and Page to reorder accordingly).

80-82: Use function property syntax instead of shorthand method signatures.

Same as the Vue test file—use function property syntax for consistency with ESLint rules.

🔧 Suggested fix
   request: {
-    get: (url: string) => Promise<{ ok(): boolean; text(): Promise<string> }>
+    get: (url: string) => Promise<{ ok: () => boolean; text: () => Promise<string> }>
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/solid-start/start-manifest/tests/start-manifest.spec.ts` around lines 80
- 82, Change the inline response type to use function property syntax instead of
method shorthand: update the anonymous response shape returned by request.get so
that ok and text are declared as properties (ok: () => boolean and text: () =>
Promise<string>) rather than shorthand methods (ok(): boolean and text():
Promise<string>); this affects the request.get return type and the ok and text
members referenced in start-manifest.spec.ts.
e2e/vue-start/start-manifest/tests/start-manifest.spec.ts (2)

1-6: Reorder node imports before external packages.

ESLint flags that node:* imports should appear before external package imports.

🔧 Suggested fix
+import { readdir } from 'node:fs/promises'
+import path from 'node:path'
+import { pathToFileURL } from 'node:url'
 import { expect } from '@playwright/test'
 import { test } from '@tanstack/router-e2e-utils'
 import type { Page } from '@playwright/test'
-import { readdir } from 'node:fs/promises'
-import path from 'node:path'
-import { pathToFileURL } from 'node:url'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/vue-start/start-manifest/tests/start-manifest.spec.ts` around lines 1 -
6, Move all Node built-in imports (readdir from 'node:fs/promises', path from
'node:path', pathToFileURL from 'node:url') above the external package imports
(expect and Page from '@playwright/test' and test from
'@tanstack/router-e2e-utils'); reorder the import block so node:* imports come
first, then external packages, keeping the same specifier names and only
changing order to satisfy ESLint.

84-86: Use function property syntax instead of shorthand method signatures.

ESLint's @typescript-eslint/method-signature-style rule requires function properties instead of shorthand method signatures in type definitions.

🔧 Suggested fix
   request: {
-    get: (url: string) => Promise<{ ok(): boolean; text(): Promise<string> }>
+    get: (url: string) => Promise<{ ok: () => boolean; text: () => Promise<string> }>
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/vue-start/start-manifest/tests/start-manifest.spec.ts` around lines 84 -
86, The type for request.get currently uses shorthand method signatures for the
returned object (ok() and text()) which violates
`@typescript-eslint/method-signature-style`; update the return type of request.get
to use function property syntax instead (e.g., change ok() to ok: () => boolean
and text() to text: () => Promise<string>) so the request.get signature becomes
get: (url: string) => Promise<{ ok: () => boolean; text: () => Promise<string>
}>, adjusting the types referenced in start-manifest.spec.ts for request.get,
ok, and text accordingly.
🤖 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/start-manifest/tests/start-manifest.spec.ts`:
- Around line 442-446: The wait after clicking the home nav uses a loose regex
(page.waitForURL(/\/([^/]*)(\/)?($|\?)/)) that can match other paths like
/lazy-css-lazy; replace it with an explicit wait for the root URL (e.g.
page.waitForURL('/') or a glob like page.waitForURL('**/')) immediately after
await page.getByTestId('nav-home').click(), so the home navigation completes
before calling await page.getByTestId('nav-/lazy-css-lazy').click(); update the
waitForURL call in the same test (the two occurrences around nav-home and
nav-/lazy-css-lazy) to use the explicit root match.

In `@packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts`:
- Around line 113-129: getAssetIdentity currently builds dedupe keys only from
href/src and type, which collapses assets that differ by important attributes;
update getAssetIdentity to include the meaningful attributes into the identity
string so deduping preserves distinct variants (for link include attrs.media,
attrs.crossOrigin, attrs.integrity, attrs.as and any other relevant attributes;
for script include attrs.async, attrs.defer, attrs.crossOrigin, attrs.integrity
and any other relevant attributes as well as children) and keep the same
delimiter pattern so appendUniqueAssets and dedupeNestedRouteManifestEntries
will treat these as distinct entries.

---

Nitpick comments:
In `@e2e/react-start/start-manifest/tests/start-manifest.spec.ts`:
- Around line 335-345: The test currently hard-codes the CSS chunk match by
using page.locator('head link[rel="stylesheet"][href*="SharedCard"]'), which
couples the test to Vite's generated name; change it to capture the actual
stylesheet href from the first render (e.g., read the href attribute from the
locator used before navigation) and store it in a variable, then after
getByTestId('nav-/b').click() and waitForURL('**/b') assert that a locator
matching head link[rel="stylesheet"][href="{capturedHref}"] exists exactly once
(use the capturedHref string in the second expect instead of 'SharedCard'); this
ensures you assert the exact emitted href persists rather than matching a
brittle partial name.

In `@e2e/solid-start/start-manifest/tests/start-manifest.spec.ts`:
- Around line 1-6: Move the Node built-in imports (readdir from
'node:fs/promises', path from 'node:path', pathToFileURL from 'node:url') to
appear before external package imports; update the import order in
start-manifest.spec.ts so the three node:* imports are listed above imports like
'@playwright/test' and '@tanstack/router-e2e-utils' (check the top-of-file
import block containing expect, test, and Page to reorder accordingly).
- Around line 80-82: Change the inline response type to use function property
syntax instead of method shorthand: update the anonymous response shape returned
by request.get so that ok and text are declared as properties (ok: () => boolean
and text: () => Promise<string>) rather than shorthand methods (ok(): boolean
and text(): Promise<string>); this affects the request.get return type and the
ok and text members referenced in start-manifest.spec.ts.

In `@e2e/vue-start/start-manifest/tests/start-manifest.spec.ts`:
- Around line 1-6: Move all Node built-in imports (readdir from
'node:fs/promises', path from 'node:path', pathToFileURL from 'node:url') above
the external package imports (expect and Page from '@playwright/test' and test
from '@tanstack/router-e2e-utils'); reorder the import block so node:* imports
come first, then external packages, keeping the same specifier names and only
changing order to satisfy ESLint.
- Around line 84-86: The type for request.get currently uses shorthand method
signatures for the returned object (ok() and text()) which violates
`@typescript-eslint/method-signature-style`; update the return type of request.get
to use function property syntax instead (e.g., change ok() to ok: () => boolean
and text() to text: () => Promise<string>) so the request.get signature becomes
get: (url: string) => Promise<{ ok: () => boolean; text: () => Promise<string>
}>, adjusting the types referenced in start-manifest.spec.ts for request.get,
ok, and text accordingly.

In `@packages/react-router/tests/Scripts.test.tsx`:
- Around line 30-35: The helper createTestManifest currently types
options.stylesheetHref and options.preloadHref as plain strings which forces
callers to cast for real manifest entries; change createTestManifest's options
to accept the actual manifest asset/preload shapes used across the codebase
(e.g. the same types used for stylesheet and preload entries in your runtime
manifest such as ManifestStylesheet/ManifestPreload or the AssetDescriptor type)
instead of string hrefs, update the function to read href from those objects
(e.g. option.stylesheet.href / option.preload.href) and update tests that passed
strings to pass the proper shaped objects so the cross-origin test no longer
needs an inline as any.

In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`:
- Around line 230-261: The test only checks getStylesheetAsset() memoization but
not that createChunkCssAssetCollector.getChunkCssAssets() returns the exact same
object instances; update the assertions to verify identity: after calling
cssAssetCollector.getChunkCssAssets(...) capture the returned assets array and
assert that specific entries (e.g., the shared stylesheet link) are the exact
same instance as returned by
createManifestAssetResolvers().getStylesheetAsset('shared.css') using strict
identity checks (toBe) for both the shared and entry stylesheet entries so the
collector cannot pass by cloning link objects; reference cssAssetCollector,
getChunkCssAssets, createManifestAssetResolvers, and getStylesheetAsset in the
assertion.
🪄 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: 602fd7a5-6f46-4a9e-ba4a-337485dab3e6

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6fe1 and 259bdf0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (94)
  • .changeset/tough-bears-tease.md
  • e2e/react-start/css-modules/src/routeTree.gen.ts
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/styles/shared-widget.module.css
  • e2e/react-start/css-modules/tests/css.prod.spec.ts
  • e2e/react-start/rsc/tests/rsc-css-modules.spec.ts
  • e2e/react-start/start-manifest/src/components/AppShell.tsx
  • e2e/react-start/start-manifest/src/components/SharedCard.tsx
  • e2e/react-start/start-manifest/src/components/SharedWidget.tsx
  • e2e/react-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/react-start/start-manifest/src/routeTree.gen.ts
  • e2e/react-start/start-manifest/src/routes/a.tsx
  • e2e/react-start/start-manifest/src/routes/b.tsx
  • e2e/react-start/start-manifest/src/routes/lazy-css-lazy.tsx
  • e2e/react-start/start-manifest/src/routes/lazy-css-static.tsx
  • e2e/react-start/start-manifest/src/styles/page-a.module.css
  • e2e/react-start/start-manifest/src/styles/page-b.module.css
  • e2e/react-start/start-manifest/src/styles/shared-card.module.css
  • e2e/react-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/react-start/start-manifest/tests/start-manifest.spec.ts
  • e2e/solid-start/start-manifest/package.json
  • e2e/solid-start/start-manifest/playwright.config.ts
  • e2e/solid-start/start-manifest/src/components/AppShell.tsx
  • e2e/solid-start/start-manifest/src/components/SharedCard.tsx
  • e2e/solid-start/start-manifest/src/components/SharedNestedLayout.tsx
  • e2e/solid-start/start-manifest/src/components/SharedWidget.tsx
  • e2e/solid-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/solid-start/start-manifest/src/routeTree.gen.ts
  • e2e/solid-start/start-manifest/src/router.tsx
  • e2e/solid-start/start-manifest/src/routes/__root.tsx
  • e2e/solid-start/start-manifest/src/routes/a.tsx
  • e2e/solid-start/start-manifest/src/routes/b.tsx
  • e2e/solid-start/start-manifest/src/routes/index.tsx
  • e2e/solid-start/start-manifest/src/routes/lazy-css-lazy.tsx
  • e2e/solid-start/start-manifest/src/routes/lazy-css-static.tsx
  • e2e/solid-start/start-manifest/src/routes/r1.tsx
  • e2e/solid-start/start-manifest/src/routes/r2.tsx
  • e2e/solid-start/start-manifest/src/routes/r3.tsx
  • e2e/solid-start/start-manifest/src/routes/r4.tsx
  • e2e/solid-start/start-manifest/src/routes/r5.tsx
  • e2e/solid-start/start-manifest/src/routes/r6.tsx
  • e2e/solid-start/start-manifest/src/routes/shared-a.tsx
  • e2e/solid-start/start-manifest/src/routes/shared-b.tsx
  • e2e/solid-start/start-manifest/src/routes/shared-c.tsx
  • e2e/solid-start/start-manifest/src/styles/page-a.module.css
  • e2e/solid-start/start-manifest/src/styles/page-b.module.css
  • e2e/solid-start/start-manifest/src/styles/root-shell.module.css
  • e2e/solid-start/start-manifest/src/styles/route-one.module.css
  • e2e/solid-start/start-manifest/src/styles/route-two.module.css
  • e2e/solid-start/start-manifest/src/styles/shared-card.module.css
  • e2e/solid-start/start-manifest/src/styles/shared-layout.module.css
  • e2e/solid-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/solid-start/start-manifest/tests/start-manifest.spec.ts
  • e2e/solid-start/start-manifest/tsconfig.json
  • e2e/solid-start/start-manifest/vite.config.ts
  • e2e/vue-start/start-manifest/package.json
  • e2e/vue-start/start-manifest/playwright.config.ts
  • e2e/vue-start/start-manifest/src/components/AppShell.tsx
  • e2e/vue-start/start-manifest/src/components/SharedCard.tsx
  • e2e/vue-start/start-manifest/src/components/SharedNestedLayout.tsx
  • e2e/vue-start/start-manifest/src/components/SharedWidget.tsx
  • e2e/vue-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/vue-start/start-manifest/src/routeTree.gen.ts
  • e2e/vue-start/start-manifest/src/router.tsx
  • e2e/vue-start/start-manifest/src/routes/__root.tsx
  • e2e/vue-start/start-manifest/src/routes/a.tsx
  • e2e/vue-start/start-manifest/src/routes/b.tsx
  • e2e/vue-start/start-manifest/src/routes/index.tsx
  • e2e/vue-start/start-manifest/src/routes/lazy-css-lazy.tsx
  • e2e/vue-start/start-manifest/src/routes/lazy-css-static.tsx
  • e2e/vue-start/start-manifest/src/routes/r1.tsx
  • e2e/vue-start/start-manifest/src/routes/r2.tsx
  • e2e/vue-start/start-manifest/src/routes/r3.tsx
  • e2e/vue-start/start-manifest/src/routes/r4.tsx
  • e2e/vue-start/start-manifest/src/routes/r5.tsx
  • e2e/vue-start/start-manifest/src/routes/r6.tsx
  • e2e/vue-start/start-manifest/src/routes/shared-a.tsx
  • e2e/vue-start/start-manifest/src/routes/shared-b.tsx
  • e2e/vue-start/start-manifest/src/routes/shared-c.tsx
  • e2e/vue-start/start-manifest/src/styles/page-a.module.css
  • e2e/vue-start/start-manifest/src/styles/page-b.module.css
  • e2e/vue-start/start-manifest/src/styles/root-shell.module.css
  • e2e/vue-start/start-manifest/src/styles/route-one.module.css
  • e2e/vue-start/start-manifest/src/styles/route-two.module.css
  • e2e/vue-start/start-manifest/src/styles/shared-card.module.css
  • e2e/vue-start/start-manifest/src/styles/shared-layout.module.css
  • e2e/vue-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/vue-start/start-manifest/tests/start-manifest.spec.ts
  • e2e/vue-start/start-manifest/tsconfig.json
  • e2e/vue-start/start-manifest/vite.config.ts
  • packages/react-router/src/Asset.tsx
  • packages/react-router/tests/Scripts.test.tsx
  • packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts
💤 Files with no reviewable changes (3)
  • e2e/react-start/css-modules/src/routes/__root.tsx
  • e2e/react-start/css-modules/src/styles/shared-widget.module.css
  • e2e/react-start/css-modules/tests/css.prod.spec.ts
✅ Files skipped from review due to trivial changes (38)
  • .changeset/tough-bears-tease.md
  • e2e/react-start/start-manifest/src/routes/lazy-css-static.tsx
  • e2e/react-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/react-start/start-manifest/src/styles/shared-card.module.css
  • e2e/react-start/start-manifest/src/components/SharedWidget.tsx
  • e2e/solid-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/solid-start/start-manifest/src/styles/route-one.module.css
  • e2e/react-start/start-manifest/src/components/SharedCard.tsx
  • e2e/solid-start/start-manifest/src/styles/route-two.module.css
  • e2e/react-start/start-manifest/src/styles/page-a.module.css
  • e2e/vue-start/start-manifest/src/styles/route-two.module.css
  • e2e/react-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/vue-start/start-manifest/src/components/SharedWidgetLazy.tsx
  • e2e/solid-start/start-manifest/src/styles/shared-layout.module.css
  • e2e/solid-start/start-manifest/src/styles/page-a.module.css
  • e2e/solid-start/start-manifest/src/styles/shared-card.module.css
  • e2e/solid-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/react-start/start-manifest/src/styles/page-b.module.css
  • e2e/react-start/start-manifest/src/routes/a.tsx
  • e2e/vue-start/start-manifest/src/styles/shared-card.module.css
  • e2e/vue-start/start-manifest/src/styles/page-b.module.css
  • e2e/solid-start/start-manifest/src/components/SharedWidget.tsx
  • e2e/vue-start/start-manifest/src/styles/page-a.module.css
  • e2e/solid-start/start-manifest/src/routes/r1.tsx
  • e2e/solid-start/start-manifest/src/styles/page-b.module.css
  • e2e/vue-start/start-manifest/src/styles/route-one.module.css
  • e2e/vue-start/start-manifest/src/styles/shared-layout.module.css
  • e2e/react-start/rsc/tests/rsc-css-modules.spec.ts
  • e2e/solid-start/start-manifest/src/styles/root-shell.module.css
  • e2e/vue-start/start-manifest/src/styles/root-shell.module.css
  • e2e/vue-start/start-manifest/src/styles/shared-widget.module.css
  • e2e/vue-start/start-manifest/package.json
  • e2e/solid-start/start-manifest/tsconfig.json
  • e2e/vue-start/start-manifest/tsconfig.json
  • e2e/solid-start/start-manifest/package.json
  • e2e/solid-start/start-manifest/src/routeTree.gen.ts
  • e2e/vue-start/start-manifest/src/routeTree.gen.ts
  • e2e/react-start/css-modules/src/routeTree.gen.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • e2e/react-start/start-manifest/src/routes/b.tsx
  • e2e/react-start/start-manifest/src/components/AppShell.tsx
  • packages/react-router/src/Asset.tsx

schiller-manuel and others added 2 commits April 15, 2026 01:50
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.

🧹 Nitpick comments (1)
e2e/react-start/start-manifest/tests/start-manifest.spec.ts (1)

360-364: Optional DRY cleanup for repeated border-color evaluation.

borderTopColor extraction is repeated in three places; a small helper would reduce duplication and make future style-assert changes safer.

♻️ Suggested refactor
 async function getBackgroundColor(testId: string, page: Page) {
   return page
     .getByTestId(testId)
     .evaluate((element) => getComputedStyle(element).backgroundColor)
 }
+
+async function getBorderTopColor(testId: string, page: Page) {
+  return page
+    .getByTestId(testId)
+    .evaluate((element) => getComputedStyle(element).borderTopColor)
+}
@@
-  expect(
-    await widget.evaluate(
-      (element) => getComputedStyle(element).borderTopColor,
-    ),
-  ).toBe(SHARED_WIDGET_BORDER)
+  expect(await getBorderTopColor('shared-widget', page)).toBe(
+    SHARED_WIDGET_BORDER,
+  )
@@
-  expect(
-    await widget.evaluate(
-      (element) => getComputedStyle(element).borderTopColor,
-    ),
-  ).toBe(SHARED_WIDGET_BORDER)
+  expect(await getBorderTopColor('shared-widget', page)).toBe(
+    SHARED_WIDGET_BORDER,
+  )
@@
-  expect(
-    await widget.evaluate(
-      (element) => getComputedStyle(element).borderTopColor,
-    ),
-  ).toBe(SHARED_WIDGET_BORDER)
+  expect(await getBorderTopColor('shared-widget', page)).toBe(
+    SHARED_WIDGET_BORDER,
+  )

Also applies to: 402-406, 425-427

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/start-manifest/tests/start-manifest.spec.ts` around lines 360
- 364, Create a small helper to DRY the repeated extraction of borderTopColor
used in the tests (the widget.evaluate((element) =>
getComputedStyle(element).borderTopColor) calls) — add a function like
getWidgetBorderTopColor(widget) in the test file and replace the three inline
widget.evaluate usages (the occurrences around the expectations at/near the
current diff and lines 402-406 and 425-427) with calls to that helper so the
style access is centralized and easier to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@e2e/react-start/start-manifest/tests/start-manifest.spec.ts`:
- Around line 360-364: Create a small helper to DRY the repeated extraction of
borderTopColor used in the tests (the widget.evaluate((element) =>
getComputedStyle(element).borderTopColor) calls) — add a function like
getWidgetBorderTopColor(widget) in the test file and replace the three inline
widget.evaluate usages (the occurrences around the expectations at/near the
current diff and lines 402-406 and 425-427) with calls to that helper so the
style access is centralized and easier to update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5780e5d-c4ac-4a8d-be94-42d8628828b1

📥 Commits

Reviewing files that changed from the base of the PR and between 259bdf0 and be47735.

📒 Files selected for processing (1)
  • e2e/react-start/start-manifest/tests/start-manifest.spec.ts

@schiller-manuel schiller-manuel merged commit e30814d into main Apr 15, 2026
19 of 21 checks passed
@schiller-manuel schiller-manuel deleted the fix-7172 branch April 15, 2026 00:19
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.

1 participant