Files
2026-11-14 09:56:45 +00:00

113 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 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.