feat(demo): list the demo's sign-ins on /beta and link into it per game
Some checks failed
PR checks / checks (pull_request) Failing after 2m16s

The public demo (demo.runicgateway.com) is live, so the site's demo slot
can be switched on. Two decisions of record, PLAN.md §15:

- D82: /beta reads the demo's GET /api/v1/public/settings demo.logins on
  the server, caches it for a minute, and lists the published sign-ins
  with the next wipe. If the demo does not answer, /beta links to its
  sign-in page instead. Gate two reads as in place.
- D83: a deep link into a game module's pages carries data-demo-game, and
  applyBrand.mjs fills it only for games in the new mounted demoGames
  list. The Rust features gain links into /rust and /rust/servers/demo.
  UO's stay hidden on a Rust demo.

The public-demo not-built entry is removed, because its resolvedBy was met.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-28 15:32:34 -05:00
parent 8701126d64
commit 769d213c80
18 changed files with 650 additions and 63 deletions

View File

@@ -32,9 +32,9 @@
* 2. SOMEWHERE TO POINT IT. `ConnectScreen.kt` on `Android-app` `main` is blunt about
* this — "nothing else in the app runs until a valid Runic Gateway site is entered and
* validated". An installed app with no deployment behind it is a text field. D27 makes
* the public demo (§15) the tester target rather than naming a private shard, which
* means the beta opens when the demo VM does. `notBuilt.mjs` already carries that
* absence; this page renders the demo link from `demoUrl` when there is one.
* the public demo (§15) the tester target rather than naming a private shard. The demo
* went up on 2026-10-27; with `demoUrl` mounted, `/beta` marks this gate in place and
* lists the demo's sign-ins as the demo publishes them (D82, `lib/demoLogins.mjs`).
*
* Neither gate is a reason not to collect addresses now — the list is what makes the first
* batch possible on day one — but a page that hid them would be advertising a beta that
@@ -180,8 +180,7 @@ export const requirements = [
body:
'The app ships pointed at nothing. Its first screen asks for the address of a site ' +
'running this platform and validates it before anything else in the app will run — ' +
'so a tester needs either their own deployment or the public demo, which is the ' +
'second of the two things this beta is waiting on.',
'so a tester needs either their own deployment or the public demo.',
},
];

View File

@@ -51,7 +51,7 @@
* ---------------------------------------------------------------------------------------
* `demoPath` — DEEP LINKS THAT ONLY EXIST WHEN A DEMO DOES
* ---------------------------------------------------------------------------------------
* §15/D12 keeps the public demo out of scope while requiring the site to gain one by way of
* §15/D12 kept the public demo out of scope while requiring the site to gain one by way of
* a line in a mounted `brand.json`. `brand.json`'s own comment promised `/features/` a
* "per-capability affordance" that had never been defined; D25 defines it as a deep link
* per capability that has a stable public route, appended to the mounted `demoUrl` by
@@ -63,8 +63,10 @@
* behind a Team id no static page can know. Paths are read from the real route tables —
* core's `client/src/App.jsx` and `module-uo`'s `client/src/entry.jsx` on `main` — never
* guessed. A module's public pages are namespaced under its own id, so a route into the
* Ultima Online module is `/uo/…`: the demo §15 plans runs that game, which is why the
* Teams item points there and the module-supplied items in `games/` carry their own.
* Ultima Online module is `/uo/…`. A link into a module's pages names that game
* (`demoGame` here, the `game` prop of `GameFeatures` for the lists in `games/`), and it
* shows only when the mounted `demoGames` lists it (D83): the demo that exists runs Rust,
* so the Teams item's `/uo/guilds` stays hidden there and Rust's clans link stands in.
*/
import { games } from './games/index.mjs';
@@ -82,6 +84,7 @@ import { games } from './games/index.mjs';
* @property {string[]} [caps] Module capability slugs, on a game's own items (`games/`) only.
* @property {boolean} [needsModule] Core machinery a module has to populate (D24).
* @property {string} [demoPath] A stable public route, deep-linked into a demo (D25).
* @property {string} [demoGame] The game module whose pages `demoPath` is in, if any (D83).
*
* @typedef {object} CapabilityGroup
* @property {string} id
@@ -116,6 +119,7 @@ const community = {
label: 'Teams',
needsModule: true,
demoPath: '/uo/guilds',
demoGame: 'uo',
detail:
'A roster, a leader, a private forum, its own notification streams and a Discord ' +
'voice channel, all hanging off one group. Core owns every part of that machinery ' +

View File

@@ -16,7 +16,12 @@
* The staff items carry no `caps`: a capability is what a client feature-detects on, and
* the admin screens are not something any client detects.
*
* No `demoPath` anywhere: the demo instance planned in §15 runs Ultima Online.
* `demoPath` is a public route in Module-Rust's own `client/src/entry.jsx` (`/rust`, and
* `/rust/servers/:id` with its `?tab=` search parameter, which `ServerDetail.jsx` reads).
* The public demo runs Rust (§15), and `demo` is its server's id, fixed by
* `website/deploy/demo/seed.json` (`modules.rust.servers`) and recreated at every wipe.
* The links show only while the mounted `demoGames` lists `rust` (D83). The staff items
* carry none: every admin page needs a sign-in, and D25's links are to public routes.
*/
/** @typedef {import('../capabilities.mjs').Capability} Capability */
@@ -26,6 +31,7 @@ export const features = [
{
label: 'Every server, live',
caps: ['rust', 'servers'],
demoPath: '/rust',
detail:
'Every Rust server the community runs, as it last reported itself: up or down, who is ' +
'on, which map, and when the next wipe is. One sidecar serves one server, so a ' +
@@ -35,6 +41,7 @@ export const features = [
{
label: 'The kill feed',
caps: ['killfeed'],
demoPath: '/rust/servers/demo',
detail:
'Kills, chat, comings and goings, RunicNPC bosses, and the world itself — cargo, ' +
'helicopters, airdrops — as they happen. Every kind passes a default-deny allowlist ' +
@@ -44,6 +51,7 @@ export const features = [
{
label: 'Leaderboards, per wipe',
caps: ['leaderboard'],
demoPath: '/rust/servers/demo?tab=leaderboard',
detail:
'Kills, deaths, NPC kills, structures and time played, for one wipe or all-time — with ' +
'the chat titles players have earned. The record survives the wipe: the game forgets, ' +
@@ -52,6 +60,7 @@ export const features = [
{
label: 'Who is online',
caps: ['presence'],
demoPath: '/rust/servers/demo?tab=online',
detail:
'Who is on right now, and who is asleep. Nothing names a player to the public by ' +
'default: how far a name reaches — staff, signed-in players, everyone — is the ' +
@@ -60,6 +69,7 @@ export const features = [
{
label: 'Wipes',
caps: ['wipes'],
demoPath: '/rust/servers/demo?tab=wipes',
detail:
'Every wipe the site has seen, and the next one once the server has a wipe schedule — ' +
'the monthly forced wipe or a rule of your own.',
@@ -67,6 +77,7 @@ export const features = [
{
label: 'The live map',
caps: ['map'],
demoPath: '/rust/servers/demo?tab=map',
detail:
'The server’s own map picture with layers on it: monuments and world events, the ' +
'zones and NPCs your events placed, players, bases as positions only, and a player’s ' +
@@ -82,6 +93,7 @@ export const features = [
},
{
label: 'Clans are Teams',
demoPath: '/rust/servers/demo?tab=clans',
detail:
'Each clan on a server is a Team on the site: a page, a feed for its members and ' +
'core’s Team notifications. Who may see a clan’s roster is set alongside who may see ' +

View File

@@ -17,7 +17,8 @@
* ids and are resolved against the shard's own string table). Coverage, not a bijection.
*
* `demoPath` is a stable public route on the module's own pages, deep-linked into a demo
* when one is mounted (D25). Paths are read from `module-uo`'s `client/src/entry.jsx`.
* when one is mounted (D25) and the mounted `demoGames` lists `uo` (D83) — which the public
* demo, a Rust deployment, does not. Paths are read from `module-uo`'s `client/src/entry.jsx`.
*/
/** @typedef {import('../capabilities.mjs').Capability} Capability */

View File

@@ -117,23 +117,9 @@ export const notBuilt = [
resolvedBy: 'A game server that runs there.',
link: { href: `${GITEA}/docs/src/branch/main/installer/INSTALL.md`, label: 'The operator guide' },
},
{
id: 'public-demo',
// Phase 5 added `app` and `beta`, and that is not tidying. D27 makes the demo the
// deployment a beta tester connects to, so on those two pages this stopped being a
// thing the site lacks and became the thing the beta is waiting for. An absence that
// blocks a call to action has to be on the page carrying that call to action.
scope: ['features', 'app', 'beta'],
title: 'A public demo you can click through',
body:
'Planned and out of scope today: a virtual machine running the whole stack including ' +
'a game server, with settings locked down and an hourly reset. Until it exists this ' +
'site does not link to one, and there is no screenshot here of something that is not ' +
'running somewhere.',
resolvedBy:
'The machine being stood up. The site is already built to gain it by way of one line ' +
'in a configuration file, rather than a rebuild.',
},
// `public-demo` stood here until 2026-10-28. Its `resolvedBy` was met when
// demo.runicgateway.com went up (docs/website/DEMO_MODE.md), and the site reaches it through
// the mounted `demoUrl`, as the entry promised (D82, D83).
/* ---------------------------------------------------------------------------------------
THE ANDROID CLIENT (phase 5)