113 lines
8.9 KiB
Markdown
113 lines
8.9 KiB
Markdown
<!-- SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 -->
|
||
# 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
|
||
`/<org>/members/<workspace>`. 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 `<Outlet />`; the
|
||
`page.tsx` beside it becomes `<path>.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 `/<org>/<workspace>` and the dedicated
|
||
directory is at `/<org>/<workspace>/modules`. The picker offers `?create=true` there when the
|
||
workspace grant allows project creation. A successful submission opens the new project overview
|
||
at `/<org>/<workspace>/<project>`. 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/<id>/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`, `/<org>/<workspace>/modules/<module>/...`. `modules` is reserved from
|
||
project slugs (`RESERVED_PROJECT_SLUGS`), as a bare `/<org>/<workspace>/<module>` would be a project's slug.
|