Skip to content

fix: allow typed NavigationItem as TNavItem#18

Merged
diogomiguel merged 1 commit intomainfrom
fix/navigation-item-base
Apr 20, 2026
Merged

fix: allow typed NavigationItem as TNavItem#18
diogomiguel merged 1 commit intomainfrom
fix/navigation-item-base

Conversation

@diogomiguel
Copy link
Copy Markdown
Collaborator

@diogomiguel diogomiguel commented Apr 20, 2026

Summary

TNavItem extends NavigationItem resolved to NavigationItem<string, void, unknown>,
pinning to to string. Consumers aliasing NavigationItem<Keys, Ctx, Meta> couldn't
pass it as a type argument without @ts-expect-error / as unknown as at every call
to createRegistry, useNavigation, defineModule, buildNavigationManifest,
AnyModuleDescriptor, and NavigationManifest.

Fix

Add NavigationItemBase — a structural upper bound using (ctx: never) => string
for the function branch of to. Function parameters are contravariant, so any
concrete (ctx: TContext) => string satisfies it. No any, no soundness loss.

Replace every TNavItem extends NavigationItemTNavItem extends NavigationItemBase
across core, react, react-router-{core,runtime}, tanstack-router-{core,runtime}.

Before — consumer forced into workarounds at every call site:

// AppNavItem has TContext = { workspaceId: string } → to: string | ((ctx) => string)
type AppNavItem = NavigationItem<ParseKeys, { workspaceId: string }, { action?: Action }>

// ❌ Type error — can't pass AppNavItem as TNavItem
const registry = createRegistry<Deps, Slots, AppNavItem>({})

// Workaround: omit the generic, suppress at register()
const registry = createRegistry<Deps, Slots>({})
// @ts-expect-error: to: (ctx) => string not assignable to to: string
registry.register(portalModule)

// @ts-expect-error at useNavigation() too — or cast through unknown
const { items } = useNavigation() as unknown as { items: readonly AppNavItem[] }

After — clean end-to-end:

const registry = createRegistry<Deps, Slots, AppNavItem>({})
registry.register(portalModule)
const { items } = useNavigation<AppNavItem>()

Module descriptors are static objects defined at load time (outside React), so to
can't close over runtime values like workspaceId. The function form defers
interpolation — resolveNavHref(item, { workspaceId }) supplies the value at render
time. That pattern is only ergonomic if the type system allows non-void TContext
without friction.

Notes

Additive change. NavigationItem is unchanged; existing to: string consumers
are unaffected. No runtime change.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced NavigationItemBase type across core packages, enabling more flexible and compatible navigation item typing throughout the framework.
  • Tests

    • Added comprehensive type-level tests validating structural type constraints and end-to-end type flow for navigation items across the module system.

@diogomiguel diogomiguel self-assigned this Apr 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@diogomiguel has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 33 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 33 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c36bae2b-9886-4b80-9674-65ac8ba80f4b

📥 Commits

Reviewing files that changed from the base of the PR and between 53bd1af and 6aad521.

📒 Files selected for processing (22)
  • packages/core/src/define-module.ts
  • packages/core/src/index.ts
  • packages/core/src/navigation-item-base.test.ts
  • packages/core/src/navigation.test.ts
  • packages/core/src/navigation.ts
  • packages/core/src/runtime-types.ts
  • packages/core/src/slots.ts
  • packages/core/src/types.ts
  • packages/react-router-core/src/define-module.ts
  • packages/react-router-core/src/index.ts
  • packages/react-router-core/src/types.ts
  • packages/react-router-runtime/src/providers.tsx
  • packages/react-router-runtime/src/registry.ts
  • packages/react-router-runtime/src/types.ts
  • packages/react/src/index.ts
  • packages/react/src/navigation-context.tsx
  • packages/tanstack-router-core/src/define-module.ts
  • packages/tanstack-router-core/src/index.ts
  • packages/tanstack-router-core/src/types.ts
  • packages/tanstack-router-runtime/src/providers.tsx
  • packages/tanstack-router-runtime/src/registry.ts
  • packages/tanstack-router-runtime/src/types.ts
📝 Walkthrough

Walkthrough

This pull request introduces NavigationItemBase, a new relaxed base type constraint for navigation items, and updates generic type parameters across the modular-react framework. All functions and interfaces that previously constrained TNavItem extends NavigationItem now extend NavigationItemBase instead, with a default of NavigationItem. The change is purely type-level with no runtime behavior modifications.

Changes

Cohort / File(s) Summary
Core Type Definitions
packages/core/src/types.ts
Introduces new NavigationItemBase export with relaxed properties (label, icon, meta, to as unknown or union types). Updates ModuleDescriptor, LazyModuleDescriptor, and AnyModuleDescriptor to use NavigationItemBase as the constraint for TNavItem instead of NavigationItem.
Core Function & Interface Constraints
packages/core/src/define-module.ts, packages/core/src/navigation.ts, packages/core/src/runtime-types.ts, packages/core/src/slots.ts
Updates generic constraints for defineModule, buildNavigationManifest, NavigationGroup, NavigationManifest, and buildSlotsManifest to extend NavigationItemBase instead of NavigationItem. Adds explicit string cast for label comparison in sort tie-breaker.
Core Exports & Tests
packages/core/src/index.ts, packages/core/src/navigation-item-base.test.ts, packages/core/src/navigation.test.ts
Adds NavigationItemBase to public type re-exports. Introduces comprehensive compile-only test suite validating NavigationItemBase as structural upper bound and verifying type narrowing through library surface. Updates test helper mod to constrain to NavigationItemBase.
React Router Core
packages/react-router-core/src/define-module.ts, packages/react-router-core/src/types.ts, packages/react-router-core/src/index.ts
Updates defineModule and module descriptor types to use NavigationItemBase constraint. Adds NavigationItemBase to public exports.
React Router Runtime
packages/react-router-runtime/src/providers.tsx, packages/react-router-runtime/src/registry.ts, packages/react-router-runtime/src/types.ts
Narrows ProvidersProps.navigation to NavigationManifest<NavigationItemBase>. Updates ModuleRegistry and createRegistry constraints. Changes ApplicationManifest and ResolvedManifest defaults to NavigationItemBase.
React Package
packages/react/src/index.ts, packages/react/src/navigation-context.tsx
Adds NavigationItemBase to exports. Tightens NavigationContext type to NavigationManifest<NavigationItemBase> and updates useNavigation constraint to extend NavigationItemBase.
TanStack Router Core
packages/tanstack-router-core/src/define-module.ts, packages/tanstack-router-core/src/types.ts, packages/tanstack-router-core/src/index.ts
Updates defineModule and module descriptor types to use NavigationItemBase constraint. Re-exports NavigationItemBase from core package.
TanStack Router Runtime
packages/tanstack-router-runtime/src/providers.tsx, packages/tanstack-router-runtime/src/registry.ts, packages/tanstack-router-runtime/src/types.ts
Narrows ProvidersProps.navigation to NavigationManifest<NavigationItemBase>. Updates ModuleRegistry and createRegistry constraints. Changes ApplicationManifest and ResolvedManifest defaults to NavigationItemBase.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Address gaps from Lokalise PoC #14: Modifies the same navigation-type generics and ModuleDescriptor-related signatures in core types and define-module files, directly aligned with this PR's constraint updates.
  • feat: Add support for framework mode in Tanstack #15: Updates ResolvedManifest and ApplicationManifest TNavItem generics, which this PR also modifies by changing the constraint from NavigationItem to NavigationItemBase.
  • Address review comments #16: Modifies navigation-related type signatures and introduces use of AnyModuleDescriptor, which is affected by the TNavItem constraint changes in this PR.

Poem

🐰 A base so broad, a type so free,
NavigationItems now align with thee,
Generic bounds relaxed with care,
Through routers and hooks, changes everywhere!
The rabbit hops through type constraints with glee! 🌿✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 accurately summarizes the main change: introducing NavigationItemBase as a structural upper bound to allow typed NavigationItem generics as TNavItem across APIs, fixing the type constraint issue.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/navigation-item-base

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
packages/core/src/navigation-item-base.test.ts (1)

93-98: Clarify the never variance wording.

The concrete ctx type is a supertype of never, not “narrower than never”. The comment below already has the right reasoning, so this is just a wording fix.

Proposed wording tweak
-    it("accepts a `to` function whose ctx is narrower than `never`", () => {
+    it("accepts a `to` function whose ctx is a supertype of `never`", () => {
       // Function parameters are contravariant — any concrete ctx widens to
       // satisfy `(ctx: never) => string`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/navigation-item-base.test.ts` around lines 93 - 98, Update
the inline comment in the test case that currently says "accepts a `to` function
whose ctx is narrower than `never`" to correctly state that the concrete ctx
type is a supertype (or "wider than") `never`; adjust the phrasing near the `it`
block and the explanatory line "// Function parameters are contravariant — any
concrete ctx widens to satisfy `(ctx: never) => string`." so the wording
consistently refers to the concrete ctx being a supertype/wider than `never`
(reference: the `it` block and the comment above it in
navigation-item-base.test.ts).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/navigation.test.ts`:
- Line 3: The tests reference the NavigationItem type but only import
NavigationItemBase and AnyModuleDescriptor; add NavigationItem to the import
list so TypeScript can resolve the type. Update the import statement that
currently imports AnyModuleDescriptor and NavigationItemBase to also include
NavigationItem (so symbols NavigationItem, NavigationItemBase, and
AnyModuleDescriptor are imported together) and run the tests/TS compile to
confirm the missing type error is resolved.

---

Nitpick comments:
In `@packages/core/src/navigation-item-base.test.ts`:
- Around line 93-98: Update the inline comment in the test case that currently
says "accepts a `to` function whose ctx is narrower than `never`" to correctly
state that the concrete ctx type is a supertype (or "wider than") `never`;
adjust the phrasing near the `it` block and the explanatory line "// Function
parameters are contravariant — any concrete ctx widens to satisfy `(ctx: never)
=> string`." so the wording consistently refers to the concrete ctx being a
supertype/wider than `never` (reference: the `it` block and the comment above it
in navigation-item-base.test.ts).
🪄 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: cb2b201c-07b2-41b6-a5e7-8d36fa614222

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5dbed and 53bd1af.

📒 Files selected for processing (22)
  • packages/core/src/define-module.ts
  • packages/core/src/index.ts
  • packages/core/src/navigation-item-base.test.ts
  • packages/core/src/navigation.test.ts
  • packages/core/src/navigation.ts
  • packages/core/src/runtime-types.ts
  • packages/core/src/slots.ts
  • packages/core/src/types.ts
  • packages/react-router-core/src/define-module.ts
  • packages/react-router-core/src/index.ts
  • packages/react-router-core/src/types.ts
  • packages/react-router-runtime/src/providers.tsx
  • packages/react-router-runtime/src/registry.ts
  • packages/react-router-runtime/src/types.ts
  • packages/react/src/index.ts
  • packages/react/src/navigation-context.tsx
  • packages/tanstack-router-core/src/define-module.ts
  • packages/tanstack-router-core/src/index.ts
  • packages/tanstack-router-core/src/types.ts
  • packages/tanstack-router-runtime/src/providers.tsx
  • packages/tanstack-router-runtime/src/registry.ts
  • packages/tanstack-router-runtime/src/types.ts

Comment thread packages/core/src/navigation.test.ts Outdated
Every `TNavItem extends NavigationItem` constraint resolved to
`NavigationItem<string, void, unknown>`, pinning `to` to `string`.
Consumers aliasing `NavigationItem<Keys, Ctx, Meta>` couldn't pass it
as a type argument without `@ts-expect-error` / `as unknown as`.
@diogomiguel diogomiguel force-pushed the fix/navigation-item-base branch from 53bd1af to 6aad521 Compare April 20, 2026 11:50
@diogomiguel diogomiguel requested a review from kibertoad April 20, 2026 12:44
@diogomiguel diogomiguel merged commit a0983ca into main Apr 20, 2026
17 checks passed
@diogomiguel diogomiguel deleted the fix/navigation-item-base branch April 20, 2026 13:40
@coderabbitai coderabbitai Bot mentioned this pull request Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants