# Leumas Admin — @leumas/admin

Leumas Admin — the CONTROL PLANE. Leumas staff only: licence issuing, EXE distribution, the customer fleet, platform trust & safety. Never ships to a customer; runs only where LEUMAS_ROLE=control.


**The control plane. Leumas staff only.**
Every Leumas user, contact, licence, install, submission and job — one console.

`admin.leumas.tech` · `LEUMAS_ROLE=control` · dev port **5100** · **never ships to a customer**.
Architecture: [CONTROL-PLANE.md](../../CONTROL-PLANE.md) · [APPLIANCES.md](../../APPLIANCES.md) §6

---

## Core functionality

1. **Overview** — the pulse on one screen: MRR, active installs, DAU, pending reviews, failed
   webhooks, incidents.
2. **Accounts** — every Leumas user, their memberships and Stripe state, their licences. Comp a plan,
   run a trial, replay a failed webhook, revoke a licence.
3. **Fleet** — every Studio install: version spread, last-seen, storage, health. The opt-in studio
   directory and the studio-to-studio relay log.
4. **Marketplace** — the app store review desk: submissions, approve/reject, sign & publish, listings,
   publishers and payouts.
5. **Distribution** — releases: Studio EXE channels and rollout %, iOS release records, the
   minimum-supported-version gate, the update feed.
6. **Analytics** — traffic across the whole ecosystem. Full detail for Leumas's own sites; aggregates
   for customer instances.
7. **Trust & safety** — moderate what people publish **to** Leumas: Social, Marketplace, rooms, and
   the accounts behind them. Contact inbox and job requests.

## What it must never do

| Never | Because |
|---|---|
| Be loaded by a customer's browser or Studio | it's staff infrastructure. Customer-facing pages live on `leumas.tech` and call `api.leumas.tech` |
| Edit a site's content | that's a tenant-admin action → Studio. Deep-link out instead |
| Put `three` on the EAGER graph | the domain rooms are real 3D (owner call, 2026-08-11 — this console gets full Studio parity), but the renderer stays behind `@leumas/domain-kit/room`'s lazy boundary. `pnpm check:deck` and the entry-graph check in §Verify are what keep it there |
| Mount the Leviathan widget | `assistant={false}` in App.jsx. Its page-capability tools drive the page they are on, and the pages here revoke customer licences |
| Grow bespoke components | four in `_shared/`, everything else from `@leumas/ui` |
| Gate on `role === 'control'` | gate on `caps.staffPlane`, or that property quietly dies |

## Security — four independent layers

| Layer | Mechanism | Cost |
|---|---|---|
| **1 Edge** | Cloudflare Access / IP allowlist / mTLS. A non-staff request never reaches Node | infra config |
| **2 Separate staff session** | `LEUMAS_COOKIE_NAME=leumas_staff` **and no `LEUMAS_COOKIE_DOMAIN`** → a host-only cookie under a different name. A stolen customer session is not a staff session | **two env vars** |
| **3 Role + plane** | `isStaff = roles.includes('leumas_staff') && isControlPlane()`. Both halves required | exists |
| **4 Grant lock** | `grantRole` refuses `leumas_staff` off-plane | exists |

```bash
# admin.leumas.tech only
# LEUMAS_COOKIE_DOMAIN deliberately UNSET
LEUMAS_COOKIE_NAME=leumas_staff
LEUMAS_SESSION_TTL_HOURS=8
```

Same `Login` component, same `users` table, a cookie that exists nowhere else. **That is the "separate
admin login", and it costs no code.**

## Design language

- **One UI kit** — `@leumas/ui` (`st-` kit). `pnpm check:ui-kit` ratchets regrowth.
- **One theme** — `@leumas/theme`. Never a literal near-black/near-white — `pnpm check:theme`.
- **Command Deck home** — 7 tiles, each with a live number. The home screen *is* the status page.
- **Cmd/K** — `navToCommands(resolveNav(VENDOR_NAV))`. Already exists, free.
- **One table, one drawer** — every list is `VendorTable`, every row opens the same drawer. Learn it
  once, use it seven times. The single biggest lever on "feels light".
- **One customer, one page** — `CustomerCard` shows account + membership + licences + instances +
  purchases together. Four surfaces, one component.
- **Density over decoration** — numbers, tables, one accent colour for "needs you".

## Composes

```
@leumas/ui  @leumas/theme        the st- kit + tokens
@leumas/features                 RequireStaff · providers · guards · RouteTabs
@leumas/api-client               every HTTP call
@leumas/config-registry          defineNavManifest · resolveNav · navToCommands
@leumas/domain-kit               the domain DOOR: routes, aside, crumbs, ⌘K, the overview HUD
@leumas/entity-kit               the room's body/idle/approach vocabulary
```

Six packages, and the fifth is the one that matters: **this console does not own its own admin
shell.** `@leumas/domain-kit` is the same code Leumas Studio renders, so a staff member moving between
`admin.leumas.tech` and a customer's install is using one console with two sets of contents. If a
control-plane surface needs something the door cannot express, the fix goes in the package and Studio
gets it too.

## The shape of a domain

```
/                     Overview — the pulse deck
/d/fleet              the DOOR: a deck of Fleet's sections over a room of live installs
/d/fleet/overview     the tile grid, kept as a place you can go
/fleet                FleetHub, tab '' → surfaces/fleet/Instances.jsx
/fleet/directory      FleetHub, tab 'directory'
/fleet/relay          FleetHub, tab 'relay'
```

Five files own a domain, and `node ops/tools/domain-scaffold/index.mjs --product admin --domain <id>`
writes four of them from the manifest:

| File | What it is |
|---|---|
| `<domain>/model/tabs.js` | **THE** tab list. Pure data — the manifest, the strip, the deck and the aside all read it |
| `<domain>/hud/overview.js` | the door's spec, pure data. `check:huds` imports it in bare Node |
| `<domain>/hud/<domain>Populators.js` | the room's cast. **Contracted never to reject** |
| `<domain>/hud/<Domain>Hud.jsx` | the door |
| `<domain>/pages/<Domain>Hub.jsx` | the tab strip + the panels, which are the untouched `surfaces/` pages |

Then one manifest entry (`hub(...)`) and one `LANDINGS` line in `AdminRoutes.jsx`.

## File tree

```
products/leumas-admin/
├── package.json [done] [warning] UNTRACKED IN GIT
├── vite.config.js [done] port 5100 · narrow proxy (NO /leviathan /mcp /library)
├── index.html [done]
├── README.md [done] this file
└── src/
 ├── main.jsx [done]
 ├── App.jsx [done] every route behind <RequireStaff> + a standing PlaneBadge
 ├── WrongDeployment.jsx [done] provider-free fallback on the wrong role
 ├── nav.manifest.js [partial] VENDOR_NAV: 4 domains → 7
 ├── AdminRoutes.jsx [todo] derived from the manifest, same pattern as Studio
    ├── _shared/
 │ ├── VendorTable.jsx [todo] list + filter + drawer. THE table
 │ ├── CustomerCard.jsx [todo] account+membership+licences+instances. Used by 4 surfaces
 │ ├── ReviewPanel.jsx [todo] approve/reject + reason. Submissions and Trust
 │ └── useVendor.js [todo] the 3 fetch hooks (list / detail / act)
    └── surfaces/
 ├── Overview.jsx [todo] the pulse deck
        ├── accounts/
 │ ├── Directory.jsx [partial] from Accounts.jsx
 │ ├── Memberships.jsx [todo] comp · trial · replay a failed webhook
 │ └── Licences.jsx [partial] from IssuedLicences.jsx
        ├── fleet/
 │ ├── Instances.jsx [todo] version · last-seen · storage · health
 │ ├── Directory.jsx [todo] opt-in studio directory
 │ └── Relay.jsx [todo] studio<->studio envelope log + rate limits
        ├── marketplace/
 │ ├── Submissions.jsx [todo] the review queue
 │ ├── Listings.jsx [todo] published apps · unpublish · pricing
 │ └── Payouts.jsx [todo] publisher earnings
        ├── distribution/
 │ ├── Releases.jsx [done] Studio EXE builds
 │ └── Channels.jsx [todo] stable/beta · rollout % · min iOS version
 ├── Analytics.jsx [todo] ecosystem · sites · instances
 └── TrustSafety.jsx [done] moderation queue
```

**~20 files, 4 shared components.** That's the whole console.

## Backing service

```
shared/services/control/ [todo] @leumas/control — mounted at /control when caps.staffPlane
└── src/
    ├── router.js                   ALL behind requireStaff
    ├── overview.js                 the pulse aggregation (one query set, cached 30s)
    ├── fleet.js                    instances · opt-in directory · relay log
    ├── analytics.js                ecosystem + rollup reads
    └── moderation.js               thin — delegates to router-kit's social-moderation
```

## Roadmap

### [todo] V0 · Commit it

- Status: todo — **`git add products/leumas-admin`** — every file is `??` today. A `git clean` deletes it

### [todo] V1 · Deploy the plane (roadmap A2)

- Status: todo — `admin.leumas.tech` at `LEUMAS_ROLE=control` + this SPA
- Status: todo — The two cookie env vars above
- Status: todo — Edge restriction: Cloudflare Access / IP allowlist / mTLS

### [todo] V2 · The 7-domain shell (roadmap A4)

- Status: todo — `VENDOR_NAV` → 7 domains; `AdminRoutes.jsx`; the four `_shared/` components
- Status: todo — Reorganize surfaces into `accounts/` `fleet/` `marketplace/` `distribution/`
- Status: todo — `Overview.jsx` with live numbers
- Status: todo — [warning] Route names must not collide with API namespaces: **`/licences` not `/licensing`**,
      **`/market` not `/marketplace`** — the dev proxy forwards those, so a navigation gets the API's 404
- Status: todo — `@leumas/control` service

### [todo] V3 · Fleet (roadmap A3)

- Status: todo — `license_activations` += `tenantId` · `report` · `lastReportAt` · `mailboxCursor` · `listed`
- Status: todo — `Instances.jsx` — version spread, last-seen, storage, per-tenant
- Status: todo — [warning] Label it honestly: the fleet only ever sees **licensed** installs. A dev checkout or an
      install with no `LEUMAS_LICENSE_SERVER` has no central row at all

### [todo] V4 · Marketplace review (roadmap C1, C3)

- Status: todo — `Submissions.jsx` — manifest validity, permissions vs description, bundle hash, origin ownership
- Status: todo — Approve → **sign** (Ed25519, reuse `@leumas/licensing`) → publish → **write the PassNode rule
      from the manifest's declared `pricing`**
- Status: todo — `Listings.jsx`, `Payouts.jsx`

### [todo] V5 · Distribution + Analytics (roadmap D3)

- Status: todo — `Channels.jsx` — stable/beta, rollout %, per-customer channel pinning
- Status: todo — iOS release records + the minimum-supported-version gate
- Status: todo — `Analytics.jsx` — **design for the aggregate tier**; let Leumas's own sites be the richer special
      case, or every chart breaks when a third-party instance appears

### [todo] V6 · Support

- Status: todo — Contact inbox for leumas.tech + job requests
- Status: todo — Escalations from the Trust queue

## Run

```bash
pnpm dev:admin
```

Port 5100. Requires a `control` or `dev` role API — `WrongDeployment.jsx` catches the mistake.

## Verify

```bash
pnpm check:huds        # both consoles' doors: spec, shell, populator, no renderer in a console
pnpm check:nav         # every route in exactly one domain, in all three manifests
pnpm check:admin       # every surface goes through AdminSurface; the CSS/inline ratchets
pnpm --filter @leumas/admin build
```

Then prove the renderer is still lazy — the one thing a browser will not show you:

```bash
grep -c WebGLRenderer products/leumas-admin/dist/assets/index-*.js   # must be 0
ls products/leumas-admin/dist/assets/three.module-*.js               # its own chunk, unreferenced by the entry
```


---
Source: products/leumas-admin/README.md
Canonical: https://docs.leumas.tech/p/products/admin
