# Route names: Next.js `app/` to TanStack Start `src/routes/` The web shell moved from Next.js to TanStack Start route by route (M-wave M6). This table is the name each Next file took, so a change that still names a Next path finds its file here. The rules are F10 in [`docs/reference/app-kit.md`](../../../../docs/reference/app-kit.md#f10-pages-on-tanstack-start); the worked example is the `notes` module through `$org.$workspace.$project.$module.$.tsx` and `src/server/page.ts`. A route file holds its server function and its view; what the loader does is in `src/server/account-pages.ts` (outside an organization) and `src/server/org-pages.ts` (under one), imported inside the handler. ## Naming - Flat file names under `src/routes/`, one dot per path segment: `$org._settings.members.$workspace.tsx` is `//members/`. No folders. - `[param]` becomes `$param`; `[...rest]` and `[[...rest]]` become `$` (the splat, read as `params._splat`, empty at the parent's own path). - A Next `layout.tsx` becomes the route file of its own path (`$org.tsx`), rendering ``; the `page.tsx` beside it becomes `.index.tsx`. A layout's checks move to its `loader` (or `beforeLoad` when a child needs the result as context), so they finish before the response starts. - A `route.ts` becomes a route file with `server: { handlers: { GET, ... } }` and no component. - A `loading.tsx` becomes `pendingComponent: RoutePending` on that path's route (`src/ui/route-pending.tsx`), which renders `LoadingPage` with the label from the root loader's `ShellData`. - `error.tsx`, `global-error.tsx`, `not-found.tsx` and `forbidden.tsx` are the root route's `errorComponent` and `notFoundComponent` (done, `__root.tsx`). - Static segments win over `$param` at the same depth, so `$org._settings.members.tsx` and `$org.$workspace.tsx` live side by side, as in Next. - `src/routeTree.gen.ts` is generated by the Start plugin on `vite dev` and `vite build`. It is not committed. - Organization pages share the frame in `$org.tsx`. A validated workspace is retained by `src/ui/workspace-context.ts` while Settings is open, so its sidebar and workspace settings use the workspace just visited even when the organization loader has not rerun. A full reload resolves the workspace preference on the server. In a workspace, the project picker and the open project’s sections follow the workspace destinations in the sidebar. - The organization home lists accessible workspaces with the kit's `ResourceList`. Projects are opened within their workspace. The workspace overview stays at `//` and the dedicated directory is at `///modules`. The picker offers `?create=true` there when the workspace grant allows project creation. A successful submission opens the new project overview at `///`. Workspace and project breadcrumbs start at the workspace. Organization settings begin at the organization. - `/settings/account` opens Personal settings in the last workspace's organization when that organization is still among the signed-in person's memberships. With one organization it uses that one; with none or an ambiguous choice it stays on the standalone account page. A preference cookie never grants access. - Root 404, 403 and error states use the kit's `PageState` and keep the generic not-found response for inaccessible organizations; they do not display the standalone document header. - The multi-organization chooser at `/` uses the same centered `AuthPage` and `AuthCard` as onboarding. Its organization choices are links, and account, sign-out, language and theme controls remain available within the card; they use the shared kit and the shell's existing account and preference handling. ## Page layout contract - List pages give their primary action to `PageHeader` or `PageSection.actions`. Role creation, workspace creation, the overview's project creation and document upload use the kit's `FormPanel`. The project directory also has a directly addressable create form. A panel form is mounted on demand in the same right panel as records, Agent and Help. A phone gets the shared full-screen sheet. The panel protects drafts and cannot close during a pending submission. - `defineResource` and `ResourcePage` remain the default for repeatable record CRUD (Notes is the worked example): one declaration owns columns, fields, mutations, record addresses and list refresh. - Keep settings forms inline when each section is saved independently. Keep short invitation forms with their one-time link result. Onboarding and identity stay on their dedicated entry layout; filter forms remain beside the list they filter. - Full permission matrices, document review and long-running work use full-page editors. A detail layout is appropriate for related record facts and context, rather than a permanent create form. - Optional modules remain disabled by default. Documents upload was verified separately with that module selected in an isolated checkout; this does not establish a live OCR/provider integration. ## The table | Next file (`apps/web/app/...`) | Start file (`apps/web/src/routes/...`) | State | |---|---|---| | `layout.tsx` | `__root.tsx` (loader `shellData`, `shellComponent`) | done (M6a) | | `error.tsx`, `global-error.tsx` | `__root.tsx` `errorComponent` | done (M6a) | | `not-found.tsx`, `forbidden.tsx` | `__root.tsx` `notFoundComponent` (`data.forbidden` is the 403 page) | done (M6a) | | `globals.css` | `src/ui/globals.css`, linked by `__root.tsx`, then `modules/styles.css` | done (M6a) | | `api/[[...rpc]]/route.ts`, `api/auth/[...all]/route.ts` | `api.$.ts` | done (M6a) | | `auth/callback/route.ts`, `auth/dev-login/route.ts`, `auth/sign-in/start/route.ts`, `auth/sign-out/route.ts` | `auth.$.ts` | done (M6a) | | `[org]/[workspace]/[project]/[module]/[[...rest]]/page.tsx` | `$org.$workspace.$project.$module.$.tsx` | done (M6a, `notes`) | | `page.tsx` | `index.tsx` | done (M6b) | | `onboarding/page.tsx` | `onboarding.tsx` | done (M6b) | | `members/page.tsx` | `members.tsx` | done (M6b) | | `settings/account/page.tsx` | `settings.account.tsx` | done (M6b) | | `invitations/[org]/[token]/page.tsx` | `invitations.$org.$token.tsx` | done (M6b) | | `auth/sign-in/page.tsx` | `auth.sign-in.tsx` | done (M6b) | | `auth/sign-up/page.tsx` | `auth.sign-up.tsx` | done (M6b) | | `auth/signed-out/page.tsx` | `auth.signed-out.tsx` | done (M6b) | | `api/brand/og/route.ts` | `api.brand.og.ts` | done (M6b) | | `[org]/layout.tsx` | `$org.tsx` | done (M6c) | | `[org]/page.tsx` | `$org.index.tsx` | done (M6c) | | `[org]/brand/logo/[variant]/route.ts` | `$org.brand.logo.$variant.ts` | done (M6c) | | `[org]/brand/og/route.ts` | `$org.brand.og.ts` | done (M6c) | | `[org]/branding/page.tsx` | `$org._settings.branding.tsx` | done (M6c) | | `[org]/settings/page.tsx` | `$org._settings.settings.index.tsx` | done (M6c) | | `[org]/workspaces/page.tsx` | `$org._settings.workspaces.tsx` | done (M6c) | | `[org]/access-audit/{layout,loading,page}.tsx` | `$org._settings.access-audit.tsx` | done (M6c) | | `[org]/audit/{layout,loading,page}.tsx` | `$org._settings.audit.tsx` | done (M6c); a tab of Settings since 2026-11-03 | | `[org]/invitations/{layout,loading,page}.tsx` | `$org._settings.invitations.tsx` | done (M6c) | | `[org]/members/layout.tsx`, `loading.tsx` | `$org._settings.members.tsx` | done (M6c) | | `[org]/members/page.tsx` | `$org._settings.members.index.tsx` | done (M6c) | | `[org]/members/[workspace]/page.tsx` | `$org._settings.members.$workspace.index.tsx` | done (M6c) | | `[org]/members/[workspace]/[project]/page.tsx` | `$org._settings.members.$workspace.$project.tsx` | done (M6c) | | `[org]/roles/layout.tsx`, `loading.tsx` | `$org._settings.roles.tsx` | done (M6c) | | `[org]/roles/page.tsx` | `$org._settings.roles.index.tsx` | done (M6c) | | `[org]/roles/[role]/page.tsx` | `$org._settings.roles.$role.tsx` | done (M6c) | | `[org]/[workspace]/layout.tsx` | `$org.$workspace.tsx` | done (M6c) | | `[org]/[workspace]/page.tsx` | `$org.$workspace.index.tsx` | done (M6c) | | `[org]/[workspace]/[project]/layout.tsx` | `$org.$workspace.$project.tsx` | done (M6c) | | `[org]/[workspace]/[project]/page.tsx` | `$org.$workspace.$project.index.tsx` | done (M6c) | | `[org]/[workspace]/[project]/documents/page.tsx` | `$org.$workspace.$project.documents.index.tsx` | done (M6c) | | `[org]/[workspace]/[project]/documents/[fileId]/page.tsx` | `$org.$workspace.$project.documents.$fileId.tsx` | done (M6c) | A module's own pages (`modules//ui/pages.tsx`) need no row: each joins `modules/pages.ts` when its page is a `ProjectPage` of F10 (`load` and `View`), as `notes` is. A module's page above a project is a `WorkspacePage` in `workspacePages` of the same file, served by one route with no Next ancestor: `$org.$workspace.modules.$module.$.tsx`, `///modules//...`. `modules` is reserved from project slugs (`RESERVED_PROJECT_SLUGS`), as a bare `///` would be a project's slug.