{
  "schema": "leumas.docs.page/1",
  "id": "how-to:operator-deck",
  "slug": "how-to/operator-deck",
  "kind": "pages",
  "bucket": "how-to",
  "title": "Build-knowledge — the Operator Deck (/dashboard)",
  "name": "the Operator Deck",
  "eyebrow": "build knowledge",
  "chip": null,
  "summary": "/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...",
  "keywords": [
    "operator-deck",
    "viewport-locked",
    "centrepiece",
    "2026-07-27",
    "cosmetics",
    "leumas operator deck",
    "how to operator deck",
    "2026-08-03"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# Build-knowledge — the Operator Deck (`/dashboard`)\n\n`/dashboard` is a viewport-locked HUD start screen, not a settings page: the user's operator card as\nthe centrepiece against a video backdrop, real XP/levels/ranks/badges derived from a ledger,\nequippable cosmetics, onboarding that knows what you have already done, and one search bar that\nanswers or hands off to Leviathan. Built P0–P14 (2026-07-27 → 2026-08-03); P15 (a 3D operator) is\ndeliberately deferred until cosmetics show engagement.\n\n**The contracts live in `ops/todos/dashboard-deck-roadmap.md` and are stated there ONCE (C1–C10).**\nThis file does not restate them — it records what a future agent needs in order to build on the deck\nwithout re-deriving it, and the traps that cost this program the most.\n\n## The shape\n\n```\nproducts/leumas-{web,studio,admin}          thin — each mounts dashboardRoutes({ variant })\n        │\n        ▼\n@leumas/features  surfaces/dashboard/       ONE subtree, byte-identical in every product\n        │  ├─ Deck.jsx          the stage: topbar · launch · operator · activity · dock\n        │  ├─ DeckHome.jsx      hosts the deck through the SHARED nav-views registry\n        │  ├─ OperatorPage.jsx  registers the `operator` Imperium mode (NOT `deck` — taken)\n        │  └─ _hud/             HudStage · HudPane · HudBackdrop · HudPanel · HudMeter\n        │\n        ├── @leumas/avatar      look/slots/rarity contracts (React-free) + OperatorCard\n        └── @leumas/progression /api/progression — the ledger is the only truth\n```\n\n## Where things live\n\n- `shared/packages/avatar` — the `look` blob contract and the card. `./look`, `./slots`, `./rarity`,\n  `./frames`, `./auras` import **nothing**, because the API imports them byte-identically.\n- `shared/engines/progression` — XP ledger, levels, ranks, badges, quests, cosmetic ownership, the\n  merged activity timeline, and `createProgressionRouter`.\n- `shared/engines/adapters/domain/progression` — the pure level maths, exposed as an adapter so it is\n  HTTP + MCP + a chatbot functioncall for free. **This is the deck's knowledge surface**; there is no\n  `adapters/knowledge/deck` bank and there should not be, because a bank is a queryable set of\n  world-reference nodes and the deck has no node set.\n- `shared/packages/features/src/surfaces/_hud` — the stage primitives, deck-agnostic.\n- `ops/tools/deck-assets` — the ffmpeg pipeline that produced the committed backdrops and cosmetics.\n- `shared/services/datacenter/src/library-assets` — the committed media, served at `/library/*`.\n\n## The rules that are not style preferences\n\n**The ledger is the only truth for XP.** `awardXp()` is the sole writer and never throws. Level, rank\nand the next threshold are derived on read from `SUM(user_experience_events.amount)`. Badge unlocks\nand cosmetic grants ARE stored — `user_badge_unlocks` is append-only so a badge is never revoked, and\n`manual`/`purchase` cosmetics require an explicit `user_cosmetics` row. `/recompute` exists because\nthose rows are stored, not derived.\n\n**Never cross the two datastores.** Typed tables go through `getDb()`/drizzle; dynamic collections go\nthrough the injected `connector`. Calling `connector.get().read('userExperienceEvents')` silently runs\n`CREATE TABLE IF NOT EXISTS dyn_userExperienceEvents` and returns `[]` — no error, permanently wrong.\n\n**`feature` is opt-out; `entitlement` is opt-in.** The deck gates every optional part on `feature`\n(absent means on). An `entitlement` gate denies until somebody seeds a policy row, so a freshly\ndownloaded appliance would render a wall of locks out of the box. A disabled part is OMITTED, never\npadlocked: an upsell for something the operator switched off is noise the end user cannot act on.\n\n**Deck code must not reach the eager bundle.** `check-deck-budget.mjs` greps the BUILT entry chunk of\nevery product for `lms-odeck`, `lms-stage__pane`, `backdrop-scrim` and `lms-op__card`. Two leaks got\nin during P14 and neither was visible to source-walking guards: a `import './dashboard.css'` in a\ncomponent with no classes, and a *named re-export* through a lane barrel — re-exporting six innocent\nnames evaluates the lane, and a CSS side-effect import cannot be tree-shaken.\n\n**The stage's CSS has four traps** — a positioned backdrop outranking static panes, `:has()`\nspecificity in the mobile release, cross-sheet breakpoint literals, and `color-mix` alpha caps. All\nfour shipped broken and none was visible to review. They are documented at length in\n`shared/packages/features/src/surfaces/_hud/README.md` and pinned by\n`shared/packages/features/test/deck-stage.test.js`.\n\n## Extending it — the three cheap paths\n\n1. **A new cosmetic.** Add a `cosmetic_definitions` row (slot, rarity, unlock rule, `assetRef`) and,\n   for art-backed slots, a `.webp` under `library-assets/cosmetics/`. `cosmeticUrl()` hardcodes the\n   `.webp` extension, so nothing else works. Frames and auras are procedural code and cost zero bytes.\n2. **A new onboarding quest.** One row in the dynamic `quests` collection — `signal`, `target`, `xp`,\n   `href`, and optionally `feature` so a tenant that switched the relevant system off never sees it.\n   No deploy. `seedQuests` never overwrites an existing row, so a tenant's edits survive.\n3. **A new badge.** One row plus one `registerSignal` function. Do not extend\n   `BADGE_REQUIREMENT_TYPE` — `custom` plus the signal registry means a new badge is never a migration,\n   and an unknown signal simply never unlocks rather than unlocking for everyone.\n\n## Verify\n\n```sh\npnpm check:deck            # bundle purity, asset budget/safety/shippability, eager-chunk markers\nnode ops/infra/scripts/smoke-deck.mjs        # appliance role, asserts BYTES for every asset variant\nnode ops/infra/scripts/smoke-progression.mjs # the 15-level fixture, row by row\npnpm smoke:dashboard\n```\n\n## What will bite you\n\n- **The assets and the generator may not be in git.** `check-deck-assets` used to exit 0 on a clean\n  checkout because its first act was an `existsSync` on a tree nothing had committed — CI was green\n  *because* the tree was invisible. It now fails loudly. If it does, the fix is a `git add`.\n- **A guard that passes because its subject is missing is worse than no guard.** That mistake appeared\n  three separate times in this program: the asset tree above, an entitlement gate that always denied\n  (verifying a negative proves nothing), and a test whose assertion matched a substring of a\n  neighbouring selector. The habit that catches it is to **inject the exact defect and watch the test\n  fail** before believing it.\n- **Measure the page, do not read it.** Every one of the deck's worst defects — an invisible quest\n  dock, an unreachable mobile layout, unclickable dock links, 1.01:1 text — was found with\n  `document.elementFromPoint` and canvas pixel sampling against the running app, and none was visible\n  in the source.\n",
  "source": {
    "path": "shared/services/knowledge/build-knowledge/operator-deck.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 7165,
    "hash": "2c7b5136c5199b7034ed52701652173502afe1d9"
  },
  "urls": {
    "html": "/p/how-to/operator-deck",
    "json": "/docs/how-to/operator-deck.json",
    "md": "/docs/how-to/operator-deck.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
