Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Dec 21, 2025

Context

For future improvements (namely #5936) we need the segmentTree nodes to be able to carry route-specific metadata that can be accessed during the path matching algorithm.

Currently, both a layout route and an index route can be carried by the same segment tree node. For example

/foo
/foo/

would both be on the 'foo' static node, with

  • node.route representing /foo/
  • node.notFound representing /foo

It was done this way because there is no possibility for a layout route (here /foo) to be matched if there is also an index route (here /foo/) at the same level. It could only be used as a fuzzy match (i.e. not found).

But now, we need more information than just the path for the matching algorithm (e.g. in #5936 we also need params.parse and skipRouteOnParseError) and this additional metadata cannot be represented on a single node.

Solution

This proposes that we introduce a new type of node to the segmentTree: index nodes. These supersede the node.notFound and node.isIndex attributes, and are now represented as regular children nodes.

Performance

This change means that we have a little bit more work to do to reach an index match (or to eliminate one from the candidate pool). However, it's negligible, and not actually measurable when benchmarking:

 ✓  @tanstack/router-core  tests/foo.bench.ts > matching 8865ms
     name                             hz     min     max    mean     p75     p99    p995    p999     rme   samples
   · new processRouteTree  36,492,386.76  0.0000  0.0200  0.0000  0.0000  0.0000  0.0000  0.0001  ±0.07%  18246194
   · old processRouteTree  36,720,035.80  0.0000  0.0249  0.0000  0.0000  0.0000  0.0000  0.0001  ±0.05%  18360019

 BENCH  Summary

   @tanstack/router-core  old processRouteTree - tests/foo.bench.ts > matching
    1.01x faster than new processRouteTree

1.01x is within the margin of error, and re-running the benchmark we get this number in either direction (old > new or new > old).

If we remove the cache, we can observe a slight performance degradation. This one is significant, but small enough.

 ✓  @tanstack/router-core  tests/foo.bench.ts > matching 1834ms
     name                            hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · new processRouteTree  2,433,502.64  0.0003  0.0756  0.0004  0.0004  0.0005  0.0005  0.0013  ±0.06%  1216752
   · old processRouteTree  2,617,481.46  0.0003  0.3523  0.0004  0.0004  0.0005  0.0006  0.0012  ±0.28%  1308741

 BENCH  Summary

   @tanstack/router-core  old processRouteTree - tests/foo.bench.ts > matching
    1.08x faster than new processRouteTree

Summary by CodeRabbit

  • Refactor
    • Index routes are now represented and traversed as explicit leaf nodes, simplifying route tree traversal and update propagation.
  • Bug Fixes
    • Trailing-slash and root cases improved so index nodes correctly participate in exact, fuzzy, and parameter matching.
  • Tests
    • Updated test trees to model index routes as nested children and adjusted traversal order and expectations accordingly.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Walkthrough

Adds explicit index-segment support and an index child on segment nodes, removes legacy isIndex/notFound flags, and updates parsing, matching, HMR traversal, and tests to create, traverse, and prioritize index routes.

Changes

Cohort / File(s) Change Summary
Index kind & node shape
packages/router-core/src/new-process-route-tree.ts
Add internal SEGMENT_TYPE_INDEX and ExtendedSegmentKind; widen SegmentNode.kind; add `index: StaticSegmentNode
Parsing, matching & traversal
packages/router-core/src/new-process-route-tree.ts
parseSegments now creates explicit index leaf nodes for trailing-slash/leaf routes and sets node.index; update getNodeMatch, traversal depth handling, isFrameMoreSpecific, fuzzy/best-match resolution, and parameter extraction to recognize and prioritize index leaves (including root-index fast path).
HMR traversal & snapshots
packages/router-plugin/src/core/route-hmr-statement.ts, packages/router-plugin/tests/add-hmr/snapshots/**
walkReplaceSegmentTree no longer assigns node.notFound when IDs match; it recurses into node.index if present while continuing to update node.route and child collections. Snapshot updates reflect index-branch traversal.
Tests (route trees / link tests)
packages/router-core/tests/new-process-route-tree.test.ts, packages/react-router/tests/link.test.tsx, packages/solid-router/tests/link.test.tsx, packages/vue-router/tests/link.test.tsx
Update route fixtures: convert a previously sibling /dashboard//dashboard/invoices case to nested /dashboard/invoices; remove trailing slash from $postId/ to $postId; add new index child path: '/' under post routes and adjust children ordering and assertions to reflect index-leaf behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus review on: getNodeMatch / isFrameMoreSpecific index-vs-kind precedence.
  • Validate parseSegments index-node creation and depth/fullPath assignment.
  • Verify walkReplaceSegmentTree recursion into node.index and removal of notFound usages.

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐇
I hop the tree where tiny pathways hide,
An index leaf folds snug at branch’s side.
No flags, just routes that nest and softly guide,
I nibble paths and tuck the map inside. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 clearly and specifically describes the main architectural change: introducing index nodes as explicit children in the segment tree structure, which aligns with the comprehensive refactoring across router-core and dependent packages.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-router-core-segment-tree-index-nodes

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

@nx-cloud
Copy link

nx-cloud bot commented Dec 21, 2025

View your CI Pipeline Execution ↗ for commit a8b8021

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

☁️ Nx Cloud last updated this comment at 2025-12-21 14:29:38 UTC

Copy link
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: 0

🧹 Nitpick comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

770-774: Potential issue with skipped non-null assertion.

When extractParams is called with a leaf from the early root index return (line 850: return { node: segmentTree.index }), leaf.skipped will be undefined. The non-null assertion leaf.skipped! on line 771 could lead to unexpected behavior:

if (leaf.skipped! & (1 << nodeIndex)) {

In JavaScript, undefined & number coerces to 0 & number = 0, so this happens to work correctly (the condition is always false when skipped is undefined). However, this relies on implicit type coercion and would fail TypeScript strict checks if the ! were removed.

Consider using a nullish coalescing operator for clarity:

🔎 Suggested clarification
-      if (leaf.skipped! & (1 << nodeIndex)) {
+      if ((leaf.skipped ?? 0) & (1 << nodeIndex)) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6839bfb and 6d72eb6.

📒 Files selected for processing (2)
  • packages/router-core/src/new-process-route-tree.ts (13 hunks)
  • packages/router-core/tests/new-process-route-tree.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/tests/new-process-route-tree.test.ts
  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/tests/new-process-route-tree.test.ts
  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/tests/new-process-route-tree.test.ts
  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/tests/new-process-route-tree.test.ts
  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/tests/new-process-route-tree.test.ts
  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/router-core/tests/new-process-route-tree.test.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (10)
packages/router-core/tests/new-process-route-tree.test.ts (1)

720-724: LGTM!

The test route structure change correctly reflects the new index segment tree behavior. The nested route under /dashboard/ properly exercises the fuzzy matching scenario where an index route should not be used as a fuzzy match target.

packages/router-core/src/new-process-route-tree.ts (9)

10-24: LGTM!

Good design choice keeping SEGMENT_TYPE_INDEX internal while SegmentKind remains the public API for route path segments. The ExtendedSegmentKind type cleanly separates the tree representation concern from the path parsing concern.


338-358: LGTM!

The index node creation logic correctly identifies leaf routes with trailing slashes and creates a dedicated index node with highest priority. The depth tracking and parent/child relationships are properly maintained.


429-477: LGTM!

Both createStaticNode and createDynamicNode consistently initialize the index property to null, ensuring the new node structure is properly set up across all node types.


479-527: LGTM!

The type definitions are well-structured:

  • StaticSegmentNode correctly includes SEGMENT_TYPE_INDEX since index nodes are static by nature
  • SegmentNode uses ExtendedSegmentKind internally while keeping the public API clean
  • Priority order is clearly documented in comments

847-850: Clarify the TODO comment regarding correctness.

The comment states this is an optimization but then contradicts itself with "TODO: it doesn't actually work correctly without this block". This suggests the early return is actually required for correctness, not just performance.

If this is a known limitation, consider documenting why the algorithm doesn't handle root index correctly without this special case, or refactoring to fix the underlying issue.


887-894: LGTM!

Correctly excludes index nodes from fuzzy matching candidates. Index routes represent exact path matches and should not serve as partial match fallbacks for longer paths.


896-917: LGTM!

The matching logic correctly handles the new index segment type:

  • Only matches index nodes when the path has a trailing slash (pathIsIndex)
  • Perfect match optimization now correctly requires SEGMENT_TYPE_INDEX for early return
  • Continues exploration when the node has an index child

1051-1064: LGTM!

The index matching logic correctly:

  • Only attempts index match when path segments are consumed (index >= partsLength)
  • Guards against matching at the root level (index > 1)
  • Maintains correct statistics (statics not incremented since index doesn't consume a segment)

1092-1111: LGTM!

The isFrameMoreSpecific function correctly prioritizes index nodes in the specificity comparison. The boolean-to-number coercion in (next.node.kind === SEGMENT_TYPE_INDEX) > (prev.node.kind === SEGMENT_TYPE_INDEX) is valid JavaScript/TypeScript behavior (true > false evaluates as 1 > 0).

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 21, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6171

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@6171

@tanstack/eslint-plugin-router

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

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6171

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6171

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6171

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6171

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@6171

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6171

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6171

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6171

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6171

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6171

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6171

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6171

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6171

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6171

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6171

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6171

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6171

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6171

commit: a8b8021

Copy link
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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d72eb6 and 8c87dee.

📒 Files selected for processing (2)
  • packages/router-core/src/new-process-route-tree.ts (13 hunks)
  • packages/router-plugin/src/core/route-hmr-statement.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-plugin/src/core/route-hmr-statement.ts
  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-plugin/src/core/route-hmr-statement.ts
  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-plugin/src/core/route-hmr-statement.ts
  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-plugin/src/core/route-hmr-statement.ts
  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-plugin/src/core/route-hmr-statement.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
🧬 Code graph analysis (1)
packages/router-core/src/new-process-route-tree.ts (1)
packages/router-core/src/route.ts (1)
  • path (1553-1555)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (9)
packages/router-core/src/new-process-route-tree.ts (8)

10-10: LGTM: Type system updates for index nodes.

The introduction of SEGMENT_TYPE_INDEX and ExtendedSegmentKind, along with the new index field on segment nodes, properly extends the type system to support explicit index route representation. The documentation clearly indicates the priority ordering.

Also applies to: 21-24, 480-480, 498-506


435-435: LGTM: Index field initialization.

Both node creation functions correctly initialize the index field to null, consistent with other child collection fields.

Also applies to: 464-464


338-358: Index node creation logic is correct.

The implementation properly creates explicit index nodes for leaf routes with trailing slashes. The logic ensures:

  • Index nodes are only created for leaf routes (route.path or no children) with trailing slashes
  • The index node becomes a child of the path segment node via node.index
  • The route is associated with the index node (line 356 executes after node is reassigned to indexNode)

This correctly separates layout nodes (e.g., /foo) from their index route nodes (e.g., /foo/).


724-727: LGTM: Fuzzy match support in findMatch.

The optional chaining at line 724 correctly handles the case where getNodeMatch returns null. The subsequent extraction of the '**' parameter (line 726) supports fuzzy matching where partial paths are captured. The logic is sound since line 724's early return guarantees leaf.node.route exists for lines 726-727.


887-894: LGTM: Index nodes correctly excluded from fuzzy matching.

Index nodes represent exact terminal routes ending with / and should not serve as fuzzy match candidates for partial path prefixes. The condition node.kind !== SEGMENT_TYPE_INDEX appropriately restricts fuzzy matching to layout and non-index route nodes.


896-916: LGTM: Index-aware exact matching logic.

The matching logic correctly handles index routes:

  • Line 898 ensures paths ending with / only match index nodes
  • Lines 905-913 provide an optimization for perfect index matches (all static segments, no dynamics/optionals)
  • Line 916 correctly allows only optional, wildcard, or index children when beyond the path length

The logic maintains correct matching semantics for the new index node structure.


1052-1063: Index node traversal logic is correct (but depends on root index special case).

The logic correctly adds index nodes to the traversal stack when:

  • The node has an index child
  • We're beyond the root segment (index > 1)
  • We've consumed all path parts (index >= partsLength)

The index > 1 condition assumes the root index case is handled by the early return at line 850 (already flagged as a concern in a separate comment). If that early return were removed, root index matching would break.


1106-1110: LGTM: Index node priority in match specificity.

The comparison correctly prioritizes index nodes over layout nodes when all other matching factors (statics, dynamics, optionals) are equal. This ensures that routes with trailing slashes (/foo/) are preferred over their layout counterparts (/foo) when both could match.

packages/router-plugin/src/core/route-hmr-statement.ts (1)

43-43: LGTM: HMR traversal updated for index nodes.

The change correctly replaces the removed node.notFound handling with recursion via node.index. This is consistent with how other child collections (static, dynamic, etc.) are traversed in the same function. The route ID match check at line 42 handles updating the route reference when found in the tree.

Copy link
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: 0

🧹 Nitpick comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

726-726: Consider using explicit undefined check instead of in operator.

The in operator on line 726 works correctly for checking if the '**' property exists, but it's an unusual pattern for optional properties in this codebase. An explicit check would be more conventional:

-  if ('**' in leaf) params['**'] = leaf['**']!
+  if (leaf['**'] !== undefined) params['**'] = leaf['**']

This also avoids the non-null assertion.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c87dee and d6d4c26.

📒 Files selected for processing (1)
  • packages/router-core/src/new-process-route-tree.ts (13 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (8)
packages/router-core/src/new-process-route-tree.ts (8)

10-10: LGTM: Index segment type is appropriately scoped as internal.

The SEGMENT_TYPE_INDEX constant is intentionally not exported (unlike the other SEGMENT_TYPE_* constants on lines 6-9), which correctly reflects that index nodes are an internal implementation detail of the segment tree. The ExtendedSegmentKind type properly extends SegmentKind for internal use.

Also applies to: 21-24


339-358: LGTM: Index node creation logic correctly handles route assignment.

The flow properly handles index routes:

  1. Creates an index child node when the path ends with / (line 342-352)
  2. Reassigns node to point to the newly created index node (line 351)
  3. Assigns the route to the current node, which is now the index node if one was created (lines 355-358)

This ensures index routes are stored on dedicated index nodes rather than shared nodes with layout routes.


435-435: LGTM: Type definitions and initializations are consistent.

The index field is properly:

  • Initialized to null in both createStaticNode and createDynamicNode
  • Typed as StaticSegmentNode<T> | null, correctly restricting index nodes to static segments
  • Declared with ExtendedSegmentKind for the parent node's kind field

The priority comment on line 500 correctly identifies index segments as highest priority.

Also applies to: 464-464, 480-480, 498-501


886-893: LGTM: Index routes correctly excluded from fuzzy matching.

Index routes require exact path matches (with trailing slash), so excluding them from fuzzy matching on line 889 is correct. Fuzzy matching is for finding partial matches, which doesn't apply to index routes.


897-897: LGTM: Index matching conditions are correct.

Line 897 correctly requires node.kind === SEGMENT_TYPE_INDEX when the path has a trailing slash (pathIsIndex), ensuring only index nodes match paths like /foo/.

The perfect match optimization (lines 904-912) provides an early return when all parts are static, no dynamics/optionals are involved, and the node is an index—this is a valid optimization for the most common exact-match case.

Also applies to: 904-912


915-915: LGTM: Beyond-path logic correctly handles index nodes.

When beyond the path parts, the algorithm now correctly continues exploring node.index, node.optional, and node.wildcard branches. This allows index nodes to match when the path has been fully consumed.


1051-1062: LGTM: Index node traversal correctly handles path termination.

This new traversal branch correctly adds index child nodes to the stack when:

  • An index node exists (node.index)
  • All path segments have been consumed (index >= partsLength)

The frame counters are appropriately updated: only depth is incremented while index, statics, dynamics, and optionals remain unchanged, because index nodes don't consume path parts and represent a distinct matching category.


1105-1109: LGTM: Frame comparison correctly prioritizes index nodes.

The updated comparison logic correctly prioritizes index nodes over non-index nodes when all other factors (statics, dynamics, optionals) are equal. The boolean comparison (next.node.kind === SEGMENT_TYPE_INDEX) > (prev.node.kind === SEGMENT_TYPE_INDEX) evaluates to true only when next is an index node and prev is not, which is the intended behavior.

When both frames have the same index status, depth provides the tiebreaker (line 1109).

},
{
id: '/dashboard',
id: '/dashboard/invoices',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has nothing to do w/ the rest of this PR, it was just incorrect before so i did a drive-by fix

Copy link
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: 1

♻️ Duplicate comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

847-850: Update misleading comment about root index optimization.

The comment claims "this is an optimization, algorithm should work correctly without this block," but based on previous review feedback, this is actually a necessary correctness fix, not an optional optimization.

The general index matching logic at lines 1053-1062 may not handle the root index case correctly, making this early return essential for proper routing behavior.

🔎 Suggested comment update
-  // quick check for root index
-  // this is an optimization, algorithm should work correctly without this block
+  // Fast path for root index route
+  // This is required for correctness (general algorithm doesn't handle root index properly)
   if (path === '/' && segmentTree.index)
     return { node: segmentTree.index, skipped: 0 }
🧹 Nitpick comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

726-727: Type safety: Wildcard parameter access relies on runtime check without proper typing.

The code uses '**' in leaf to guard leaf['**']!, but leaf's type signature (line 737) doesn't include the optional '**': string property that fuzzy matching adds (lines 1082-1086). TypeScript cannot verify the non-null assertion is safe.

🔎 Suggested type improvement

Update the return type of getNodeMatch and the leaf parameter type in extractParams to reflect the optional wildcard property:

// In getNodeMatch return type (around line 1082-1086)
return {
  node: bestFuzzy.node,
  skipped: bestFuzzy.skipped,
  '**': decodeURIComponent(splat),
} as { node: AnySegmentNode<T>; skipped: number; '**'?: string }

// Update extractParams signature (line 737)
function extractParams<T extends RouteLike>(
  path: string,
  parts: Array<string>,
  leaf: { node: AnySegmentNode<T>; skipped: number; '**'?: string },
) {

Then replace the non-null assertion with a proper check:

-  if ('**' in leaf) params['**'] = leaf['**']!
+  if (leaf['**'] !== undefined) params['**'] = leaf['**']
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd36c32 and 0fe3ad6.

📒 Files selected for processing (1)
  • packages/router-core/src/new-process-route-tree.ts (11 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (3)
packages/router-core/src/new-process-route-tree.ts (3)

339-358: Well-structured index node creation.

The logic correctly:

  • Identifies leaf routes ending with /
  • Creates a dedicated index child node
  • Maintains proper parent-child relationships and depth tracking
  • Makes the index node matchable for terminal matches

This implementation cleanly achieves the PR's goal of separating index routes into dedicated nodes in the segment tree.


1051-1063: Index traversal logic is sound, but relies on buggy terminal filter.

The index node traversal correctly:

  • Triggers only when at or beyond path length (index >= partsLength)
  • Maintains proper frame state (doesn't inflate statics count)
  • Enables index routes to participate in matching

However, this depends on the terminal node filter at line 898 to prevent index nodes from matching non-trailing-slash paths, which currently has a bug (see previous comment).


1106-1110: Specificity comparison correctly prioritizes index nodes.

The updated comparison properly places index nodes higher in the priority hierarchy (after statics, dynamics, and optionals). The boolean comparison (kind === SEGMENT_TYPE_INDEX) > ... correctly evaluates to prefer index-typed nodes.

This logic is sound in principle, but it relies on the terminal filter at line 898 to ensure index and non-index nodes don't compete inappropriately. Once that filter is fixed, this comparison will work correctly.

Copy link
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: 0

♻️ Duplicate comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

771-771: Unsafe non-null assertion on optional field.

The non-null assertion leaf.skipped! is applied to an optional field. While undefined & number evaluates to falsy NaN, it's semantically incorrect.

🔎 Proposed fix
-      if (leaf.skipped & (1 << nodeIndex)) {
+      if ((leaf.skipped ?? 0) & (1 << nodeIndex)) {
🧹 Nitpick comments (2)
packages/router-core/src/new-process-route-tree.ts (2)

726-727: Type safety: Return type doesn't include optional ** property.

The code checks for a ** property on leaf (line 726) and extracts it (line 727), but the function getNodeMatch returns { node: AnySegmentNode<T>; skipped: number } which doesn't declare the optional ** field. Line 1085 shows that fuzzy matches return this additional property.

Per coding guidelines requiring TypeScript strict mode, the return type should be a union:

{ node: AnySegmentNode<T>; skipped: number } | 
{ node: AnySegmentNode<T>; skipped: number; '**': string }

Or use an intersection with an optional property:

{ node: AnySegmentNode<T>; skipped: number; '**'?: string }
🔎 Suggested type-safe approach

Update the return type of getNodeMatch around line 841:

 function getNodeMatch<T extends RouteLike>(
   path: string,
   parts: Array<string>,
   segmentTree: AnySegmentNode<T>,
   fuzzy: boolean,
-) {
+): { node: AnySegmentNode<T>; skipped: number; '**'?: string } | null {

This makes the optional ** property explicit in the type system.


1106-1110: Index node specificity correctly prioritized.

The specificity comparison correctly prioritizes index nodes over non-index nodes when all other metrics (statics, dynamics, optionals) are equal. The boolean-to-number coercion via > is a valid and common pattern for this type of comparison.

The specificity order is now: statics > dynamics > optionals > index > depth, which aligns with the PR objectives.

Optional: Improve readability with explicit number coercion

While the boolean comparison works correctly, explicit coercion might improve readability:

-              ((next.node.kind === SEGMENT_TYPE_INDEX) >
-                (prev.node.kind === SEGMENT_TYPE_INDEX) ||
+              (Number(next.node.kind === SEGMENT_TYPE_INDEX) >
+                Number(prev.node.kind === SEGMENT_TYPE_INDEX) ||

However, this is purely stylistic and the current approach is a well-established pattern.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86cee60 and c4b1a06.

📒 Files selected for processing (1)
  • packages/router-core/src/new-process-route-tree.ts (11 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:27.078Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.
📚 Learning: 2025-12-21T12:52:27.078Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:27.078Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-22T00:56:49.237Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-28T21:41:45.233Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5284
File: e2e/react-start/basic/server.js:50-0
Timestamp: 2025-09-28T21:41:45.233Z
Learning: In Express v5, catch-all routes must use named wildcards. Use `/*splat` to match everything except root path, or `/{*splat}` (with braces) to match including root path. The old `*` syntax is not allowed and will cause "Missing parameter name" errors. This breaking change requires explicit naming of wildcard parameters.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-22T00:56:53.426Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (8)
packages/router-core/src/new-process-route-tree.ts (8)

10-24: LGTM: Clean type extension for index segments.

The introduction of SEGMENT_TYPE_INDEX and ExtendedSegmentKind properly extends the existing segment type system with clear documentation.


339-358: LGTM: Index node creation logic is sound.

The logic correctly creates index nodes for leaf routes ending with /, properly maintains the parent-child relationship, advances depth, and makes the node matchable. The condition isLeaf && path.endsWith('/') accurately identifies when an index node should be created.


435-435: LGTM: Factory functions updated consistently.

Both createStaticNode and createDynamicNode correctly initialize the new index field to null.

Also applies to: 464-464


480-480: LGTM: Type definitions correctly reflect index node semantics.

The type updates properly extend StaticSegmentNode to include SEGMENT_TYPE_INDEX, add the index field to SegmentNode, and update the kind to ExtendedSegmentKind. The constraint that index must be a StaticSegmentNode<T> is correct since index routes are static pathname segments with trailing slashes.

Also applies to: 498-527


847-850: LGTM: Valid optimization for root index route.

This fast path for root index routes (/) is a valid optimization. The general algorithm would handle this case via the index traversal logic at lines 1052-1063, but this early return avoids unnecessary stack operations.


887-894: LGTM: Fuzzy matching correctly excludes index nodes.

The condition node.kind !== SEGMENT_TYPE_INDEX ensures that fuzzy matches (partial path matches) don't resolve to index routes, which makes sense because index routes represent exact trailing-slash endpoints. Fuzzy matching should match layout/parent routes instead.


896-917: LGTM: Beyond-path matching implements intended index semantics.

The matching logic at line 898 correctly implements the design decision documented in learnings: paths without trailing slashes can match index routes (e.g., /a matches /a/), but not vice-versa. The optimization at lines 905-911 efficiently short-circuits when a perfect index match is found. Line 916 appropriately allows continued traversal only for index, optional, and wildcard nodes when beyond the path.

Based on learnings: This permissive behavior provides better UX.


1052-1063: LGTM: Index node traversal correctly integrated.

The traversal logic for index nodes is correctly implemented:

  • Only traverses to index when isBeyondPath (as index represents trailing /)
  • Properly increments depth (index is a child node)
  • Correctly maintains statics, dynamics, and optionals counts (index specificity is handled separately at lines 1106-1110)

Copy link
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: 0

🧹 Nitpick comments (1)
packages/router-core/src/new-process-route-tree.ts (1)

717-732: Consider improving type safety for fuzzy match return value.

The code safely handles the '**' property that can be added during fuzzy matching (line 1085), using an in check before access. However, the type system doesn't capture this optional property on the leaf object.

This is existing behavior and works correctly at runtime, but you could optionally improve type safety by defining a union type that includes the fuzzy match variant.

💡 Optional type safety improvement

You could define a more precise return type for getNodeMatch that includes the optional '**' property for fuzzy matches:

type NodeMatchResult<T> = 
  | { node: AnySegmentNode<T>; skipped: number }
  | { node: AnySegmentNode<T>; skipped: number; '**': string }

This would make the type system aware of the fuzzy match variant.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4b1a06 and a8b8021.

📒 Files selected for processing (1)
  • packages/router-core/src/new-process-route-tree.ts (11 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/src/new-process-route-tree.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/src/new-process-route-tree.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:27.078Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.
📚 Learning: 2025-12-21T12:52:27.078Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:27.078Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-22T00:56:49.237Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-28T21:41:45.233Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5284
File: e2e/react-start/basic/server.js:50-0
Timestamp: 2025-09-28T21:41:45.233Z
Learning: In Express v5, catch-all routes must use named wildcards. Use `/*splat` to match everything except root path, or `/{*splat}` (with braces) to match including root path. The old `*` syntax is not allowed and will cause "Missing parameter name" errors. This breaking change requires explicit naming of wildcard parameters.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-09-22T00:56:53.426Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Implement type-safe routing with search params and path params

Applied to files:

  • packages/router-core/src/new-process-route-tree.ts
🧬 Code graph analysis (1)
packages/router-core/src/new-process-route-tree.ts (1)
packages/router-core/src/route.ts (1)
  • path (1553-1555)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (11)
packages/router-core/src/new-process-route-tree.ts (11)

10-24: LGTM: Index segment type introduced cleanly.

The internal SEGMENT_TYPE_INDEX constant and ExtendedSegmentKind type extension follow the existing pattern for segment types. The comments clearly distinguish route path segments from segment tree segments.


339-358: LGTM: Index node creation logic is correct.

The logic correctly creates index nodes for leaf routes with trailing slashes. The isLeaf condition properly identifies routes that define matchable paths (even if they have children), and the index node is appropriately configured with incremented depth and proper parent linkage.


429-445: LGTM: Static node initialization includes index property.

The index: null initialization is consistent with other optional child properties and aligns with the updated SegmentNode type.


451-477: LGTM: Dynamic node initialization includes index property.

The index: null initialization follows the same pattern as the static node and maintains consistency with the type definition.


479-527: LGTM: Type definitions properly updated for index support.

The type definitions correctly extend SegmentNode to include the index property and update kind to use ExtendedSegmentKind. The comment clearly indicates that index segments have the highest priority, which aligns with the matching logic.


847-850: LGTM: Root index fast path is a valid optimization.

The fast path for root index matching (path === '/') is correctly identified as an optimization. The general index matching logic at lines 1052-1063 would handle this case, but the fast path avoids unnecessary stack operations for this common scenario.


886-894: LGTM: Fuzzy matching correctly excludes index nodes.

The fuzzy matching logic appropriately excludes index nodes (node.kind !== SEGMENT_TYPE_INDEX) when tracking the best partial match. This ensures that partial path matches prefer layout routes over index routes, which is the correct behavior for navigating to parent routes.


896-914: LGTM: Perfect match optimization is correctly implemented.

The early return for perfect index matches (all static segments, no dynamics/optionals/wildcards) is a valid optimization that avoids unnecessary traversal when we've found an exact match. The comment accurately describes this as an optimization rather than a correctness requirement.

Based on learnings: The matching condition at line 898 intentionally allows paths without trailing slashes to match index routes, providing better UX.


915-917: LGTM: Beyond-path matching logic is correct.

The comment and logic correctly identify that only index segments, optional segments, and wildcard segments can match when the path has been fully consumed. This aligns with the routing semantics where these segment types can match at or beyond the path boundary.


1051-1064: LGTM: General index matching logic is sound.

The index matching logic correctly handles the case where the path has been fully consumed and an index child exists. The stack frame properly increments depth while maintaining the path index, reflecting that index nodes are structural children in the tree without consuming additional path segments.


1092-1112: LGTM: Specificity comparison correctly prioritizes index nodes.

The specificity comparison properly includes index node preference as a tiebreaker. The expression (next.node.kind === SEGMENT_TYPE_INDEX) > (prev.node.kind === SEGMENT_TYPE_INDEX) compares boolean values as numbers (true=1, false=0), effectively preferring index routes over layout routes when other factors are equal. This aligns with the design where index routes represent more specific trailing-slash matches.

@Sheraff Sheraff merged commit 7b4dcee into main Dec 21, 2025
6 checks passed
@Sheraff Sheraff deleted the refactor-router-core-segment-tree-index-nodes branch December 21, 2025 15:32
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.

3 participants