ALTITUDE · COMPANIONS PLATES READ: census-bricks.json · census-couplings.json SEE ALSO 4 

FIT VERDICTexploded, then seated: every coupling is a published stud, on core or on the brick below — and the server takes none of core’s

THE ALTITUDE ATLAS — DRAWING SETSHEET 2 / 14

the BRICK ASSEMBLY

ALTITUDE 2 — one baseplate, ten bricks, 72 authored files · an exploded LEGO assembly with every coupling named to the API call that makes it, brick and stud faces drawn opaque so nothing reads through a mass in front of it · REV I 2026-10-15: brick 10, ui-router-mobx, drops onto the far seat of stud C’s front rail, and its cap carries ringed stud K, which brick 3 peers from brick 1’s cap — brick 3 holds no hook of its own · source counted at origin/main @ eb1162da (2026-10-18)

SCALE — plan = whole studs (1 stud per 150 sloc, rounded up to the next standard brick shape) · height = one course per 3 authored files · the baseplate is not massed COUPLING IS THE SUBJECT — accent marks a published extension point; the bricks themselves are drawn plain PARTS — 10 BRICKS, 2 PLATES 1 lit-ui-router x1 · 2×4 · 8 courses 2 navigation-location-plugin x1 · 1×2 · 1 course 3 lit-ui-router-mobx x1 · 1×1 · 2 courses 4 ui-router-server x1 · 2×4 · 5 courses 5 lit-ui-router-effect x1 · 1×2 · 2 courses 6 lit-ui-router-ssr x1 · 2×4 · 5 courses 7 lit-ui-router-signals x1 · 1×2 · 2 courses 8 ui-router-effect x1 · 1×1 · 1 course 9 ui-router-signals x1 · 1×1 · 1 course 10 ui-router-mobx x1 · 1×1 · 1 course SPARE PARTS — STUD A, NOT IN THIS SET @uirouter/visualizer 7.2.1 @uirouter/sticky-states 1.5.1 @uirouter/dsr 1.2.0 @uirouter/rx 1.0.0 all four queue on stud A — sheet 4 STUD SCHEDULE — the published surface each brick seats on Arouter.plugin(factory) the back rail, eight seats — brick 1 registers servicesPlugin on it the red ring: ONE location plugin per router — brick 2 SWAPS core’s out BstateRegistry.decorator(…) brick 1 — decorator(‘views’, litViewsBuilder) is the whole Lit graft CtransitionService.onSuccess() — a front rail, a hook per caller bricks 8, 9 and 10 — one hook per router each; 8 and 10 seat on it DurlService.listen() / .sync() / .rules brick 1 starts the client; brick 4 replays rules on its own plate Eglobals.current / .params bricks 8, 9 and 10 read them into observables — none writes them ON THE BRICKS — seams a brick publishes for the ones above it FUIRouterLitElement.seekRouter(host) brick 1’s ui-router-context event — bricks 3, 5 and 7 seat here Glit-ui-router/context — context-request brick 1’s provideRouter · withRouterSync · getScopedRouter, requestContext · provideContext — brick 6 seats here; 3, 5 and 7 ask here first HcreateServerRouter({ mounts }).resolve(path) brick 4’s verdict table — brick 6 prerenders through it IrouteRef(router) · snapshotRoute() brick 8’s SubscriptionRef — brick 5 forks it per host JRouterSignals.for(router) · watchSelection() brick 9’s signal state — brick 7 binds it to a Lit host KRouterStore.for(router) brick 10’s observable store — brick 3 reacts to it on a Lit host 3 lit-ui-router-mobx 1.2.0 5f · 145 sloc · 1×1 · 2 courses SEATS ON BRICK 1 — F · seekRouter() AND PEERS BRICK 10 — K · RouterStore.for() 7 lit-ui-router-signals 0.1.1 4f · 171 sloc · 1×2 · 2 courses SEATS ON BRICK 1 — F · seekRouter() AND PEERS BRICK 9 — J · RouterSignals.for() 5 lit-ui-router-effect 0.4.1 4f · 281 sloc · 1×2 · 2 courses SEATS ON BRICK 1 — F · seekRouter() AND PEERS BRICK 8 — I · routeRef() 1 lit-ui-router 1.16.5 22f · 2,860 sloc · 2×4 · 8 courses STUDS A · B · D extends UIRouter · carries F · G 2 ui-router-navigation- location-plugin 1.1.2 2f · 208 sloc · 1×2 · 1 course STUD A — the LOCATION SEAT a swap, never an addition 8 ui-router-effect 0.1.2 2f · 21 sloc · 1×1 · 1 course STUDS C · E · carries I — routeRef() 10 ui-router-mobx 0.1.1 2f · 68 sloc · 1×1 · 1 course C RAIL · carries K — RouterStore.for() 9 ui-router-signals 0.1.1 3f · 95 sloc · 1×1 · 1 course STUDS C · E — on the plate itself carries J — RouterSignals.for() 6 lit-ui-router-ssr 0.4.0 13f · 1,127 sloc · 2×4 · 5 courses SEATS ON BRICK 1 — G · context-request AND ON BRICK 4 — H · createServerRouter() the bridge — a drop line onto each assembly 4 ui-router-server 0.4.0 15f · 1,670 sloc · 2×4 · 5 courses STUDS A′ · D′ — on a plate of its own carries H — createServerRouter() @uirouter/core 6.1.2 — THE BASEPLATE 80 files · 5,272 sloc · not massed a plate is ground: every brick peers it, and no brick may replace it A router.plugin(factory) one method, not eight slots: bricks queue on it D urlService .listen() · .sync() · .rules — brick 1 starts and syncs the client router C transitionService .onSuccess({}, …) — one hook per router from each of bricks 8, 9 and 10 E globals .current · .params · the last transition bricks 8, 9 and 10 mirror them, never write B stateRegistry .decorator(‘views’, litViewsBuilder) the one graft that renders Lit ui-router-server takes NO stud on this plate — @uirouter/core is an OPTIONAL peer for it THE SECOND PLATE — the same core, headless peerDependenciesMeta marks core optional; simulate.ts builds a headless UIRouter — servicesPlugin + serverLocationPlugin, the mount’s matcher config replayed — pooled per mount, prewarm builds them ahead; reached only through a lazy import(), so the default ‘matcher’ tier, the one brick 6 drives by default, ships with no plate at all bricks 3, 5, 6 and 7 seat on brick 1 and 6 on brick 4 too, and bricks 5, 7 and 3 peer studs I, J and K on bricks 8, 9 and 10 — every brick-to-brick joint is a seam the lower brick publishes THE FINISHED MODEL — the same parts seated in a landscape of two levels: three steps at the drawing’s own corner, then the whole from two more STEP 1 — brick 1 takes the rail, brick 2 the location seat, bricks 8, 9 and 10 on C and E STEP 2 — bricks 3, 5 and 7 seat on brick 1’s cap, stud F STEP 3 — brick 6 seats on G and on H: the bridge spans the no-DOM line THE WHOLE, FROM THE OPPOSITE CORNER — brick 6 is the bridge; the free rail seats stay ringed THE WHOLE, FROM THE SERVER SIDE — the optional peer in place: the headless plate, dashed, on its shelf under brick 4, and brick 6 seated on H TWO LEVELS OF GROUND the browser — the DOM, one course of ground under the client plate the server shelf — a request runtime with no DOM (node >=22.12; Connect, Vite, fetch and Hono adapters), standing three courses higher, because brick 4’s cap must meet brick 1’s for brick 6 to seat on both the headless plate is dashed on its shelf: an optional peer, reached by lazy import — the ground is real STRUCTURE SCHEDULE — one row per brick · quantized plan · courses · the exact coupling, with its source 0 @uirouter/core 6.1.2 — THE BASEPLATE · 80f · 5,272 sloc · NOT MASSED (a plate is ground) · studs A–E are its published API every brick on this sheet declares it as a peerDependency; the only joints from one brick to another are 3 → 1, 5 → 1, 5 → 8, 6 → 1, 6 → 4, 7 → 1, 7 → 9 and 3 → 10 1 lit-ui-router 1.16.5 · 22f · 2,860 sloc · 2×4 · 8 courses · studs A · B · D · carries F · G class UIRouterLit extends UIRouter — moulded, not snapped; everything lit-specific enters through published seams: this.plugin(servicesPlugin) · this.stateRegistry.decorator(‘views’, litViewsBuilder) · urlService.listen()/sync() · viewService._pluginapi._viewConfigFactory(‘lit’, …) (internal) [src/core.ts] and publishes two of its own: F UIRouterLitElement.seekRouter(host), the ui-router-context event; G lit-ui-router/context, the context-request protocol [src/ui-router.ts, src/context.ts] 2 ui-router-navigation-location-plugin 1.1.2 · 2f · 208 sloc · 1×2 · 1 course · stud A (LOCATION SEAT) navigationLocationPlugin = locationPluginFactory(‘vanilla.navigationLocation’, true, NavigationLocationService, BrowserLocationConfig) a router holds exactly one location plugin, so this brick SWAPS core’s pushStateLocation; it peers core only — it has never heard of Lit [src/index.ts] 3 lit-ui-router-mobx 1.2.0 · 5f · 145 sloc · 1×1 · 2 courses · seats on brick 1 (F) · peers brick 10 (K) RouterStore.for(router) comes from ui-router-mobx ^0.1.0, stud K on brick 10, which registers the one onSuccess per router — this brick holds no hook of its own; src/router-store.ts re-exports it [src/router-reaction-controller.ts, src/router-store.ts] RouterReactionController → requestRouter(host, { subscribe: true }) over stud G, then UIRouterLitElement.seekRouter(host), the ui-router-context event — stud F on brick 1 [src/router-reaction-controller.ts] 4 ui-router-server 0.4.0 · 15f · 1,670 sloc · 2×4 · 5 courses · NO STUD on this plate · carries H peerDependenciesMeta: { ‘@uirouter/core’: { optional: true } } — the ‘matcher’ tier is dependency-free pattern matching the ‘simulate’ tier reaches plates of its own behind a lazy import(): createHeadlessRouter(states, config) — new UIRouter() + plugin(servicesPlugin) + plugin(serverLocationPlugin), the mount’s strict / caseInsensitive / defaultSquashPolicy replayed [src/simulate.ts] a pool of them per mount, each held by one request at a time — prewarm builds them ahead, release() resets one for the next request [src/index.ts, src/router-pool.ts] 5 lit-ui-router-effect 0.4.1 · 4f · 281 sloc · 1×2 · 2 courses · seats on brick 1 (F) · peers brick 8 (I) routeRef(router) and snapshotRoute() come from ui-router-effect ^0.1.0, stud I on brick 8, which registers the one onSuccess per router — this brick holds no hook of its own [src/router-ref-controller.ts, src/index.ts] RouterRefController → getScopedRouter() under a server render, else requestRouter(host, { subscribe: true }) over stud G, then seekRouter(host) on stud F; RefController forks one Effect fiber per host · peers effect ^3.22.0 || ^4.0.2 [src/router-ref-controller.ts, src/ref-controller.ts] 6 lit-ui-router-ssr 0.4.0 · 13f · 1,127 sloc · 2×4 · 5 courses · seats on brick 1 (G) AND on brick 4 (H) — the one part with a drop line onto each plate’s assembly prerender() → createServerRouter({ mounts }).resolve(path) per path · provideRouter(root, router) + withRouterSync(router, …) around @lit-labs/ssr render() [src/prerender.ts] UiViewRenderer → getScopedRouter() + router.viewService.registerUIView(); withServedRender(UiView) extends brick 1’s view, which wakes on requestContext(adoptUiViewContext) [src/ui-view-renderer.ts, src/served-view.ts, src/register.ts] client half: hydrateRoot() → provideContext(container, adoptUiViewContext, adopt) + hydrate(); uiViewSlot() marks each view’s hole · peers @lit-labs/ssr ^4.1.0 and ssr-client ^1.1.7 — a render table, not a router plate [src/client.ts] 7 lit-ui-router-signals 0.1.1 · 4f · 171 sloc · 1×2 · 2 courses · seats on brick 1 (F) · peers brick 9 (J) RouterSignalController → getScopedRouter() under a server render, else requestRouter(host, { subscribe: true }) over stud G, then UIRouterLitElement.seekRouter(host), stud F on brick 1 [src/router-signal-controller.ts] RouterSignals.for(router) + watchSelection() from ui-router-signals ^0.1.0 — it holds no hook of its own; brick 9 registers the one onSuccess per router [src/router-signal-controller.ts, src/signal-controller.ts] 8 ui-router-effect 0.1.2 · 2f · 21 sloc · 1×1 · 1 course · studs C · E on the plate · carries I · peers core and nothing in this repo routeRef(router) → router.transitionService.onSuccess({}, …), one SubscriptionRef per router; snapshotRoute() reads globals.current/.params/.successfulTransitions — no Lit host, no brick under it · peers effect ^3.22.0 || ^4.0.2 [src/route-ref.ts] 9 ui-router-signals 0.1.1 · 3f · 95 sloc · 1×1 · 1 course · studs C · E on the plate · carries J · peers core and nothing in this repo RouterSignals.for(router) → attach(): one transitionService.onSuccess({}, …) hook writes a Signal.State; route · current · params · transition are Signal.Computed over it — no Lit host, no brick under it · peers signal-polyfill ^0.2.0 [src/router-signals.ts] 10 ui-router-mobx 0.1.1 · 2f · 68 sloc · 1×1 · 1 course · the C rail’s far seat · carries K · peers core and nothing in this repo RouterStore.for(router) → attach(): one transitionService.onSuccess({}, update) hook writes MobX observables; update() reads globals.current/.params/.successfulTransitions — no Lit host, no brick under it · peers mobx ^6.0.0 || ^7.0.0 [src/router-store.ts] 11 eslint-plugin-lit-ui-router 1.2.1 · 12f · 1,462 sloc · NOT A BRICK — no stud on any router plate a lint package: it plugs into ESLint/oxlint hosts, never into @uirouter/core — the one published part of eleven that lives on a different table TOTAL — 10 bricks drawn · 72 authored files · 6,646 sloc, standing on one 80-file, 5,272-line baseplate · counted at origin/main @ eb1162da (2026-10-18), same basis as sheets 3 and 4
FIG. 2Every companion drawn here enters through a published stud — on @uirouter/core, or on a brick that publishes one of its own. Drawn exploded, the sheet answers the only question that matters about a plugin architecture: pull any brick off and what breaks? Only what stands on it or peers it — four bricks seat on lit-ui-router, one of them on the server too, three of them peering a brick on the plate — and the plate’s studs stay where they are.

GENERAL NOTES

Why bricks. The mechanism these packages share is a standardised coupling: each companion attaches through a published extension point — on @uirouter/core, or on a brick below it that publishes one — and any brick nothing stands on can be left in the box without disturbing the rest. That is a stud, and a stud is worth drawing. So this is an exploded isometric — the LEGO instruction manual's own idiom — with a numbered part per package, a drop line onto the exact stud it takes, and a parts callout. The exploded view seats nothing, because a seated assembly hides the undersides, and the undersides are the argument; the finished model under it is drawn seated for the other half of the reading — the shape the parts make — in three steps at the drawing's own corner and then whole from two more, on a landscape of two levels: browser ground under the client plate, and a server shelf under the headless one.

The plate is core, not this package. Sheet 4's finding decides it: every limb in the family declares @uirouter/core as a peer. lit-ui-router is therefore brick 1, not the ground — and the drawing is honest about the one place the metaphor strains: class UIRouterLit extends UIRouter is moulded onto the plate, not snapped to it. What the drawing then shows is that the graft is thin anyway. Everything Lit-specific arrives through two published seams — this.plugin(servicesPlugin) and this.stateRegistry.decorator('views', litViewsBuilder) — plus urlService.listen()/sync() to start the thing. Three calls, and one internal seam — viewService._pluginapi._viewConfigFactory('lit', …) — that core does not publish. That is the whole renderer coupling.

One stud is a seat, not a socket. router.plugin() is the back rail: a single method that anything may queue on, which is why the spare-parts box is drawn at all — @uirouter/visualizer, sticky-states, dsr and rx all register through it and none of them knows this repo exists. One seat on that rail is ringed red, because it behaves differently: a router holds exactly one location plugin, so ui-router-navigation-location-plugin is a swap for core's pushStateLocation, never an addition. That package peers @uirouter/core and nothing else — it has never heard of Lit, and would work identically under the React or Angular adapters.

Brick 1 publishes two studs of its own, and four bricks take them. lit-ui-router-mobx and lit-ui-router-effect both ask first over stud G, requestRouter(host, { subscribe: true }), so a placeholder router's upgrade reaches them, and seat through UIRouterLitElement.seekRouter(host) — stud F, a bubbling ui-router-context event, published precisely as the dependency-injection primitive for external reactivity systems — when no provider answers. Brick 3 holds no hook of its own: it takes RouterStore.for() from ui-router-mobx ^0.1.0, stud K on brick 10, and runs a MobX reaction per connected host over the store it returns. Brick 5 holds no hook of its own: it takes routeRef() and snapshotRoute() from ui-router-effect ^0.1.0, stud I on brick 8, and forks one Effect fiber per connected host over the SubscriptionRef it returns. Both observe; neither writes router state. 145 and 281 lines. lit-ui-router-ssr takes stud G alone: lit-ui-router/context, the community context-request protocol spoken without @lit/context. prerender() provides the router on the render root with provideRouter() and scopes each render with withRouterSync(), its UiViewRenderer reads that scope with getScopedRouter(), and on the client the served view it defines — withServedRender(UiView), brick 1's own view class extended — wakes on requestContext(adoptUiViewContext), which hydrateRoot() answers with provideContext().

The agnostic bricks. ui-router-effect (brick 8), ui-router-signals (brick 9) and ui-router-mobx (brick 10) peer @uirouter/core and nothing in this repo — no Lit, no brick 1: each holds one transitionService.onSuccess({}, …) hook per router and reads globals, into an Effect SubscriptionRef through routeRef(), a Signal.State through RouterSignals.for() or MobX observables through RouterStore.for(). So all three drop straight onto the plate, brick 8 onto stud C, brick 9 onto stud E and brick 10 onto the far seat of stud C's front rail — onSuccess() is one method that queues a hook per caller — 21, 95 and 68 lines with no brick under them. Each one-stud cap is ringed, because a Lit binding on brick 1's cap peers it: I, routeRef(), which brick 5 takes; J, RouterSignals.for(router) and watchSelection(), which lit-ui-router-signals (brick 7) takes at ^0.1.0; and K, RouterStore.for(router), which brick 3 takes at ^0.1.0. Bricks 5, 7 and 3 find their router over G and then F on brick 1, and stand eight courses above the plate, so they peer I, J and K without sitting on them: those joints are lettered at both ends, not dropped, and no binding holds a hook of its own.

The fourth brick has no stud on the client plate, and that is the design. ui-router-server declares @uirouter/core as an optional peer (peerDependenciesMeta); its default 'matcher' tier is dependency-free pattern matching and never loads core, and its 'simulate' tier reaches plates of its own behind a lazy import() — a pool of headless routers per mount, each a new UIRouter() with servicesPlugin and the package’s own serverLocationPlugin, the mount’s matcher config replayed onto it, built on first use or up front by prewarm, reset between requests and dropped after a failed transition, because core mutates registrations and no two requests may hold one at the same time. Drawing it over a dashed second plate is the only truthful placement: it is the same mould, a different assembly, and the tie back to the client plate is crossed out. Seated, that plate stands on a shelf of its own: server ground, a request runtime with no DOM (node >=22.12; Connect, Vite, fetch and Hono adapters), drawn three courses above the browser ground because brick 6 must meet brick 4's cap at the height of brick 1's. The plate on it is dashed — an optional peer, reached by lazy import — and the ground is not. It does carry a stud of its own, H — createServerRouter({ mounts }), whose resolve(path) returns a verdict.

Brick 6 is the bridge. lit-ui-router-ssr peers lit-ui-router ^1.16.1 and ui-router-server ^0.2.0 || ^0.3.0 || ^0.4.0, and it is the one part in the family that takes a drop line onto each of this sheet's two assemblies: G on brick 1, H on brick 4. prerender() compiles the mount table with createServerRouter(), asks it for a verdict per path, and turns shell verdicts into pages, redirects into host rules and the otherwise projection into the 404 document. It never reaches the second plate's studs itself — that plate is ui-router-server's to load or not. Its other two peers, @lit-labs/ssr ^4.1.0 and @lit-labs/ssr-client ^1.1.7, are a render table, not a router plate, so they take no stud here.

Massing, quantized. Continuous mass would have made these bricks unbuildable shapes, so the census is rounded to LEGO: plan is one stud per 150 sloc rounded up to the next standard shape (1×1, 1×2, 2×2, 2×3, 2×4), height is one course per three authored files. The result is legible and it is a finding — the three bricks that carry a renderer, a server and the prerender bridge between them are all 2×4s; the seven that plug the router into something are four 1×1 and three 1×2 tiles, 21, 68, 95, 145, 171, 208 and 281 lines. A plug that needed to be a 2×4 would be lit-ui-router's problem to absorb, not a package. The baseplate is deliberately not massed: 80 files and 5,272 lines of core is ground, and ground has no height.

Eleven published packages; ten bricks in this assembly. Every runtime companion is a brick, and every one is drawn. The remaining package, eslint-plugin-lit-ui-router 1.2.1 (12 files, 1,462 sloc), is not a brick at all: it takes no stud on any router plate, because it couples to ESLint and oxlint, not to @uirouter/core. Every number on this sheet is read from www/atlas.lit-ui-router.dev/data/census-bricks.json — counted at origin/main @ eb1162da (2026-10-18).

KEY

a published package — plan ∝ quantized sloc, courses ∝ files
a stud — a published extension point, on core or on a brick
the location seat — exactly one per router, so it swaps
a ringed stud on a plate brick — a brick on the cap peers it, lettered at both ends, not dropped
drop line — the brick falls onto that stud
a second plate — optional peer, reached by lazy import
no coupling — the server takes no stud on this plate
spare part — same stud, not in this set (sheet 4)
ground — the browser, and a server shelf above the no-DOM line
PROJECTTHE ALTITUDE ATLAS
SHEET TITLEthe BRICK ASSEMBLY
SCALETEN PACKAGES
FORMBRICK ASSEMBLY
CLIENTlit-ui-router · simshanith
DATE / REV2026-10-18 · I
DRAWN BYFable (Claude, AI)
SHEET2 OF 14