DESIGN.md@packages/routing-tanstack/architecture · git:20261017.b2f89db · 2026-10-17 · sha256 46f270f2535fb956

DESIGN.md@packages/routing-tanstack/architecture git:20261017.b2f89dbA

Immutable. This exact content never changes and is served at /api/v1/blob/46f270f2535fb956.

---
type: DESIGN
system: frontx-routing-tanstack
status: draft
---

# Technical Design — Routing TanStack Provider

- [ ] `p3` - **ID**: `cpt-frontx-routing-tanstack-design-engine-provider`

<!-- toc -->

- [1. Architecture Overview](#1-architecture-overview)
  - [1.1 Architectural Vision](#11-architectural-vision)
  - [1.2 Architecture Drivers](#12-architecture-drivers)
  - [1.3 Architecture Layers](#13-architecture-layers)
- [2. Principles & Constraints](#2-principles--constraints)
  - [2.1 Design Principles](#21-design-principles)
  - [2.2 Constraints](#22-constraints)
- [3. Technical Architecture](#3-technical-architecture)
  - [3.1 Domain Model](#31-domain-model)
  - [3.2 Component Model](#32-component-model)
  - [3.3 API Contracts](#33-api-contracts)
  - [3.4 Internal Dependencies](#34-internal-dependencies)
  - [3.5 External Dependencies](#35-external-dependencies)
  - [3.6 Interactions & Sequences](#36-interactions--sequences)
  - [3.7 Database schemas & tables](#37-database-schemas--tables)
- [4. Additional context](#4-additional-context)
- [5. Traceability](#5-traceability)

<!-- /toc -->

## 1. Architecture Overview

### 1.1 Architectural Vision

`@gears-frontx/routing-tanstack` is the default implementation of the engine-provider port the navigation substrate declares (`cpt-frontx-routing-fr-engine-provider-port`, owned by [routing DESIGN](../../routing/architecture/DESIGN.md)). Where the substrate stays agnostic of any concrete router engine, this package is the deliberately concrete side of that boundary: it is the only place in the ecosystem a router engine's package is imported, and the only place the substrate's own `NavigationHistory` contract, together with an extension's own entry, is translated into what a concrete engine expects.

The package's own entry points are `ExtensionRouter` and `navigation()`; there is no other caller. The FrontX router lives in `@gears-frontx/routing`, which exports one ready `router` per runtime (`cpt-frontx-adr-extension-routing-port`). The FrontX router assigns each extension its opaque occupant value at mount (`assignOccupantValue`) and validates it; the extension's runtime supplies its own router a reader of that value (`supplyNavigation`); this package reads the entry address from that ready `router` through `ownEntryAddress()`, and from nowhere else. This package shares the runtime's one copy of `@gears-frontx/routing` with the router the runtime's registry receives, as [routing DESIGN §3.2](../../routing/architecture/DESIGN.md#32-component-model) (FrontX Router) states. An extension's own code authors only its own route tree, using this package's re-exported route-tree builders, renders `ExtensionRouter` with that tree, and reaches its own subroute outside React through `navigation()`; it never constructs a router, adapts history, or reads an entry address.

The package holds three components: the Engine Provider, the Extension Router and the Navigation facade. The Engine Provider sits here rather than inside the navigation substrate because of the package boundary recorded in `cpt-frontx-adr-core-package-boundaries`: the substrate has to stay UI-framework- and engine-agnostic to remain a core member, and the only way to make that a package boundary rather than an intra-package convention is to give the provider its own published artifact. The Extension Router and the Navigation facade are the only callers of the Engine Provider and are the package's public surface for extension code.

The Engine Provider builds a **virtual history** for the concrete engine to navigate: it accepts the entry address that `ExtensionRouter` and `navigation()` read from the ready `router` (`ownEntryAddress()`) — never read from the substrate's history, and never derived by this package — treats that entry's own reserved `route` parameter as the virtual location's pathname (`/` when the entry carries none) and every other parameter as its search, and adapts that virtual history into TanStack Router's own `RouterHistory` contract — deriving the members that contract requires beyond the substrate's five, and translating the substrate's own subscriber notification into the `SubscriberArgs` shape (`location`, `action`) `RouterHistory`'s `subscribe` callback expects — before it constructs the engine's router over that history and the extension's route tree. The extension router builds that history from the entry address the ready `router` returns, builds the engine router over it, and renders it. The navigation facade gives navigate, replace and location, and builds a fresh adapted history per call. Both work in every runtime, over the calling runtime's own private subroute: an extension's own entry, or, in the shell runtime, whose router was never supplied navigation, the shell's subroute (the pathname); in an extension runtime whose router was supplied navigation but has no occupant value, that extension has no access to the router: `ownEntryAddress()` throws there, and both refuse. The call-level shapes are specified by the `engine-provider` FEATURE (`cpt-frontx-algo-routing-engine-provider-extension-router`, `cpt-frontx-algo-routing-engine-provider-navigation`).

Every navigation the constructed router performs writes back only that one entry's own parameters, through exactly one call to the navigation substrate's own URL back-projection helper (`cpt-frontx-algo-routing-route-ownership-signal-url-back-projection`), which itself keeps that write inside the control boundary (`cpt-frontx-routing-principle-control-boundary`) — never a sibling occupant's entry, another domain's entry, or the pathname. In the shell runtime, every navigation writes the pathname alone and leaves the query string untouched. A hash passed to a navigation or to `createHref` is dropped, so the page hash stays outside every runtime's reach. `createHref` composes a full, shareable composed-application URL from an updated virtual location by calling the navigation substrate's own grammar serializer over the current entry list with this occupant's own entry replaced.

Replacing this package with a different engine-provider port implementation is scoped to this package's own `ExtensionRouter` and `navigation()` and to each extension's own route-tree authoring code — rewritten to the replacement's own builders and hooks — and reaches no further: the navigation substrate and the runtime are unaffected.

The reserved parameter `route` is this package's own convention, not a rule the navigation substrate's own URL grammar imposes (routing DESIGN §1.1, Navigation Substrate); the navigation substrate's own engine-provider port names no concrete engine and mandates no particular virtual-location shape, only that a provider accept the shared history, the entry address (or its absence), and an opaque route tree, and produce a constructed router, which this package's `ExtensionRouter` renders, that reads and writes only its own entry's own payload (routing FEATURE `cpt-frontx-feature-routing-navigation-substrate` §1.5). Two conforming providers are therefore free to adopt two different internal-route conventions of their own; this package's worked choice is `route`, applied identically in every extension's runtime (§1.5 of this package's own `engine-provider` FEATURE).

### 1.2 Architecture Drivers

#### Functional Drivers

The package's requirements are owned by its own [PRD](./PRD.md).

| Requirement | Design Response |
|-------------|------------------|
| `cpt-frontx-routing-tanstack-fr-engine-adaptation` | The Engine Provider projects the entry address `ExtensionRouter` and `navigation()` read from the ready `router` into a virtual history (`route` parameter → pathname, remaining parameters → search), adapts it into TanStack Router's `RouterHistory` and `SubscriberArgs` shapes, then constructs the router via `createRouter({ routeTree, history })` (`cpt-frontx-component-routing-engine-provider`, `cpt-frontx-algo-routing-engine-provider-history-adaptation`, `cpt-frontx-algo-routing-engine-provider-router-creation`). |
| `cpt-frontx-routing-tanstack-fr-scoped-navigation-zone` | Every navigation an extension's constructed router performs writes back only the occupant's own entry, through the navigation substrate's own control boundary; the adapter never reads or writes a sibling's entry, another domain's entry, or the pathname. In the shell runtime, every navigation writes the shell's subroute (the pathname) alone and leaves the query string untouched. A hash passed to a navigation is dropped in every runtime (`cpt-frontx-component-routing-engine-provider`). |
| `cpt-frontx-routing-tanstack-fr-extension-router` | `ExtensionRouter({ routeTree })` builds the history from the entry address read through `router.ownEntryAddress()`, builds the engine router and renders it; it works over the calling runtime's own private subroute: an extension's own entry, or, in the shell runtime, whose router was never supplied navigation, the shell's subroute (the pathname), and in an extension runtime whose router was supplied navigation but has no occupant value it refuses, since `ownEntryAddress()` throws there (`cpt-frontx-component-routing-tanstack-extension-router`). |
| `cpt-frontx-routing-tanstack-fr-extension-navigation` | `navigation()` gives `navigate(path)`, `replace(path)` and `location()` over the calling runtime's own private subroute, and builds a fresh adapted history per call; in the shell runtime, whose router was never supplied navigation, it works over the shell's subroute (the pathname) only, and in an extension runtime whose router was supplied navigation but has no occupant value it refuses, since `ownEntryAddress()` throws there (`cpt-frontx-component-routing-tanstack-navigation`). |
| `cpt-frontx-routing-tanstack-fr-public-entry` | The public entry carries `ExtensionRouter`, `navigation`, and the engine's route-tree builders and scoped route hooks, and no router construction; the history-adaptation and router-construction functions and the port types stay internal (§3.3). |

#### NFR Allocation

| NFR ID | NFR Summary | Allocated To | Design Response | Verification Approach |
|--------|-------------|--------------|-----------------|------------------------|
| `cpt-frontx-routing-tanstack-nfr-single-ecosystem-edge` | Exactly one intra-ecosystem import: the navigation substrate | The published package | The manifest declares exactly one intra-ecosystem dependency — `@gears-frontx/routing` — and the import graph carries no other ecosystem edge (§3.4, Dependency Rules). | The boundary guards (`arch:edges`, `arch:deps`) hold the manifest and the import graph to the declared single-edge property. |

This member records its decisions here rather than in a decision record of its own. `cpt-frontx-adr-core-package-boundaries` is the record whose "Scope relative to other published-libraries members" note names this package's own boundary constraints (ROUTING-TANSTACK-1, defined and owned by this DESIGN's §2.2) as sitting outside that ADR's core partition. `cpt-frontx-adr-extension-routing-port` is the record that places the ready router in `@gears-frontx/routing`, where the router assigns each extension its opaque occupant value at mount (`assignOccupantValue`) and validates it, and the extension's runtime supplies its own router a reader of that value (`supplyNavigation`); this package's construction surface is unaffected by how that value is computed, only by the entry address it reads from the ready `router` through `ownEntryAddress()`.

### 1.3 Architecture Layers

- [ ] `p3` - **ID**: `cpt-frontx-routing-tanstack-tech-stack`

```mermaid
graph TD
    MFE["Extension (own route tree)"] -->|renders ExtensionRouter routeTree| ExtRouter["Extension Router"]
    MFE -->|navigation: navigate, replace, location| Nav["Navigation facade"]
    ExtRouter -->|reads ownEntryAddress| Router["Ready router (@gears-frontx/routing)"]
    Nav -->|reads ownEntryAddress| Router
    ExtRouter -->|adapts the history, builds the router, renders it| Provider[Engine Provider]
    Nav -->|adapts the history| Provider
    Provider -->|reads NavigationHistory + grammar codec| Substrate["Navigation Substrate (@gears-frontx/routing)"]
    Provider -->|projects| Virtual["Virtual location (route param -> pathname, rest -> search)"]
    Provider -->|createRouter routeTree, history| Engine["Router engine (TanStack Router)"]
    Provider -->|writes back own entry via URL back-projection helper| Substrate
    Provider -.->|adapts to RouterHistory + SubscriberArgs| Engine
```

| Layer | Responsibility | Technology |
|-------|---------------|------------|
| Engine provider | Projects the entry address read from the ready `router` into a virtual history and adapts it to TanStack Router via `createRouter({ routeTree, history })`; the sole package in the ecosystem permitted an engine import | TypeScript over `@tanstack/react-router` (React) and `@tanstack/router-core` |
| Extension router and navigation | `ExtensionRouter` builds and renders the extension's one router from its route tree; `navigation()` gives the extension's own subroute access outside React; both read the entry address from the ready `router` of `@gears-frontx/routing` | TypeScript over React |

## 2. Principles & Constraints

### 2.1 Design Principles

#### Engine Confined To This Package

- [ ] `p2` - **ID**: `cpt-frontx-routing-tanstack-principle-engine-confined`

No package in this ecosystem other than this one imports a concrete router engine. The navigation substrate reaches the shared history only through its own `NavigationHistory` contract — `location`, `subscribe`, `push`, `replace`, `go` — never through the engine-specific `RouterHistory` contract this package builds on top of it. This is what lets a different engine-provider port implementation replace this package without the substrate or the runtime noticing that a swap occurred.

### 2.2 Constraints

#### ROUTING-TANSTACK-1 — Sole engine import in the ecosystem

- [x] `p2` - **ID**: `cpt-frontx-constraint-routing-tanstack-sole-engine-import`

No other package in this ecosystem imports a concrete router engine or its packages directly — concretely, no package other than this one imports from the `@tanstack/*` scope, apart from the table library `@tanstack/react-table`, which is no part of a router engine and is excepted by name. This package is the sole, deliberate exception, so a mechanical import-graph guard can name that one scope against exactly this one package.

**ADRs**: `cpt-frontx-adr-core-package-boundaries` — cited for the partition context this constraint sits outside of (that record's `More Information` states the core partition's scope excludes an engine-bound member like this one); it does not own this constraint, which this DESIGN defines and owns directly.

## 3. Technical Architecture

### 3.1 Domain Model

| Entity | Definition | Representation |
|--------|------------|-----------------|
| Entry address | The domain key and extension an extension was mounted at — `{domainKey, extension}` — or its absence in the shell runtime, which has no occupant value. The runtime's own occupant value, once the ready `router` of `@gears-frontx/routing` has validated it (`cpt-frontx-adr-extension-routing-port`); this package reads it through `router.ownEntryAddress()`. Defined by the navigation substrate ([routing DESIGN §3.3](../../routing/architecture/DESIGN.md#33-api-contracts), Engine-provider port); consumed here as this package's own internal construction input, never derived by this package itself. | `{domainKey, extension}` or absent, provider input |
| Virtual location | This package's own pathname-and-search pair, constructed from the occupant's own entry: the entry's reserved `route` parameter as pathname (`/` when absent), every other parameter as search. Read by the constructed router at every render, and written back to the occupant's own entry alone on every navigation. In the shell runtime, it is the shell's subroute (the pathname) with an empty search, and a navigation writes the pathname alone. It never carries a hash. This entity, and the `route` convention it is built on, belong to this package alone — the navigation substrate carries no notion of an extension's pathname or search; the page's pathname carries no occupancy and is the shell's subroute (routing DESIGN §1.1, Navigation Substrate). Reading a search string with a malformed percent-escape never throws (`cpt-frontx-algo-routing-engine-provider-history-adaptation`). | `{pathname, search}`, provider-owned, projected from `@gears-frontx/routing`'s `NavigationHistory` and the occupant's own entry |

### 3.2 Component Model

#### Engine Provider

- [ ] `p2` - **ID**: `cpt-frontx-component-routing-engine-provider`

Concrete artifact: `@gears-frontx/routing-tanstack` (package entry).

##### Why this component exists

A router engine renders routes and matches search parameters, and engines evolve on their own cadence. The Engine Provider is the sole adapter that projects the navigation substrate's shared history and an extension's own entry into a virtual location a concrete engine can navigate, so the choice of engine is a per-framework decision rather than a substrate-wide one — and, under the package boundaries `cpt-frontx-adr-core-package-boundaries` records, a package-level decision rather than an in-package one.

##### Responsibility scope

- Accepts the entry address that `ExtensionRouter` and `navigation()` read from the ready `router` through `ownEntryAddress()` — never read by this component from the navigation substrate's history or from anywhere else — or its absence, in the shell runtime, which has no occupant value.
- Projects that entry's own payload into a virtual location: the reserved `route` parameter as pathname (`/` when absent; written back only when the pathname is not `/`), every other parameter as search — the identical projection in every extension's runtime.
- In the shell runtime, projects the shell's subroute (the pathname) as the virtual pathname, with an empty search, and writes every navigation to the pathname alone, leaving the query string, which carries every extension's entry, untouched.
- Drops a hash passed to `push`, `replace` or `createHref`, in every runtime: the page hash stays outside every runtime's reach.
- Adapts that virtual location and the navigation substrate's `NavigationHistory` into the concrete engine's `RouterHistory` input — filling in the members the engine's contract requires beyond the substrate's five, full list and derivation owned by `cpt-frontx-algo-routing-engine-provider-history-adaptation` (that algorithm also records `block`'s deliberately degraded semantics, §4) — and translating `NavigationHistory`'s own subscriber notification into the `SubscriberArgs` shape (`location`, `action`) `RouterHistory`'s `subscribe` callback expects — then calls `createRouter({ routeTree, history })`.
- Writes every navigation the constructed router performs back to the occupant's own entry alone, through exactly one call to the navigation substrate's own URL back-projection helper (`cpt-frontx-algo-routing-route-ownership-signal-url-back-projection`), which keeps that write inside the control boundary — never a sibling occupant's entry, another domain's entry, or the pathname.
- Composes a full composed-application URL from an updated virtual location for `createHref`, by calling the navigation substrate's own grammar serializer over the current entry list with this occupant's own entry replaced.
- Owns the concrete engine dependency: `@tanstack/react-router`, together with `@tanstack/router-core` for the router types this package's own emitted declarations name.
- Re-exports the engine's own route-tree builders and scoped route hooks, so an extension's own code authors its route tree and redirects without an engine import of its own; it does not re-export the engine's router construction.
- Unsubscribes the constructed router from the shared history when the extension that owns it unmounts, so a torn-down router's callback stops receiving the fan-out.

##### Responsibility boundaries

- Is the only package in this ecosystem permitted to import a concrete router engine (`cpt-frontx-constraint-routing-tanstack-sole-engine-import`).
- Its replacement is scoped to this package's own `ExtensionRouter` and `navigation()` and to each extension's own route-tree authoring code — rewritten to the replacement's own builders and hooks; it does not reach the navigation substrate, the URL grammar, or the runtime (`cpt-frontx-routing-fr-engine-provider-port`).
- Never obtains, derives, or validates an entry address itself; it only accepts the one `ExtensionRouter` or `navigation()` passes it, or none. Validating the runtime's occupant value is the ready `router`'s own responsibility (`cpt-frontx-adr-extension-routing-port`), never this component's.
- Exposes no public surface; it is reached only through `ExtensionRouter` and `navigation()`. Rendered code uses this package's re-exported scoped route hooks.
- Does not resolve which route owner a URL belongs to; that is the navigation substrate's route ownership signal (`cpt-frontx-feature-routing-route-ownership-signal`, owned by [routing DESIGN](../../routing/architecture/DESIGN.md)). This component only runs once `ExtensionRouter` or `navigation()` has called it.
- In an extension's runtime, does not read or write any entry other than the one it was addressed at — never a sibling occupant's entry under the same domain key, and never an entry under another domain key — nor the shell's subroute (the pathname), which carries no occupancy (routing DESIGN §1.1, Control boundary). In the shell runtime, it reads and writes the pathname alone and no entry.

##### Related components (by ID)

- `cpt-frontx-component-routing-navigation-substrate` — the core package's own component ([routing DESIGN §3.2](../../routing/architecture/DESIGN.md#32-component-model)); supplies the `NavigationHistory` instance and the URL grammar codec this component projects into a virtual location and adapts into `RouterHistory`. The entry address itself comes from the ready `router` through `ownEntryAddress()`, never from the substrate's history.

#### Extension Router

- [ ] `p2` - **ID**: `cpt-frontx-component-routing-tanstack-extension-router`

Concrete artifact: `ExtensionRouter` (package entry).

##### Why this component exists

FrontX builds and renders each runtime's one router from the route tree that runtime provides, so authors write nothing beyond a route tree.

##### Responsibility scope

- Adapts the realm-shared history over the entry address `router.ownEntryAddress()` returns, builds the engine router from the route tree and that history, and renders it. It takes `{ routeTree }`.
- Works over the calling runtime's own private subroute: an extension's own entry, or, in the shell runtime, which has no occupant value, the shell's subroute (the pathname).

##### Responsibility boundaries

- Reads the entry address only from the ready `router` of `@gears-frontx/routing`; never derives or validates one.

##### Related components (by ID)

- `cpt-frontx-component-routing-engine-provider` — the component this one calls to adapt the history, build the router and render it.
- `cpt-frontx-component-routing-router` — the ready router whose `ownEntryAddress()` read gives this component the entry address.

#### Navigation

- [ ] `p2` - **ID**: `cpt-frontx-component-routing-tanstack-navigation`

Concrete artifact: `navigation()` (package entry).

##### Why this component exists

Code outside React in any runtime, such as an action handler, needs read and write access to its own private subroute and to nothing else.

##### Responsibility scope

- Gives `navigate(path)`, `replace(path)` and `location()` over the calling runtime's own private subroute: an extension's own entry, or, in the shell runtime, the shell's subroute (the pathname).
- Builds a fresh adapted history per call.

##### Responsibility boundaries

- Reads the entry address only from the ready `router` of `@gears-frontx/routing`; changes only the calling runtime's own private subroute.

##### Related components (by ID)

- `cpt-frontx-component-routing-engine-provider` — the component this one calls to adapt the history.
- `cpt-frontx-component-routing-router` — the ready router whose `ownEntryAddress()` read gives this component the entry address.

### 3.3 API Contracts

- [ ] `p2` - **ID**: `cpt-frontx-routing-tanstack-interface-package-entry`

- **Contracts**: `ExtensionRouter`; `navigation`; and the engine's own route-tree builders and scoped route hooks re-exported unchanged (`createRootRoute`, `createRootRouteWithContext`, `createRoute`, `useNavigate`, `useParams`, `useSearch`, `useRouterState`, `Link`, `Outlet`, `redirect`, `notFound`).
- **Technology**: TypeScript library API, single entry point.
- **Location**: `src/index.ts` — the entry point, carrying this package's default TanStack Router adapter's contract (the runtime's one router, the own-subroute navigation, and route-tree authoring).

The package's public surface is the one a runtime's own code calls: it renders `ExtensionRouter` with its own route tree, reaches its own private subroute outside React through `navigation()`, and authors routes with the engine's own builders, scoped route hooks and redirect. Everything the construction needs internally to project and adapt a virtual location stays out of the public surface; it is a detail of how `ExtensionRouter` and `navigation()` are satisfied, not a surface any caller assembles by hand.

| Public surface | Purpose |
|----------------|---------|
| `ExtensionRouter` | Builds and renders the extension's one router from the route tree the extension provides, over a history built from the entry address the ready `router` returns. It works over the calling runtime's own private subroute: an extension's own entry, or, in the shell runtime, the shell's subroute (the pathname). |
| `navigation` | The calling runtime's own private subroute access outside React: navigate, replace and location. It builds a fresh adapted history per call, and changes only that subroute: an extension's own entry, or, in the shell runtime, the shell's subroute (the pathname). |
| `createRootRoute`, `createRootRouteWithContext`, `createRoute`, `useNavigate`, `useParams`, `useSearch`, `useRouterState`, `Link`, `Outlet`, `redirect`, `notFound` | The concrete engine's own surface — route-tree construction, state and navigation hooks, declarative navigation and nested-route rendering, and the redirect/not-found route-resolution helpers — re-exported unchanged so an extension's own code, authoring its own route tree, never needs a direct `@tanstack/react-router` import of its own (`cpt-frontx-constraint-routing-tanstack-sole-engine-import`). |
| `createHref`, a member of the adapted `RouterHistory` rather than a separate export | Composes a full, shareable composed-application URL from an updated virtual location, by calling the navigation substrate's own grammar serializer over the current entry list with this occupant's own entry replaced — never by concatenating path fragments of its own. In the shell runtime, the result is the current URL with the pathname replaced. A hash given with the target is dropped; the returned href carries no hash. |
| `RouterHistory` contract | The engine's own history contract, reached through the `RouterHistory` type `@tanstack/react-router` re-exposes. This package builds it from a virtual location projected from the navigation substrate's `NavigationHistory` and the entry address read from the ready `router`, adding the members the engine's contract requires beyond the substrate's five (full list owned by `cpt-frontx-algo-routing-engine-provider-history-adaptation`), and translating the substrate's subscriber notification into the `SubscriberArgs` shape (`location`, `action`) this contract's `subscribe` callback expects. `length`/`canGoBack` are derived on every access from the navigation substrate's own `Location.position` (`position + 1`, `position > 0`) — never a counter this adapter keeps of its own and never a `window.history.length` fallback (`cpt-frontx-algo-routing-navigation-substrate-position-tracking`). |

**Internal, not published.** History adaptation, router construction, the provider component, the engine-provider port types and the entry-address type are internal to this package: while public, they would let code build a history or a router over another extension's part of the URL. The virtual-location projection (reading and writing the reserved `route` parameter against every other parameter), and the composed and shell history sources it selects between, are internal as well. History adaptation selects the composed projection when an entry address is supplied, and the shell projection (over the pathname alone) in the shell runtime, whose router was never supplied navigation. The provider component's mount boundary is where the adapted history's registration against the shared `NavigationHistory` is established and released, so the subscription's lifetime matches the mount's rather than the history object's. Their names, input forms, option ordering and internal types are specified in `cpt-frontx-algo-routing-engine-provider-router-creation`.

### 3.4 Internal Dependencies

Exactly one: `@gears-frontx/routing`, the navigation substrate whose `NavigationHistory` contract and entry address this package projects into a virtual history and adapts, whose engine-provider port this package implements, and whose ready `router` this package reads (`ownEntryAddress()`) — the single-edge property this member claims under the layer's membership rules (root DESIGN §1.3), held to the cross-member dependency policy of root DESIGN §3.4. This package shares the runtime's one copy of `@gears-frontx/routing` with the router the runtime's registry receives (routing DESIGN §3.2, FrontX Router), so the entry address this package reads is the one the runtime's own router holds.

**Dependency Rules** (per project conventions):
- No circular dependencies at the design level: the navigation substrate never depends on this package; the dependency runs one way only.
- This package depends only on `@gears-frontx/routing` and reads its ready `router`; the runtime depends on neither routing package.
- This package shares the runtime's one copy of `@gears-frontx/routing` with the router the runtime's registry receives (routing DESIGN §3.2, FrontX Router).
- No import of any other ecosystem package beyond the navigation substrate (`cpt-frontx-routing-tanstack-nfr-single-ecosystem-edge`).

### 3.5 External Dependencies

#### Router engine

| Dependency Module | Interface Used | Purpose |
|-------------------|-----------------|---------|
| `@tanstack/react-router` | `createRouter`, `createRootRoute`, `createRootRouteWithContext`, `createRoute`, `RouterProvider`, `useNavigate`, `useParams`, `useSearch`, `useRouterState`, `Link`, `Outlet`, `redirect`, `notFound` | The concrete router engine's React binding — the exact framework-specific package this provider adapts a virtual history for. `createRouter` and `RouterProvider` are used inside this package only; the route-tree builders and scoped route hooks are re-exported. This package's UI framework is React; a provider bound to a different UI framework is a different, non-default provider this package does not ship. |
| `@tanstack/router-core` | The engine's own router type surface (`RouterContextOptions` and the router/route types the React binding is built over) | Declared as this package's own dependency because its emitted type declarations name that surface directly (`src/router-creation.tsx`); a consumer type-checking against them resolves it here rather than reaching into the React binding's own dependency. |

**Engine contract ownership**: the `RouterHistory` member list and the `SubscriberArgs` callback shape are the engine's own contract, not this package's. This package adapts its own virtual location, itself projected from `NavigationHistory` and the entry address read from the ready `router`, to whatever that contract requires at the time it is built, and a change to it is a change to this package alone — the navigation substrate, its `NavigationHistory` contract, and every consumer of it are unaffected, which is the isolation the engine-provider port exists to give.

**Dependency Rules** (per project conventions):
- `@tanstack/react-router` and `@tanstack/router-core` are dependencies of this package alone; no other ecosystem package imports either.
- This package is replaceable: a different engine-provider port implementation is not required to depend on it at all.

### 3.6 Interactions & Sequences

#### Engine Provider Projects A Virtual Location And Constructs A Router

- [ ] `p3` - **ID**: `cpt-frontx-routing-tanstack-seq-adapt-and-construct`

**Use cases**: `cpt-frontx-routing-tanstack-usecase-swap-router-engine`

**Actors**: `cpt-frontx-routing-tanstack-actor-microfrontend-developer`

```mermaid
sequenceDiagram
    participant MFE as Extension (own route tree)
    participant Ext as Extension Router
    participant Router as Ready router (@gears-frontx/routing)
    participant Provider as Engine Provider
    participant Substrate as Navigation Substrate (@gears-frontx/routing)
    participant Engine as TanStack Router
    MFE->>Ext: render ExtensionRouter({ routeTree })
    Ext->>Router: router.ownEntryAddress()
    Router-->>Ext: this occupant's entry address
    Ext->>Provider: adapt the realm-shared history over the entry address
    Provider->>Substrate: read NavigationHistory and this occupant's own entry
    Provider->>Provider: project entry into virtual location (route param -> pathname, rest -> search)
    Provider->>Provider: adapt to RouterHistory + SubscriberArgs (cpt-frontx-algo-routing-engine-provider-history-adaptation)
    Provider-->>Ext: adapted history
    Ext->>Provider: build the router from the route tree and the adapted history
    Provider->>Engine: createRouter({ routeTree, history })
    Engine-->>Provider: constructed router
    Provider-->>Ext: constructed router
    Ext->>Provider: render the router
    Note over Provider,Substrate: A navigation writes back only this occupant's own entry, through the substrate's control boundary.
    Note over Provider,Substrate: On unmount, Provider unsubscribes from Substrate's fan-out (cpt-frontx-algo-routing-engine-provider-teardown).
```

**Description**: The path every construction of this provider takes: the extension renders `ExtensionRouter` with its own route tree, which reads this occupant's entry address from the ready `router` of `@gears-frontx/routing` through `ownEntryAddress()`, adapts the navigation substrate's shared history and that entry into the concrete engine's own history contract through the Engine Provider, constructs the engine's router, and renders it in the extension's own component tree. `navigation()` follows the same first steps, with a fresh adapted history per call. The core deep-link cold-mount sequence this participates in — a URL resolving to this extension and the runtime mounting it — is owned by the navigation substrate's own DESIGN ([routing DESIGN §3.6](../../routing/architecture/DESIGN.md#36-interactions--sequences), `cpt-frontx-routing-seq-deep-link-cold-mount`), which names this package's mounted router as the participant that reads the already-current location at start; this sequence documents only what happens once that construction reaches this package's own entry points.

### 3.7 Database schemas & tables

Not applicable. The package holds no database and no durable persistence.

## 4. Additional context

The package's central design tension is the same one the navigation substrate's own DESIGN names as its central tension, expressed as a package boundary rather than an in-package one: a router engine is a real, concrete dependency that the extension's router needs ready to use, and the navigation substrate that dependency sits behind must not carry it. Keeping this package separate from the navigation substrate — recorded in `cpt-frontx-adr-core-package-boundaries` — is what turns the engine-behind-port discipline the substrate's own DESIGN describes into a package-level guarantee: no import-graph guard confined to one package's internals is needed, because the concrete engine simply cannot appear anywhere the substrate's own source lives.

Recorded failure modes:

- **A route guard built on this package's `block` expects it to stop every navigation.** It cannot: the substrate's fan-out notifies only after a navigation has already committed, so the adapted `block` can stop a navigation issued through the same constructed router's own `RouterHistory` object, but not a back/forward step, a `go` call from another unit, or a navigation another unit performs directly through the shared `NavigationHistory` — this is a recognized, degraded adaptation, not a routinely derived member (`cpt-frontx-algo-routing-engine-provider-history-adaptation`).
- **A consumer expects a navigation to carry its own per-entry state.** It cannot: the navigation substrate excludes caller-owned per-entry state from its contract — `push` and `replace` take a path alone, its `Location` shape has no field for one, and it owns the browser's own per-entry state exclusively for bookkeeping it rewrites on every write (routing FEATURE `cpt-frontx-feature-routing-navigation-substrate` §1.5, "Entry-carried state — not part of this contract"). This adapter accepts the state argument the engine's history contract passes and drops it, rather than holding a provider-local map the first reload would empty and the first back step would bypass. What a consumer sees: `useLocation().state` and `navigate({ state })` never carry its own value, and route masking, built on that same state, does not work — a recognized, degraded adaptation alongside `block`'s (`cpt-frontx-algo-routing-engine-provider-history-adaptation`). Search carries what state would have, and a consumer that genuinely needs entry-carried state needs a different port implementation over an engine whose own contract provides it.
- **Unmounting an extension without unsubscribing its router.** If this package does not unsubscribe the constructed router from the shared history at teardown, the unmounted router's callback keeps receiving the fan-out for a component tree that no longer exists. This package's own teardown responsibility (§3.2; `cpt-frontx-algo-routing-engine-provider-teardown`) is what prevents this. The mirror image — an adapted history constructed and then discarded without ever being mounted, which a development-mode double-render produces routinely — is prevented by the same pairing from the other side: the registration is made by the mount boundary of the component that renders the router, never by construction, so there is nothing to leak from an instance no mount ever claimed. The cost is that an adapted history observes nothing until that component mounts it.
- **Navigation from outside the mounted router's own tree.** Code that runs outside the rendered route tree — an action handler, for instance — has no router to call. That code reaches navigation through this package's `navigation()`, which builds a fresh adapted history per call over the calling runtime's own private subroute and can change only that subroute: an extension's own entry, or, in the shell runtime, the shell's subroute (the pathname). Rendered code uses this package's re-exported scoped route hooks instead.
- **A router built on top of this adapter writes outside its own entry.** The navigation substrate's own control boundary confines every write an extension's router performs to the occupant's own entry — never a sibling's, never another domain's, never the pathname (routing DESIGN §1.1, Control boundary; routing FEATURE `cpt-frontx-feature-routing-navigation-substrate` §1.5, port shape — "no bare top-level key"). A change outside the calling runtime's own private subroute happens only through an action — never through this router's own routing table or `navigation()`.
- **A write the shared history refuses.** The substrate bounds its own fan-out drain and raises once a subscriber that navigates from every round it receives has driven that drain past the bound (`cpt-frontx-algo-routing-navigation-substrate-fanout-dispatch`), and that throw surfaces out of the `push`/`replace` this adapter issues. `RouterHistory`'s own `push`/`replace` return nothing and the engine calls them fire-and-forget, so there is no caller to propagate to: the adapter reports the failure through its own error-reporting channel (`console.error` when none is given) and leaves the virtual location where it was, rather than letting the throw cross its own asynchronous navigation path and become an unobserved promise rejection.
- **`createHref` builds a link by string concatenation instead of the grammar serializer.** A hand-assembled link risks corrupting a neighboring entry's own delimiters or omitting the pathname; `createHref` always composes its result by calling the navigation substrate's own serializer over the full current entry list with this occupant's own entry replaced, never by concatenating fragments of its own.
- **A navigation to a `route=` path the extension's own route tree does not declare.** It resolves to this package's own `notFound` route — the occupant's own router's own not-found, never whichever level's own consumer would show as a fallback for an entry the route ownership signal reports unresolved (the host's, at the outermost domain, or an enclosing extension's, at any level nested deeper); the two are distinct levels, and this package never decides or participates in the latter.

Domains this design does not address:

- **Security** — Not applicable because this package holds no credential and authenticates or authorizes no one. The one access rule it carries — that each runtime reads and changes only its own private subroute — is stated in §3.2 (Engine Provider, Responsibility boundaries) and above ("A router built on top of this adapter writes outside its own entry").
- **Performance** — Not applicable because this package sets no latency or throughput target of its own.
- **Reliability** — Addressed above: a write the shared history refuses is reported and leaves the virtual location where it was ("A write the shared history refuses"), and an unmounted router stops receiving the fan-out ("Unmounting an extension without unsubscribing its router").
- **Operations** — Not applicable because this package is a library with no deployed service.
- **Compliance** — Not applicable because this package handles no regulated or personal data.
- **Usability** — Not applicable because this package renders no user interface of its own; `ExtensionRouter` renders the route tree the runtime provides.
- **Integration and evolution** — Addressed in §3.3 and §3.5: the public entry, the engine-provider port this package implements, and the engine's own history contract, a change to which is a change to this package alone (§3.5, Engine contract ownership).
- **Maintainability** — Not applicable because this package sets no maintainability target of its own; ROUTING-TANSTACK-1 (§2.2) and the single-ecosystem-edge NFR (§1.2) bound its dependencies.
- **Testing** — Not applicable because this design sets no test strategy of its own; the `engine-provider` FEATURE's acceptance criteria state what an implementation is tested against.
- **Business** — Not applicable because this is an internal library design; the outcomes it serves are the PRD's goals (PRD §1.3).
- **Capacity and cost** — Not applicable because this package runs inside the page with no service, storage or infrastructure of its own to size or pay for.

## 5. Traceability

- **Features**: [features/engine-provider/FEATURE.md](./features/engine-provider/FEATURE.md) (`cpt-frontx-feature-routing-engine-provider`)
- **Root chain**: [PRD](../../../architecture/PRD.md), [DESIGN](../../../architecture/DESIGN.md), [DECOMPOSITION](../../../architecture/DECOMPOSITION.md)
- **Core chain**: [routing PRD](../../routing/architecture/PRD.md), [routing DESIGN](../../routing/architecture/DESIGN.md) — the navigation substrate whose engine-provider port this package implements, and whose URL grammar (routing DESIGN §1.1; `cpt-frontx-routing-adr-domain-occupancy-addressing-granularity`) this package's virtual location is projected from and its `createHref` serializes back into.
- `cpt-frontx-adr-extension-routing-port` — the record placing the ready router in `@gears-frontx/routing`, from which this package's `ExtensionRouter` and `navigation()` read the entry address.

This package's requirements are owned by its own [PRD](./PRD.md), per the 3-layer model: each member explains its own requirements, and the root PRD describes the layers and the requirements binding every member equally.