Spec: F62 — TanStack Router (Type-Safe Routing)
Status: partial (source-backed; frontend/browser runtime evidence remains open)
1. Problem & CEO Job
See clearly (job 1). The application needs one auditable routing source of truth for dynamic paths, redirects, lazy boundaries, and navigation. TanStack Router provides typed route construction and navigation while the compatibility shim allows the existing page call sites to migrate incrementally.
Implementation detail, not user-facing. Zero visible change to the CEO.
2. User Stories & Acceptance Criteria
-
As a developer, I want type-safe route params and search params, so that missing or malformed params produce compile errors instead of runtime crashes.
- Given a route with
/:id, accessingroute.idis typed asstring, neverstring | undefined— proved by$paramsyntax inrouteTree.tsanddeclare module '@tanstack/react-router'Register - Given a search parameter
?status=open, the route’ssearchtype is checked at compile time — Zod search schemas on/work/tasksand/crm/dealsroutes
- Given a route with
-
As a developer, I want loader data automatically typed across route boundaries, so that I never cast
anyfrom a loader to a component.- Given a route loader returning
Task[], the component readsdataasTask[]— loader on/work/todaywired in Phase 2
- Given a route loader returning
-
As a developer, I want to be able to co-locate routes with their data dependencies (TanStack Query integration), so that prefetching and loading states are handled declaratively.
- Given a route, I can call
queryClient.ensureQueryDatain a loader and the component reads cached data — pending Phase 3 runtime cutover
- Given a route, I can call
3. Scope Boundaries
- In: Installation, route tree definition, typed dynamic paths, legacy redirects, lazy route components, intent/pre-main matched-route preload wiring, and incremental migration of existing page call sites
- Out: Nested layouts/outlets beyond what we already have, code-splitting per route (not beneficial at current scale), SSR (we’re a SPA)
4. Data Model
n/a — no models touched.
5. API Contract
n/a — routing layer only. No new API endpoints.
6. UI / UX
- Route tree:
frontend/src/routes/routeTree.ts— the canonical flat/nested tree with 107createRoute(...)declarations,$parampaths, auth guards, redirects, and a registered router - Router entry:
frontend/src/main.tsxrenders TanStackRouterProviderwith the exported router;frontend/src/lib/router.tsxis a compatibility shim over TanStack primitives for incremental call-site migration - Pre-main route warm-up:
earlyAuthenticatedRoutePreloadEntry.tsis a separate Vite entry that advances Layout and the matched authenticated P0 leaf only when the existing non-secret session hint is present and logout is not pending; it does not guard routes or start data requests - Migration strategy: route definitions and boot are on TanStack Router. Current source inspection found zero
react-router-domimports underfrontend/src; 94 page/component files use the local compatibility shim. Query-loader integration remains a separate future boundary. - States: 404 catch-all route renders
NotFoundPage; loader error shows toast + fallback - Components used:
Link(typed, to props compile-checked);router.navigate({ to: '...' })compile-checked - i18n: n/a — routing keys stay in
sidebarnamespace
7. Test Plan
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Typed route params compile correctly | type | tsc --noEmit |
| 2 | Invalid route param access is caught at compile time | type | tsc --noEmit |
| 3 | All existing links still navigate correctly | e2e | lumi.spec.ts (existing describe blocks) |
Migration is incremental — no single “ship” gate. Existing tests must pass at every step.
8. Current Behavior
The current source has 107 createRoute(...) declarations, a createRouter({ routeTree }) registration, typed dynamic route construction, legacy redirects, lazy intent preloaders, and a separate pre-main matched-route warm-up entry. Focused Vitest and production-build checks execute locally; browser navigation and deployed behavior remain explicit evidence gaps. Query-loader integration remains pending as a separate implementation phase.
9. Error States
- 404: TanStack Router’s catch-all route renders
NotFoundPage(existing) - Loader error: TanStack Router’s
onErrorhandler shows a toast + fallback content - Compile error on migration:
tsc --noEmitcatches undefined params before deploy
10. Diagrams & Docs Touched
frontend/DIAGRAMS.md§1 route tree — update schema to show typed linksfrontend/AGENTS.md— add routing convention
11. Implementation Audit
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | @tanstack/react-router installed | ✅ match | frontend/package.json |
| 2 | Route tree at frontend/src/routes/routeTree.ts with createRootRoute + typed leaf routes | ✅ match | routeTree.ts contains 107 createRoute(...) declarations and $param paths |
| 3 | Dynamic params use $param syntax (/work/projects/$id/board, /accept-invitation/$token) | ✅ match | Phase 2; routeTree.ts |
| 4 | router.navigate({ to: '...' }) compile-time checked | ✅ source match | tanstack-router.test.ts; no current command-bound tsc result retained |
| 5 | Route-tree and preload tests registered | ✅ pass | Focused router selection 139/139 plus production build on 2026-07-16; browser execution remains open |
| 6 | frontend/DIAGRAMS.md §1 updated with Phase 2 note | ✅ match | frontend/DIAGRAMS.md |
| 7 | Browser navigation runtime execution | ⚠️ gap | Router Vitest and production build passed locally; the registered Playwright navigation suite is not retained as commit-bound browser evidence |
12. Open Questions / Decisions
- Whether to use file-based routing (
@tanstack/router-plugin) or flat file route tree - Decision: Keep the current
routeTree.tssource of truth. It is larger than the original estimate, but source inspection and registered tests are aligned to the live tree; splitting it is outside this slice.
13. Bug / Change Log
- 2026-07-16: Added a build-separated pre-main entry for remembered authenticated P0 routes. It advances only Layout and the matched leaf, skips public/unknown paths and pending logout, and leaves refresh/router authorization unchanged. Build and unit guards enforce entry separation and ordering.
- 2026-07-16: Contract Graph audit reconciled the stale
BrowserRouter/react-router-domclaims with current source.main.tsxuses TanStackRouterProvider; no app-sourcereact-router-domimports remain. Browser and query-loader runtime evidence remain explicit gaps. - 2026-06-15: Frontend route namespaced:
/today->/work/today,/tasks->/work/tasks,/projects/$id/board->/work/projects/$id/board,/deals->/crm/deals(app-wide IA restructure; API paths unchanged). - 2026-06-14: SP-C audit. Corrected §6 route tree path (
routes.ts→routeTree.ts), updated §2 ACs to reflect Phase 1+2 shipped status, fixed §8 version (v6 → v7). Phase 3 (runtime cutover, 77react-router-domimports) is the remaining gap.