> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing

> How apps/web resolves locale, workspace identity, and canonical dashboard URLs

`apps/web` routing has two jobs:

* map browser-friendly URLs onto the App Router file structure
* canonicalize workspace identity before pages and APIs touch the database

For the complete permission model that sits on top of this routing layer, see `platform/architecture/workspaces-permissions`.

The TanStack migration keeps a route ownership manifest for this route tree.
Use `platform/architecture/tanstack-rust-migration` before moving any
`apps/web/src/app` route to `apps/tanstack-web` or `apps/backend`.

## File Structure Versus Browser URLs

The dashboard uses the App Router structure:

```text theme={null}
apps/web/src/app/[locale]/(dashboard)/[wsId]/...
```

Users do not need to think about the internal route groups. What they see in the browser is effectively:

```text theme={null}
/{workspace-segment}/...
```

where `workspace-segment` is one of:

* a workspace UUID
* `personal`
* `internal`

## Proxy Responsibilities

`apps/web/src/proxy.ts` is the routing coordinator at the edge.

It is responsible for:

* locale negotiation
* auth-cookie propagation during redirects
* onboarding and root-path redirect behavior
* canonical workspace slug redirects
* workspace-aware request preprocessing before the App Router handles the page

## Workspace Slug Canonicalization

Two workspace IDs are intentionally rewritten into stable slugs:

* the current user's personal workspace UUID -> `personal`
* `ROOT_WORKSPACE_ID` -> `internal`

That means the proxy may receive a UUID but still redirect the browser onto a slugged URL for consistency.

### Why this matters

* links are shorter and more stable
* users do not need to memorize internal UUIDs for personal/internal workspaces
* page code can still resolve those slugs back to real UUIDs with shared helpers

## Default Workspace Redirect

When an authenticated user hits `/`, the proxy uses `getUserDefaultWorkspace()` to find the best landing workspace.

Resolution order:

1. `user_private_details.default_workspace_id`, if it still exists and the user is a member
2. the user's personal workspace

The redirect target becomes:

* `personal` for a personal workspace
* `internal` for the root workspace
* the UUID for a normal workspace

## Page-Level Workspace Resolution

Server pages should not parse workspace slugs manually.

Use `WorkspaceWrapper`, which calls `getWorkspace()` and gives the page:

* `workspace`
* canonical UUID `wsId`
* `isPersonal`
* `isRoot`

If the lookup fails, the wrapper calls `notFound()`.

This makes page code simpler because the page receives the normalized UUID even when the URL used `personal` or `internal`.

## API-Level Workspace Resolution

Route handlers should use `normalizeWorkspaceId()`.

That helper:

* accepts the raw route segment
* converts `personal` into the authenticated user's personal workspace UUID
* converts `internal` into `ROOT_WORKSPACE_ID`
* preserves request-scoped auth when passed a request-derived Supabase client

The common rule is:

* URLs may use slugs
* database reads and writes should use canonical UUID workspace IDs

## Routing And Authorization Are Separate Layers

Routing tells the app **which** workspace the request targets.

Authorization tells the app **whether** the current actor may view or mutate something inside that workspace.

That separation is deliberate:

* `WorkspaceWrapper` / `getWorkspace()` handle membership and identity resolution for pages
* `normalizeWorkspaceId()` handles route-param normalization for APIs
* `getPermissions()` handles feature-level access control

Do not collapse those responsibilities into one helper or assume a normalized workspace ID automatically means the caller has feature permissions.
