{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/consoles-web",
  "slug": "products/consoles-web",
  "kind": "capabilities",
  "bucket": "product",
  "title": "@leumas/consoles-web — Leumas Consoles at consoles.leumas.tech",
  "name": "@leumas/consoles-web",
  "eyebrow": "Leumas Consoles at consoles.leumas.tech",
  "chip": null,
  "summary": "consoles.leumas.tech — Leumas Consoles on the public web. A free, prerendered reference with a page per console — what the hardware was, which emulator core runs it in a browser and which native...",
  "keywords": [
    "consoles",
    "emulator",
    "retro gaming",
    "emulatorjs",
    "nes emulator",
    "snes emulator",
    "game boy emulator",
    "nintendo ds emulator",
    "playstation emulator",
    "browser emulator",
    "retro console",
    "leumas consoles"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "\n# @leumas/consoles-web — Leumas Consoles at `consoles.leumas.tech`\n\nA public, prerendered console reference — a page per machine, saying what it was, which emulator\ncore runs it and whether that happens in a tab or on your desktop — and behind a Leumas membership,\n**the deck itself**: bring games you already own, play them, and keep your save states.\n\n**Leumas supplies no games.** There is no ROM catalogue here, nothing to download and no links to\none. That is not a caveat bolted onto the pitch; it is the shape of the product.\n\n---\n\n## The two halves, and why they are two\n\n| | Path | Who | What serves it |\n|---|---|---|---|\n| The reference | `/`, `/systems`, `/systems/:id`, `/download`, `/legal` | anyone, no account | prerendered HTML, one file per page |\n| The deck | `/consoles`, `/consoles/:system`, `/consoles/:system/play` | signed in + membership | the SPA fallback; no file exists |\n\n[critical] **`consolesRoutes()` from `@leumas/features` already owns `/consoles`** and wraps the whole\nsubtree in `RequireAuth`, with eighteen hardcoded `/consoles/...` links inside its five screens. The\npublic reference therefore lives at `/systems`, not at `/consoles`. Giving the two the same prefix\nwould either collide with the deck's routes or put `RequireAuth` in front of the pages whose entire\njob is to be readable by a stranger — a crawler bounced to `/login`, on every indexable URL the site\nhas.\n\n**This product renders no console UI of its own.** The carousel, the library, the uploader, the\nplayer and the discovery search are all `@leumas/features`' — the same code Leumas Studio mounts.\n`src/screens/Deck.jsx` is thirty lines and adds nothing but the router and a `lazy()` boundary. A\nchange to the deck belongs in `shared/packages/features/src/surfaces/consoles/`, where Studio gets\nit too.\n\n---\n\n## Where the shelf comes from\n\nNothing on this site names a console by hand. `scripts/systems.mjs` reads `BROWSER_EMULATORS` and\n`NATIVE_EMULATORS` out of `@leumas/a-consoles` at build time and writes the result to\n`dist/consoles-systems.json`; `src/content/pages.js` turns each row into a page, and the browser\nfetches the same file back.\n\nTwo consequences worth stating:\n\n- The site **cannot advertise a console the engine does not have**, and a core added to the engine\n  gets a page on the next build with no edit here.\n- **Every count in the copy is computed**, not typed. \"Six console cores\", \"the eighteen that need a\n  real emulator\" and \"twenty-seven native emulators\" were all written by hand once and one of them\n  was already wrong. A number in prose has no guard on it and no test that can fail: it is simply\n  read by a customer and quietly disbelieved.\n\n`src/content/consoles.js` holds the one thing that genuinely has no other source — a paragraph of\nwhat each machine *was*, and the year. It owns no emulator mapping and must not start to.\n\n---\n\n## Six things that will bite whoever edits this next\n\n1. **`/api/consoles/*` is behind `requireAuth` + `consoles.core`.** A signed-out visitor cannot\n   reach the engine at all, which is why the shelf and the logos are served as *static files from\n   this build* (`consoles-systems.json`, `consoles-art/**`) rather than fetched from the API. Both\n   are public on purpose and carry nothing about anybody's library.\n\n2. **With `spa: true`, any unproxied path answers `index.html` at 200 `text/html`.** Every fetch\n   helper here checks the **content type**, never the status alone — `useSystems` and\n   `Download.jsx` both do, and a caller reading the status sees success with a truthy body. The same\n   trap hits the share card and all 23 console logos: a missing image \"loads\", and only the bytes\n   tell.\n\n3. **Never set `VITE_API_BASE`.** The session cookie is httpOnly and same-origin; an absolute API\n   origin drops it on every authenticated call and the deck renders permanently signed out with\n   nothing in the console to say why.\n\n4. **The prerendered copy and the React screens must say the same words.** They do, because both\n   read `content/pages.js`. A screen that renders different prose means the crawler and the reader\n   are on two different pages and only one is being ranked — a bug with no symptom.\n\n5. **No root-level filename may collide with the apex build.** imperium-server mounts leumas.tech's\n   own `dist` as static *before* this site's dispatcher on every host, so a file called `logo.svg`\n   or `favicon.ico` here is silently shadowed. Everything this build emits at the root is\n   `consoles-`-namespaced, and `check:consoles-public` refuses a build that adds a colliding name.\n\n6. **The port is in two files and nothing reconciles them.** `5050` in `vite.config.js` and in\n   `SERVICES.consoles` in `ops/infra/scripts/dev.mjs`.\n\n---\n\n## Running it\n\n```sh\npnpm dev:consoles                      # http://localhost:5050, API proxied from :3000\npnpm --filter @leumas/consoles-web build\npnpm check:consoles-public             # the build's own SEO + asset guard (runs inside build too)\npnpm smoke:consoles-site               # the real Imperium dispatcher over dist/\npnpm smoke:consoles-isolation # [critical] one member's library is unreachable to another\n```\n\n`pnpm deploy:consoles` ships it; `dist` is excluded from the deploy, so the **server** builds it.\n\n---\n\n## The engine behind it\n\n`@leumas/a-consoles` (`shared/engines/adapters/domain/a-consoles`) — 6 EmulatorJS browser cores, 27\nnative desktop launchers, the library catalogue, uploads, save states, game-art lookup and link-out\ndiscovery, plus 8 agent tools. Mounted at `/api/consoles`.\n\n[critical] **Every library route is owner-scoped.** Uploads, saves, listings and byte-serving all resolve\nthrough `pathsFor(ownerId)`, so a member's games live in `data/consoles/u/<owner>/` and another\nmember cannot list them *or fetch one by knowing its exact filename*. `smoke:consoles-isolation`\nasserts the 404 on the byte route, not merely the absence from the listing — filtering the listings\nis cosmetic while `/files/roms/*` still walks every root.\n",
  "source": {
    "path": "products/leumas-consoles/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 6288,
    "hash": "4417ad75ef48ed03f9f5d0dbc4d74e2839db7952"
  },
  "urls": {
    "html": "/p/products/consoles-web",
    "json": "/docs/products/consoles-web.json",
    "md": "/docs/products/consoles-web.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/features",
      "pkg:@leumas/leviathan",
      "pkg:@leumas/theme",
      "pkg:@leumas/ui"
    ],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  },
  "exports": null
}
