All checks were successful
PR checks / checks (pull_request) Successful in 1m41s
The last leg of the Asset Bridge (docs/link/v8.md §16, phase 9c). The cutover put protocol 8 on `main` in every declaration site, which turned this repo red by construction: `checkFacts.mjs` reads the protocol from `link`'s `main`. Two checks were failing, and only one of them was expected. **checkFacts (10)** — protocol 7 → 8 in all three reads (sidecar, overlay, bundle), the bundle triple to 2026.09.15 / sidecar v2.3.0 / overlay v1.3.0, and the releases to link v2.3.0, installer v0.2.0, Module-uo v1.3.0. `moduleApi` and the capability list did not move: core's whole share of eight phases was a deletion. **checkReference (9)** — nobody had planned for this one. `Bridge.cfg` grew nine keys across phases 5, 6 and 7 and the reference page had gone on not mentioning them, which is the failure mode that check exists for. They are listed as two new groups, `Client assets` and `Spawn files`, because they are two separate consents. ## The prose the bridge changed - **New page, Administration → Client files.** `Admin → Client files` is a screen an operator has to press a button on and the site had no page for it. It carries the rule nothing else states: **nothing here happens on a restart**, so a patched client keeps serving the old artwork until somebody says so. - **Protocol versions** said the most recent bump touched five repositories and named `website`. Protocol 8 touched four and did not reach core — its absence is the interesting half, and the page now says why. The store-migration paragraph gains 8, which changed no line of the sidecar's store despite moving megabytes. - **The bridge** said "two ways in". There are three: bulk reads go over the request/reply path in pages, one in flight at a time, and the reason they must not ride the event stream is structural rather than about speed. - **Requirements** gains `libgdiplus` for Linux shard hosts, and the note that no game client has to be installed for any of this — a ServUO shard cannot boot without one. - **Troubleshooting** gains the failure mode that did not exist before: a bestiary with no pictures, or items reading as numbers. - **Maintenance** gains the button to press after patching a client, and **Verify the whole stack** the `doctor` check that landed with it. - The spawn atlas and the marketplace both carry artwork now, so both capability descriptions say so, and `canonicalDocs` points at `link/v8.md`. ## Screenshots, retaken against the real thing The atlas and marketplace shots predate the asset plane, so both were retaken on the demo deployment against a real shard and a real sidecar, plus the new admin screen: 1,095 portraits imported in 2.1 s, 67,496 names in 1.4 s, 313 item pictures in 0.8 s. Nothing here is a mock-up. The atlas shot moved 36px down its page because portraits made the rows taller. ## One defect found on the way `seedDemo.mjs` pinned the demo deployment at **protocol 4** with a comment explaining a `module-uo` debt that has since been paid. Its default now comes from `platform.json`, so the rig cannot drift two protocols behind the platform again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
251 lines
12 KiB
JavaScript
251 lines
12 KiB
JavaScript
/**
|
||
* screens.mjs — every screenshot the site ships, and where it came from.
|
||
*
|
||
* PLAN.md §13 phase 9, D4 / D42–D45.
|
||
*
|
||
* ---------------------------------------------------------------------------------------
|
||
* ONE LIST, THREE READERS
|
||
* ---------------------------------------------------------------------------------------
|
||
* `scripts/captureScreens.mjs` reads this to know what to shoot and where to click before
|
||
* it shoots; `src/components/Screenshot.astro` reads it to render one figure by id; and
|
||
* `scripts/checkScreens.mjs` reads it to prove that every file exists at the declared size
|
||
* and that nothing in `public/screens/` is orphaned. A screenshot is therefore a data
|
||
* change: add an entry, re-run the capture, and the check tells you if you missed a step.
|
||
*
|
||
* That split is what makes a re-capture cheap. §1 says the site must not describe a
|
||
* product that no longer looks like that, and the way a screenshot goes stale is that
|
||
* nobody remembers how it was taken. The route, the viewport, the scroll offset and the
|
||
* signed-in state are all here, so the answer to "how do I retake this" is one command.
|
||
*
|
||
* ---------------------------------------------------------------------------------------
|
||
* WHY EVERY WEB SHOT IS THE SAME SIZE
|
||
* ---------------------------------------------------------------------------------------
|
||
* A 1280×800 viewport at 1.5× device pixels — 1920×1200 in the file. Uniform because the
|
||
* marketing pages lay them out in a grid and a grid of mixed aspect ratios reads as an
|
||
* accident, and because a check that asserts one pair of numbers cannot drift the way a
|
||
* per-file table can. Where a page's interesting part is below the fold, `scrollY` moves
|
||
* the frame rather than the size changing.
|
||
*
|
||
* The phone shots are the device's own portrait size and are declared per family for the
|
||
* same reason (see `PHONE`).
|
||
*
|
||
* ---------------------------------------------------------------------------------------
|
||
* WHAT IS IN THEM
|
||
* ---------------------------------------------------------------------------------------
|
||
* A demo deployment of this platform, wired to a real ServUO shard over a real sidecar
|
||
* (D42): the marketplace rows are player vendors the game actually holds, the atlas is
|
||
* parsed from the shard's own spawn files, the guild rosters came over the bridge, and
|
||
* since protocol 8 the artwork in both came off that host's own UO client. The
|
||
* deployment is branded "Runic Gateway Demo" rather than a real community's name (D43) —
|
||
* the screenshots show the platform, not somebody's private shard.
|
||
*
|
||
* Nothing here is a mock-up, and nothing here was drawn.
|
||
*/
|
||
|
||
/** Web capture geometry. The capture script and the check both read these. */
|
||
export const WEB = { viewport: { width: 1280, height: 800 }, scale: 1.5, width: 1920, height: 1200 };
|
||
|
||
/**
|
||
* Phone capture geometry — the emulator's own portrait pixels, unscaled.
|
||
*
|
||
* An API 35 device rather than the API 36 the plan named: the API 36 image on this machine
|
||
* had 200 MB left on its data partition and refused the install, and wiping somebody's
|
||
* development device to take a screenshot is not a trade worth making. The app targets both.
|
||
*/
|
||
export const PHONE = { width: 1440, height: 3088 };
|
||
|
||
/**
|
||
* @typedef {object} Screen
|
||
* @property {string} id File stem under `public/screens/`, and the handle a page uses.
|
||
* @property {string} route Route on the demo deployment. The capture script's only input.
|
||
* @property {boolean} admin Capture signed in as an administrator rather than anonymously.
|
||
* @property {number} [scrollY] Pixels to scroll before the shot, when the subject is below the fold.
|
||
* @property {string} alt What the screen shows, for somebody who cannot see it.
|
||
* @property {string} caption The sentence printed under the figure.
|
||
* @property {'web'|'phone'} family Which geometry the file follows.
|
||
*/
|
||
|
||
/** @type {Screen[]} */
|
||
export const screens = [
|
||
// ── The product's public surfaces ───────────────────────────────────────────────────
|
||
{
|
||
id: 'shard-status',
|
||
route: '/uo/shard',
|
||
admin: false,
|
||
alt: 'The shard page of a Runic Gateway site, showing the shard online, its gold supply, the state of the shard link, and one player online in Britain.',
|
||
caption:
|
||
'The shard console. Every number on it came over the bridge from a running game server — including the player who was standing in Britain when this was taken.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'marketplace',
|
||
route: '/uo/market',
|
||
admin: false,
|
||
scrollY: 470,
|
||
alt: 'The marketplace page, listing items for sale by player vendors — each row showing the item picture, its name, the shop, the seller, the location and the price — above a search box and price filters.',
|
||
caption:
|
||
'Player vendors, searchable from the website — the same index the in-game vendor search reads, honouring the same per-vendor opt-out. The names and the pictures both come out of the client on the shard host.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'spawn-atlas',
|
||
route: '/uo/atlas',
|
||
admin: false,
|
||
scrollY: 466,
|
||
alt: 'The spawn atlas, listing creatures — sea serpent, water elemental, orc, ettin, horse, goat, sheep — each with the artwork from the game client beside how many of them spawn and on which facets.',
|
||
caption:
|
||
"The spawn atlas is parsed from the shard's own spawn files, so it stays accurate whether or not the server is up. Every portrait was decoded on the shard host and came over the same bridge.",
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'guilds',
|
||
route: '/uo/guilds',
|
||
admin: false,
|
||
alt: 'The guilds page, listing each guild on the shard with its abbreviation, how many members are online, and its leader.',
|
||
caption:
|
||
'Guilds arrive from the shard, not from a form — and a Team on the website is one of them, with its own forum and roster.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'houses',
|
||
route: '/uo/houses',
|
||
admin: false,
|
||
alt: 'The houses page, listing homes that have entered their final decay stage with their owner and location.',
|
||
caption:
|
||
'Houses in danger, from the same decay data the game uses — a live process, not a nightly export.',
|
||
family: 'web',
|
||
},
|
||
|
||
{
|
||
id: 'news',
|
||
route: '/site/news',
|
||
admin: false,
|
||
alt: 'The news page, listing posts with their category, date, headline and summary.',
|
||
caption:
|
||
'News, five-on-friday and the newsletter are one posting system with three categories — and none of it knows what game you run.',
|
||
family: 'web',
|
||
},
|
||
|
||
// ── The administration screens the documentation describes ──────────────────────────
|
||
{
|
||
id: 'admin-dashboard',
|
||
route: '/admin',
|
||
admin: true,
|
||
alt: 'The administration dashboard, showing the site mode, counts of posts, wiki pages and users, and a feed of recent administrative activity.',
|
||
caption: 'The dashboard on first sign-in: site mode, what the site holds, and who has done what.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'admin-users',
|
||
route: '/admin/users',
|
||
admin: true,
|
||
alt: 'The users screen, listing accounts with their role and status and the controls to change them.',
|
||
caption: 'Users and roles. A change of role takes effect on the next request, not the next login.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'admin-shard',
|
||
route: '/admin/uo/link',
|
||
admin: true,
|
||
alt: 'The shard connection screen, showing the sidecar connected, the websocket ingest live, and fields for the sidecar URL, token and protocol version.',
|
||
caption:
|
||
'The shard connection, showing a live sidecar. The token is write-only: it is never sent back to any client, including this screen.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'admin-client-files',
|
||
route: '/admin/uo/files',
|
||
admin: true,
|
||
alt: 'The client files screen, showing the creature portraits section: 1,095 pictures held of 1,095 catalogued, 746 of 800 creatures matched, the last import and the extractor version, above Update and Re-import everything buttons.',
|
||
caption:
|
||
'Client files, imported from the UO client on the shard host over the same bridge. Nothing here happens on a restart — these buttons are the only thing that imports.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'admin-modules',
|
||
route: '/admin/modules',
|
||
admin: true,
|
||
alt: 'The modules screen, showing the Ultima Online module running with the routes it mounts, and the list of hosts modules may be installed from.',
|
||
caption:
|
||
'A module is installed from a release URL and runs inside the server, so only listed hosts are allowed to serve one.',
|
||
family: 'web',
|
||
},
|
||
{
|
||
id: 'admin-appearance',
|
||
route: '/admin/appearance',
|
||
admin: true,
|
||
alt: 'The appearance screen, showing the colour and typography controls a deployment uses to set its own theme.',
|
||
caption: 'Branding is data. One image runs as any community, and the app takes its colours from here too.',
|
||
family: 'web',
|
||
},
|
||
|
||
// ── The Android app, against the same deployment on the same day (D26) ──────────────
|
||
//
|
||
// Captured from an emulator pointed at the demo stack through `adb reverse`, signed in as
|
||
// an ordinary player. The app takes its name, colours and navigation from the site it is
|
||
// connected to, so these are not a neutral app: they are one deployment's app.
|
||
{
|
||
id: 'app-home',
|
||
route: '/',
|
||
admin: false,
|
||
alt: "The app's home screen, showing the deployment's emblem and name, a live online indicator and its description.",
|
||
caption:
|
||
"The app is one screen of setup: type the address of a site, and it becomes that community's app.",
|
||
family: 'phone',
|
||
},
|
||
{
|
||
id: 'app-shard',
|
||
route: '/shard',
|
||
admin: false,
|
||
alt: "The app's shard screen, showing the shard online with one player and its gold supply, links to champion spawns, guilds, governors and falling houses, and a live activity feed.",
|
||
caption:
|
||
'The same bridge feeds the phone: the same player online, and an activity list that is live — those houses entered their final decay stage while it was open.',
|
||
family: 'phone',
|
||
},
|
||
{
|
||
id: 'app-market',
|
||
route: '/market',
|
||
admin: false,
|
||
alt: "The app's marketplace, listing weapons for sale with their price, the shop selling them and where it stands.",
|
||
caption: 'Every player vendor on the shard, searchable from a phone.',
|
||
family: 'phone',
|
||
},
|
||
{
|
||
id: 'app-wiki',
|
||
route: '/wiki',
|
||
admin: false,
|
||
alt: "The app's wiki index, listing pages with their category and summary above a search box.",
|
||
caption:
|
||
"The wiki, the news and the rules are the site's own content, rendered natively rather than in a web view.",
|
||
family: 'phone',
|
||
},
|
||
{
|
||
id: 'app-account',
|
||
route: '/account',
|
||
admin: false,
|
||
alt: "The app's account screen for a signed-in player, with username and password controls, two-factor authentication, trusted devices and recovery codes.",
|
||
caption:
|
||
'Self-service, and the same session model as the website: two-factor, trusted devices and recovery codes all live here.',
|
||
family: 'phone',
|
||
},
|
||
{
|
||
id: 'app-drawer',
|
||
route: '/',
|
||
admin: false,
|
||
alt: "The app's navigation drawer for a signed-in player, listing the deployment's own pages above the player's own account, characters, vendors and houses.",
|
||
caption:
|
||
"The drawer is the deployment's own navigation, not a fixed menu — a site that renames or reorders its pages renames and reorders them here.",
|
||
family: 'phone',
|
||
},
|
||
];
|
||
|
||
/** One entry by id, or `undefined`. */
|
||
export function screenById(id) {
|
||
return screens.find((shot) => shot.id === id);
|
||
}
|
||
|
||
/** Every entry in a family, in declaration order. */
|
||
export function screensOf(family) {
|
||
return screens.filter((shot) => shot.family === family);
|
||
}
|