{"slug":"business/L1_TO_APP_ROUTER_ALIGNMENT","title":"components/l1 → app/ Alignment Spec","section":"business","sectionLabel":"Business","status":"unclassified","type":"document","path":"business/L1_TO_APP_ROUTER_ALIGNMENT.mdx","bytes":11099,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n**Status:** Proposed\n**Scope:** `apps/web/src/components/l1` (240 files, 75,448 LOC) and `apps/web/src/app`\n**Question answered:** which parts of `components/l1` are route concerns that belong in the App Router tree, and which are components that should stay put.\n\n---\n\n## 1. The sorting rule\n\nOne test decides every file:\n\n> **`app/` answers \"what URL am I at?\" — `components/` answers \"what does this look like?\"**\n\nA file belongs in `app/` if it reads or writes location, maps a path to a view, or owns chrome that persists across a URL change. Everything else is a component, regardless of how large or important it is.\n\nToday `components/l1` contains all four App Router primitives — route table, layout, template, loading — implemented as client components. That is the entire misalignment. Nothing is _missing_; it is all in the wrong place, so Next.js cannot see it.\n\n---\n\n## 2. Current state\n\n### 2.1 Two routing worlds coexist\n\n**World A — already Next-native (the reference pattern).**\n`/domains/[domain]/[view]` resolves through `domain-route-manifest.ts` (server-safe) into `domain-route-shell.tsx`, which uses `next/navigation` and drives navigation by URL. This is correct and should be the template for everything else.\n\n**World B — SPA (everything else).**\nThirteen entry points in `app/` return the _identical_ component:\n\n```tsx\n// app/settings/page.tsx, app/admin/page.tsx, app/knowledge/page.tsx,\n// app/governance/page.tsx, app/administration/page.tsx, app/customer-zero/page.tsx,\n// app/app/page.tsx, app/app/[...segments]/page.tsx,\n// app/workspace/[department]/page.tsx, .../work/[module]/page.tsx,\n// app/workbench/[department]/page.tsx, .../work/[module]/page.tsx\n  return <WorkbenchRoute />;\n}\n```\n\n`WorkbenchRoute` → `ClientProviders` (mounts `<BrowserRouter>`) → `AppShell` (`ssr: false`) → `WorkspaceShellNew` → `ContentRouter`. Next.js sees one indistinguishable page at seven distinct URLs. The real routing happens in `content-router.tsx` reading `useLocation()`.\n\n**Consequences:** no nested layouts (one `layout.tsx` exists in the whole app), no `loading.tsx`/`error.tsx`, no per-route metadata, no streaming, and all 75k LOC of `l1` sits behind a single `ssr: false` boundary that must resolve before first paint.\n\n### 2.2 Four registries describe the same route set\n\n`content-router.tsx` · `domain-view-registry.tsx` (774) + its hand-synced twin `domain-route-manifest.ts` · `platform-registry.tsx` · `config/os-shell-registry.ts` · `app/_lib/workbench-routes.ts`\n\nThe manifest file's own header — _\"Keep in sync with domain-view-registry.tsx\"_ — is the cost being paid. The App Router filesystem is the registry; these collapse into it.\n\n### 2.3 The shim points backwards\n\n`shims/next-navigation.ts` maps `next/navigation` → react-router, commented _\"Used during migration from Next.js to Vite + React Router.\"_ The repo has since reversed direction. 37 files import `react-router`; 16 use `next/navigation`. The shim now actively holds the codebase in the world it is trying to leave.\n\n---\n\n## 3. Classification\n\n### 3.1 MOVE to `app/` — route resolvers (delete on completion)\n\nThese are routers wearing component costumes. The App Router replaces them with directories.\n\n| File                                    | LOC | Becomes                                                                                                                 |\n| --------------------------------------- | --- | ----------------------------------------------------------------------------------------------------------------------- |\n| `workspace/content-router.tsx`          | 794 | route segments + `generateStaticParams`                                                                                 |\n| `l1-module-content.tsx`                 | 908 | route segments                                                                                                          |\n| `domains/domain-view-registry.tsx`      | 774 | filesystem routes (retires `domain-route-manifest.ts` with it)                                                          |\n| `workspace/animated-content-router.tsx` | 384 | `template.tsx` — Next remounts templates on navigation, which is precisely the transition semantic this file hand-rolls |\n| `workspace/platform-registry.tsx`       | 118 | folded into segments                                                                                                    |\n\n### 3.2 PROMOTE to `layout.tsx` — persistent chrome\n\nStay as presentational components; get _mounted by_ a layout instead of wrapping their own children. This is what stops sidebars remounting on every navigation.\n\n| File                                | LOC  | Becomes                                                                                                                                     |\n| ----------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------- |\n| `admin/admin-shell.tsx`             | 350  | `app/admin/layout.tsx` — already does `isActive(pathname, href)` with react-router `Link`; a mechanical swap to `next/link` + `usePathname` |\n| `domains/domain-route-shell.tsx`    | 214  | `app/domains/[domain]/layout.tsx`; `page.tsx` renders only the view                                                                         |\n| `workspace/workspace-shell-new.tsx` | 1436 | split: chrome → `app/(workbench)/layout.tsx`, nav data → server module, view state → segments                                               |\n| `navigation/global-nav-bar.tsx`     | 342  | layout-mounted                                                                                                                              |\n| `workspace/loader-phase1.tsx`       | 463  | `loading.tsx`                                                                                                                               |\n\n### 3.3 CONVERT to `page.tsx` — files already named \"page\" that are not pages\n\n`admin/pages/*` — **17 files, 3,154 LOC**, each 1:1 with a URL that already exists in `admin-shell`'s nav:\n\n`actions` · `admin-today` · `audit` · `billing` · `connectors` · `governance` · `iam` · `knowledge-governance` · `observability` · `provisioning` · `registry` · `roles` · `schema` · `tenancy` · `tools` · `usage` · `users`\n\n→ `app/admin/<name>/page.tsx`, each with its own `metadata`, `loading.tsx`, and code-split boundary.\n\n### 3.4 STAY in `components/` — genuine components\n\nAll of `domains/*/views/*` (~45k LOC), `workbench/*` primitives, `views/*`, `twin/`, `knowledge/`, `notifications/`, `command-palette/`, `ooda/`, `surfaces/`. These render; they do not route. They move only if the `l1/` folder name is retired (§6).\n\n---\n\n## 4. Target tree\n\n```\napp/\n├── layout.tsx                        # root: html, providers, fonts\n├── (marketing)/                      # route group — public\n│   ├── layout.tsx                    # ← legacy-pages/marketing/MarketingShell\n│   └── [slug]/page.tsx\n├── (workbench)/                      # route group — authenticated\n│   ├── layout.tsx                    # ← workspace-shell-new chrome\n│   ├── template.tsx                  # ← animated-content-router\n│   ├── loading.tsx                   # ← loader-phase1\n│   ├── error.tsx\n│   ├── domains/[domain]/\n│   │   ├── layout.tsx                # ← domain-route-shell (sidebar persists)\n│   │   ├── page.tsx\n│   │   └── [view]/page.tsx\n│   └── admin/\n│       ├── layout.tsx                # ← admin-shell\n│       └── {users,roles,audit,…}/page.tsx    # ← admin/pages/* ×17\n└── onboarding/\n    ├── layout.tsx                    # ← OnboardingShell chrome\n    └── [step]/page.tsx\n```\n\nThe seven duplicate `WorkbenchRoute` URL families collapse into one `(workbench)` group.\n\n---\n\n## 5. Sequence\n\nOrdered by **evidence per unit of risk** — each phase proves the pattern before the next inherits it.\n\n| #     | Move                                                                      | Risk   | Why first                                                                                                     |\n| ----- | ------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------- |\n| **1** | `admin/pages/*` → `app/admin/*/page.tsx` + `admin-shell` → `layout.tsx`   | Low    | 17 files already 1:1 with URLs; isolated subtree; zero product-surface impact. Proves the pattern end to end. |\n| **2** | `domain-route-shell` → `layout.tsx`; delete `domain-route-manifest.ts`    | Low    | Already `next/navigation`. Removes the hand-synced registry and stops sidebar remount.                        |\n| **3** | Collapse 7 `WorkbenchRoute` families into `(workbench)`                   | Medium | Redirects from retired paths; decide canonical vocabulary first (§6).                                         |\n| **4** | `content-router` + `l1-module-content` → segments                         | High   | Only safe once 1–3 have established the layout contract.                                                      |\n| **5** | Delete `shims/next-navigation.ts`; convert remaining react-router imports | High   | Last. Deleting the shim is the completion signal, not the starting move.                                      |\n\nPhases 1–2 are independently shippable and reversible.\n\n---\n\n## 6. Two decisions this surfaces\n\n**Route vocabulary.** `/app`, `/workspace`, `/workbench`, and `/domains` currently mount the same shell. `CLAUDE.md` says _\"The Workbench is one surface.\"_ Four URL families for one surface is naming drift with a routing cost. Recommend `/workbench/*` canonical, the rest permanent redirects.\n\n**The `l1/` folder name.** `CLAUDE.md` forbids `L1`/`L2`/`L4`/`L7` in user-facing UI. The prohibition is about UI, so the folder is not a violation — but `layers/`, `l1/`, `l2/`, `l4/`, `l7/`, and `_deprecated/l2-removed/` keep retired vocabulary alive in the paths every contributor reads daily. Moving route concerns out shrinks `l1/` naturally; renaming the remainder to `components/workbench/` is a follow-on, not part of this work.\n\n---\n\n## 7. Definition of done\n\n- [ ] `shims/next-navigation.ts` deleted; zero `react-router` imports in `apps/web/src`\n- [ ] `<BrowserRouter>` removed from `client-providers.tsx`\n- [ ] Every persistent chrome surface is a `layout.tsx`; no shell wraps its own children\n- [ ] Route set is derivable from the filesystem alone — no parallel registry\n- [ ] `ssr: false` count reduced from 22 to only genuinely browser-only widgets\n- [ ] Each route family has `loading.tsx`, `error.tsx`, and `metadata`\n"}