Docs
/
build knowledge

the Operator Deck

/dashboard is a viewport-locked HUD start screen, not a settings page: the user's operator card as the centrepiece against a video backdrop, real XP/levels/ranks/badges derived from a ledger...

Build-knowledge — the Operator Deck (/dashboard)

/dashboard is a viewport-locked HUD start screen, not a settings page: the user's operator card as the centrepiece against a video backdrop, real XP/levels/ranks/badges derived from a ledger, equippable cosmetics, onboarding that knows what you have already done, and one search bar that answers or hands off to Leviathan. Built P0–P14 (2026-07-27 → 2026-08-03); P15 (a 3D operator) is deliberately deferred until cosmetics show engagement.

The contracts live in ops/todos/dashboard-deck-roadmap.md and are stated there ONCE (C1–C10). This file does not restate them — it records what a future agent needs in order to build on the deck without re-deriving it, and the traps that cost this program the most.

The shape

products/leumas-{web,studio,admin}          thin — each mounts dashboardRoutes({ variant })
        │
        ▼
@leumas/features  surfaces/dashboard/       ONE subtree, byte-identical in every product
        │  ├─ Deck.jsx          the stage: topbar · launch · operator · activity · dock
        │  ├─ DeckHome.jsx      hosts the deck through the SHARED nav-views registry
        │  ├─ OperatorPage.jsx  registers the `operator` Imperium mode (NOT `deck` — taken)
        │  └─ _hud/             HudStage · HudPane · HudBackdrop · HudPanel · HudMeter
        │
        ├── @leumas/avatar      look/slots/rarity contracts (React-free) + OperatorCard
        └── @leumas/progression /api/progression — the ledger is the only truth

Where things live

  • shared/packages/avatar — the look blob contract and the card. ./look, ./slots, ./rarity,

./frames, ./auras import nothing, because the API imports them byte-identically.

  • shared/engines/progression — XP ledger, levels, ranks, badges, quests, cosmetic ownership, the

merged activity timeline, and createProgressionRouter.

  • shared/engines/adapters/domain/progression — the pure level maths, exposed as an adapter so it is

HTTP + MCP + a chatbot functioncall for free. This is the deck's knowledge surface; there is no adapters/knowledge/deck bank and there should not be, because a bank is a queryable set of world-reference nodes and the deck has no node set.

  • shared/packages/features/src/surfaces/_hud — the stage primitives, deck-agnostic.
  • ops/tools/deck-assets — the ffmpeg pipeline that produced the committed backdrops and cosmetics.
  • shared/services/datacenter/src/library-assets — the committed media, served at /library/*.

The rules that are not style preferences

The ledger is the only truth for XP. awardXp() is the sole writer and never throws. Level, rank and the next threshold are derived on read from SUM(user_experience_events.amount). Badge unlocks and cosmetic grants ARE stored — user_badge_unlocks is append-only so a badge is never revoked, and manual/purchase cosmetics require an explicit user_cosmetics row. /recompute exists because those rows are stored, not derived.

Never cross the two datastores. Typed tables go through getDb()/drizzle; dynamic collections go through the injected connector. Calling connector.get().read('userExperienceEvents') silently runs CREATE TABLE IF NOT EXISTS dyn_userExperienceEvents and returns [] — no error, permanently wrong.

feature is opt-out; entitlement is opt-in. The deck gates every optional part on feature (absent means on). An entitlement gate denies until somebody seeds a policy row, so a freshly downloaded appliance would render a wall of locks out of the box. A disabled part is OMITTED, never padlocked: an upsell for something the operator switched off is noise the end user cannot act on.

Deck code must not reach the eager bundle. check-deck-budget.mjs greps the BUILT entry chunk of every product for lms-odeck, lms-stage__pane, backdrop-scrim and lms-op__card. Two leaks got in during P14 and neither was visible to source-walking guards: a import './dashboard.css' in a component with no classes, and a named re-export through a lane barrel — re-exporting six innocent names evaluates the lane, and a CSS side-effect import cannot be tree-shaken.

The stage's CSS has four traps — a positioned backdrop outranking static panes, :has() specificity in the mobile release, cross-sheet breakpoint literals, and color-mix alpha caps. All four shipped broken and none was visible to review. They are documented at length in shared/packages/features/src/surfaces/_hud/README.md and pinned by shared/packages/features/test/deck-stage.test.js.

Extending it — the three cheap paths

  1. A new cosmetic. Add a cosmetic_definitions row (slot, rarity, unlock rule, assetRef) and,

for art-backed slots, a .webp under library-assets/cosmetics/. cosmeticUrl() hardcodes the .webp extension, so nothing else works. Frames and auras are procedural code and cost zero bytes.

  1. A new onboarding quest. One row in the dynamic quests collection — signal, target, xp,

href, and optionally feature so a tenant that switched the relevant system off never sees it. No deploy. seedQuests never overwrites an existing row, so a tenant's edits survive.

  1. A new badge. One row plus one registerSignal function. Do not extend

BADGE_REQUIREMENT_TYPEcustom plus the signal registry means a new badge is never a migration, and an unknown signal simply never unlocks rather than unlocking for everyone.

Verify

pnpm check:deck            # bundle purity, asset budget/safety/shippability, eager-chunk markers
node ops/infra/scripts/smoke-deck.mjs        # appliance role, asserts BYTES for every asset variant
node ops/infra/scripts/smoke-progression.mjs # the 15-level fixture, row by row
pnpm smoke:dashboard

What will bite you

  • The assets and the generator may not be in git. check-deck-assets used to exit 0 on a clean

checkout because its first act was an existsSync on a tree nothing had committed — CI was green because the tree was invisible. It now fails loudly. If it does, the fix is a git add.

  • A guard that passes because its subject is missing is worse than no guard. That mistake appeared

three separate times in this program: the asset tree above, an entitlement gate that always denied (verifying a negative proves nothing), and a test whose assertion matched a substring of a neighbouring selector. The habit that catches it is to inject the exact defect and watch the test fail before believing it.

  • Measure the page, do not read it. Every one of the deck's worst defects — an invisible quest

dock, an unreachable mobile layout, unclickable dock links, 1.01:1 text — was found with document.elementFromPoint and canvas pixel sampling against the running app, and none was visible in the source.

Source shared/services/knowledge/build-knowledge/operator-deck.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt