From 6d2920d60f84615dd1b2bd82ead21b6b3c3316e1 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Wed, 4 Feb 2026 20:38:30 +0100 Subject: [PATCH] feat(16-04): add TanStack Router file-based routes and app shell layout Create route structure with __root, index (redirect to /initiatives), initiatives/index (dashboard stub), initiatives/$id (detail stub with type-safe params), and inbox (stub). Add AppLayout with persistent nav header matching wireframe: title, New Initiative button, navigation tabs with active highlighting. Disabled tabs for Agents/Tasks/Settings (out of scope). Replace temporary health-check App with RouterProvider. Route tree auto-generated by TanStack Router Vite plugin. --- packages/web/src/App.tsx | 16 +-- packages/web/src/layouts/AppLayout.tsx | 57 +++++++++ packages/web/src/routeTree.gen.ts | 113 ++++++++++++++++++ packages/web/src/router.tsx | 10 ++ packages/web/src/routes/__root.tsx | 16 +++ packages/web/src/routes/inbox.tsx | 14 +++ packages/web/src/routes/index.tsx | 7 ++ packages/web/src/routes/initiatives/$id.tsx | 16 +++ packages/web/src/routes/initiatives/index.tsx | 19 +++ 9 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 packages/web/src/layouts/AppLayout.tsx create mode 100644 packages/web/src/routeTree.gen.ts create mode 100644 packages/web/src/router.tsx create mode 100644 packages/web/src/routes/__root.tsx create mode 100644 packages/web/src/routes/inbox.tsx create mode 100644 packages/web/src/routes/index.tsx create mode 100644 packages/web/src/routes/initiatives/$id.tsx create mode 100644 packages/web/src/routes/initiatives/index.tsx diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 22269a7..0949b99 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,16 +1,8 @@ -import { trpc } from './lib/trpc'; +import { RouterProvider } from '@tanstack/react-router' +import { router } from './router' function App() { - const health = trpc.health.useQuery(); - - return ( -
-

Codewalk District

-

- Server: {health.data?.status ?? 'connecting...'} -

-
- ); + return } -export default App; +export default App diff --git a/packages/web/src/layouts/AppLayout.tsx b/packages/web/src/layouts/AppLayout.tsx new file mode 100644 index 0000000..9ad3369 --- /dev/null +++ b/packages/web/src/layouts/AppLayout.tsx @@ -0,0 +1,57 @@ +import { Link } from '@tanstack/react-router' +import { Button } from '@/components/ui/button' + +const navItems = [ + { label: 'Initiatives', to: '/initiatives', enabled: true }, + { label: 'Inbox', to: '/inbox', enabled: true }, + { label: 'Agents', to: '#', enabled: false }, + { label: 'Tasks', to: '#', enabled: false }, + { label: 'Settings', to: '#', enabled: false }, +] as const + +export function AppLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {/* Header */} +
+
+ + Codewalk District + + +
+ {/* Navigation */} + +
+ + {/* Page content */} +
+ {children} +
+
+ ) +} diff --git a/packages/web/src/routeTree.gen.ts b/packages/web/src/routeTree.gen.ts new file mode 100644 index 0000000..5ad0e2a --- /dev/null +++ b/packages/web/src/routeTree.gen.ts @@ -0,0 +1,113 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as InboxRouteImport } from './routes/inbox' +import { Route as IndexRouteImport } from './routes/index' +import { Route as InitiativesIndexRouteImport } from './routes/initiatives/index' +import { Route as InitiativesIdRouteImport } from './routes/initiatives/$id' + +const InboxRoute = InboxRouteImport.update({ + id: '/inbox', + path: '/inbox', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const InitiativesIndexRoute = InitiativesIndexRouteImport.update({ + id: '/initiatives/', + path: '/initiatives/', + getParentRoute: () => rootRouteImport, +} as any) +const InitiativesIdRoute = InitiativesIdRouteImport.update({ + id: '/initiatives/$id', + path: '/initiatives/$id', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/inbox': typeof InboxRoute + '/initiatives/$id': typeof InitiativesIdRoute + '/initiatives/': typeof InitiativesIndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/inbox': typeof InboxRoute + '/initiatives/$id': typeof InitiativesIdRoute + '/initiatives': typeof InitiativesIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/inbox': typeof InboxRoute + '/initiatives/$id': typeof InitiativesIdRoute + '/initiatives/': typeof InitiativesIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/inbox' | '/initiatives/$id' | '/initiatives/' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/inbox' | '/initiatives/$id' | '/initiatives' + id: '__root__' | '/' | '/inbox' | '/initiatives/$id' | '/initiatives/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + InboxRoute: typeof InboxRoute + InitiativesIdRoute: typeof InitiativesIdRoute + InitiativesIndexRoute: typeof InitiativesIndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/inbox': { + id: '/inbox' + path: '/inbox' + fullPath: '/inbox' + preLoaderRoute: typeof InboxRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/initiatives/': { + id: '/initiatives/' + path: '/initiatives' + fullPath: '/initiatives/' + preLoaderRoute: typeof InitiativesIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/initiatives/$id': { + id: '/initiatives/$id' + path: '/initiatives/$id' + fullPath: '/initiatives/$id' + preLoaderRoute: typeof InitiativesIdRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + InboxRoute: InboxRoute, + InitiativesIdRoute: InitiativesIdRoute, + InitiativesIndexRoute: InitiativesIndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/web/src/router.tsx b/packages/web/src/router.tsx new file mode 100644 index 0000000..51ce5b5 --- /dev/null +++ b/packages/web/src/router.tsx @@ -0,0 +1,10 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export const router = createRouter({ routeTree }) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} diff --git a/packages/web/src/routes/__root.tsx b/packages/web/src/routes/__root.tsx new file mode 100644 index 0000000..e995d87 --- /dev/null +++ b/packages/web/src/routes/__root.tsx @@ -0,0 +1,16 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' +import { AppLayout } from '../layouts/AppLayout' + +export const Route = createRootRoute({ + component: () => ( + + + + ), + notFoundComponent: () => ( +
+

Page not found

+

The page you are looking for does not exist.

+
+ ), +}) diff --git a/packages/web/src/routes/inbox.tsx b/packages/web/src/routes/inbox.tsx new file mode 100644 index 0000000..d7e3928 --- /dev/null +++ b/packages/web/src/routes/inbox.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/inbox')({ + component: InboxPage, +}) + +function InboxPage() { + return ( +
+

Agent Inbox

+

Content coming in Phase 19

+
+ ) +} diff --git a/packages/web/src/routes/index.tsx b/packages/web/src/routes/index.tsx new file mode 100644 index 0000000..bba3d5a --- /dev/null +++ b/packages/web/src/routes/index.tsx @@ -0,0 +1,7 @@ +import { createFileRoute, redirect } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + beforeLoad: () => { + throw redirect({ to: '/initiatives' }) + }, +}) diff --git a/packages/web/src/routes/initiatives/$id.tsx b/packages/web/src/routes/initiatives/$id.tsx new file mode 100644 index 0000000..29af1fa --- /dev/null +++ b/packages/web/src/routes/initiatives/$id.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/initiatives/$id')({ + component: InitiativeDetailPage, +}) + +function InitiativeDetailPage() { + const { id } = Route.useParams() + return ( +
+

Initiative Detail

+

Initiative ID: {id}

+

Content coming in Phase 18

+
+ ) +} diff --git a/packages/web/src/routes/initiatives/index.tsx b/packages/web/src/routes/initiatives/index.tsx new file mode 100644 index 0000000..8d7177f --- /dev/null +++ b/packages/web/src/routes/initiatives/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' +import { trpc } from '../../lib/trpc' + +export const Route = createFileRoute('/initiatives/')({ + component: DashboardPage, +}) + +function DashboardPage() { + const health = trpc.health.useQuery() + return ( +
+

Initiative Dashboard

+

+ Server: {health.data?.status ?? 'connecting...'} +

+

Content coming in Phase 17

+
+ ) +}