# @leumas/domain-kit — a domain door, once

A DOMAIN DOOR, once — the overview HUD, its view host, the console nav view and the manifest→routes/aside/crumbs/⌘K derivation every product's admin repeats. The 3D room is a separate, optional...


Leumas Studio and Leumas Admin are different products for different people, and they draw a **domain**
the same way on purpose: a door you land on, a deck of what is inside it, view modes over that same
list, a tab strip under each page, one aside, one breadcrumb trail, one ⌘K. An operator moves between
the two consoles all day, so "the same" has to mean the same code, not the same screenshot.

This package is that code. It was Studio-local until every one of its behaviours had to exist in the
control plane too.

## Subpaths

| Import | What you get | Touches `three`? |
|---|---|---|
| `@leumas/domain-kit` | `DomainOverviewHud`, `useSeededFocus`, `DomainViewHost`, `DOMAIN_VIEWS`, `useArrowStep`, `registerDomainRoom`, `getDomainRoom`, `HudDeckFigure`, `useSettledFocus`, `useDomainScope`, `scopeHref`/`unscopeHref`/`scopeFromPath` | **no** |
| `@leumas/domain-kit/routes` | `DomainRoutes`, `DomainLanding`, `DomainHub`, `domainsToRoutes`, `blockingFeature`, `CrumbTitleContext`, `useCrumbTitle` | **no** |
| `@leumas/domain-kit/console-view` | side effect: registers the `console` nav view | no |
| `@leumas/domain-kit/room` | side effect: registers the 3D room | **yes** |
| `@leumas/domain-kit/room/backdrop` | `DomainWorldBackdrop` for a surface drawing its own stage | **yes** |
| `@leumas/domain-kit/domain-hud.css` | `.lms-dhud__*` (the shell imports it itself) | no |

## The room is opt-in, and that is a bundle decision

`DomainOverviewHud` does not import the backdrop. It asks `roomRegistry.js` for a component, and a
product opts in with one line in its entry:

```js
import '@leumas/domain-kit/console-view';
import '@leumas/domain-kit/room';        // ← without this line, no renderer in the build
```

`lazy(() => import('./DomainWorldBackdrop.jsx'))` inside the shell would *look* free — it is behind a
promise — but it is an unconditional **module edge**, so every consumer emits a `three` chunk whether
or not any door passes a `load`. Runtime already degrades correctly, so the defect is invisible in a
browser and only ever shows up as a renderer in someone's `dist/`. A product without the room import
gets identical doors minus the backdrop: they render their `fallbackStage`.

`pnpm check:huds` asserts the shell mentions neither `three` nor `@leumas/cinematic`, and that no
domain console imports `/room` directly.

## A character-select deck: `HudDeckFigure` + `useSettledFocus`

Two doors draw a deck where the item IS the picture (`chrome="none"`) — AI's personas and Hosting's
websites — and they arrived at the same box independently. `HudDeckFigure` is that box: a **poster**
that is always there and always cheap, an optional **live** layer over it, a **plate**, and **pills**
that render only on the centred item.

The poster is never unmounted, only faded. That is the entire failure story: it costs one composited
element, the card never reflows when the expensive layer swaps in, and there is no code path to write
for a snapshot that never rendered, a document that refused to frame, or a dead WebGL context —
because the failure state *is* the resting state.

`useSettledFocus(focusId, ms)` reports the focus id only once the deck has stopped moving. Both decks
need it for the same reason: a held arrow key steps several cards a second, and mounting the expensive
layer per step churns WebGL contexts (browsers cap them near sixteen and drop the **oldest**, so a long
deck starts killing the room drawn behind it — with the symptom appearing nowhere near the cause).

[warning] Not `useSeededFocus`, which picks the *initial* focus. Adjacent name, unrelated job.

Five layout rules live in `domainhud.css` beside the code they constrain, each one a defect that
already shipped: the media is `position: absolute; inset: 0` and **never** `block-size: 100%`; one
`z-index` puts both layers over a door's decorative pseudo-elements; transitions are `transform` and
`opacity` only; no `backdrop-filter` anywhere on a deck over a live canvas; a `filter` may sit on a
still element and never on a moving one.

## A scoped domain: the thing is in the URL

Some domains are *about* one of the operator's things — Hosting is about one website, and every one of
its tabs is. That subject belongs in the address, not in a component's state:

```
/admin/hosting/sites/<siteId>/domains
```

The nav manifest declares those tabs **unscoped** (`/admin/hosting/sites/domains`) and marks the
sub-item `scoped: true`. It has to: four guard scripts and the diagram generator read the manifest in
bare Node, where there is no current site and never can be. So the manifest states the *shape* and
`DomainRoutes` reads the id out of the location, publishing it on `DomainScopeContext`.

**Compare unscoped, emit scoped.** `sectionRows` decides which row is active by matching its hrefs
against the location, so it is given the location with the id removed; the rows it returns get the id
put back. Reversing that is silent — nothing matches, no row is active, and the aside looks like it
lost its place rather than like it has a bug. `scopePath.js` is plain JS so a bare-Node test can pin
both directions (`test/domain-scope.test.js`).

With no scoped sub-item in a domain, `useDomainScope()` returns `null` and every consumer behaves
exactly as it did before scopes existed — which is what keeps the other doors byte-identical.

## The two rules the shell will not break

- **It never fetches.** Every domain keeps its own data source and hands down `items`, `status`,
  `loading`, `onRetry`.
- **It never gates.** `useCanvasCapable()` belongs in the caller's default export — except under
  `downgrade: 'stage'`, where the registered room asks it and swaps only the stage.

One `AdminSurface`, ever. It is rendered by `DomainOverviewHud`, so a caller must not wrap it in
another.

## Adding a domain to a product

1. `<domain>/model/tabs.js` — THE tab list, pure data, no JSX (guards import it in bare Node).
2. `<domain>/hud/overview.js` — the spec: `{ id, base, hudLabel, deckLabel, surface, select, footer,
   downgrade, room? }`. `id` must equal the folder name.
3. `<domain>/hud/<Domain>Populators.js` — `(api) => Promise<inhabitant[]>`, **contracted never to
   reject**. Only if the door draws a room.
4. `<domain>/hud/<Domain>Hud.jsx` — the door: `DomainViewHost` whose `renderConsole` is a
   `DomainOverviewHud` spread from the spec.
5. `<domain>/pages/<Domain>Hub.jsx` — the tabbed surface: `RouteTabs` + a nested `<Routes>` of lazy
   panels. `activeTabId()` is the only source of "which tab is open".
6. One manifest sub-item carrying `tabs`, and one `LANDINGS` entry for the door.

`node ops/tools/domain-scaffold/index.mjs --product admin --domain <id>` writes 1, 2, 4 and 5 from
the manifest.


---
Source: shared/packages/domain-kit/README.md
Canonical: https://docs.leumas.tech/p/packages/domain-kit
