Leumas Admin
@leumas/adminLeumas 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.
Leumas Admin — @leumas/admin
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 · APPLIANCES.md §6
Core functionality
- Overview — the pulse on one screen: MRR, active installs, DAU, pending reviews, failed
webhooks, incidents.
- Accounts — every Leumas user, their memberships and Stripe state, their licences. Comp a plan,
run a trial, replay a failed webhook, revoke a licence.
- Fleet — every Studio install: version spread, last-seen, storage, health. The opt-in studio
directory and the studio-to-studio relay log.
- Marketplace — the app store review desk: submissions, approve/reject, sign & publish, listings,
publishers and payouts.
- Distribution — releases: Studio EXE channels and rollout %, iOS release records, the
minimum-supported-version gate, the update feed.
- Analytics — traffic across the whole ecosystem. Full detail for Leumas's own sites; aggregates
for customer instances.
- 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 |
# 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-kitratchets 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 —
CustomerCardshows 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. Agit cleandeletes it
[todo] V1 · Deploy the plane (roadmap A2)
- Status: todo —
admin.leumas.techatLEUMAS_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.jsxwith live numbers - Status: todo — [warning] Route names must not collide with API namespaces:
/licencesnot/licensing,
/market not /marketplace — the dev proxy forwards those, so a navigation gets the API's 404
- Status: todo —
@leumas/controlservice
[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
pnpm dev:admin
Port 5100. Requires a control or dev role API — WrongDeployment.jsx catches the mistake.
Verify
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:
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