feat(screens): phase 9 — real screenshots, from a real shard
All checks were successful
PR checks / checks (pull_request) Successful in 1m25s
All checks were successful
PR checks / checks (pull_request) Successful in 1m25s
D4 asked for screenshots of the review stack rather than placeholders. Seventeen
of them: eleven of the site in a browser, six of the app on a phone, all from one
demo deployment wired to a running ServUO shard over a real sidecar, captured on
one day (D42).
The deployment is branded "Runic Gateway Demo" rather than a real community (D43),
and the captures sit beside the claims they support — the homepage, /features/, and
five of the administration pages phase 7 could describe but not show (D44).
The rig is committed rather than remembered (D45):
scripts/seedDemo.mjs content, by driving the site's own API — never SQL,
because a row the product could not have produced is
a screenshot of a product that does not exist
src/data/screens.mjs every capture: route, viewport, scroll, alt, caption
scripts/captureScreens.mjs npm run screens:capture
scripts/checkScreens.mjs the ninth check script, in CI
Shard-side dressing is servuo-plugins' scaffolding (D46), never deployed.
The rig found five things nothing else had. One is fixed upstream — a fresh
module-uo install pinned wire protocol 3 against a sidecar speaking 4, released as
v1.0.2, which this repo's own facts check then caught in platform.json. Four are
raised as product observations and worked around in the rig: a renamed guild
member never reaches the site, a guild deleted while the shard is down is a ghost
row forever, "Houses in danger" cannot show a house that was already collapsing,
and the app's news list prints raw ISO timestamps.
Players online reads 0. Logging a character in needs a UO client driven by hand,
and that is where this stopped — PLAN.md §10 says exactly why, and how to retake
the two frames that would change.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
240
src/data/screens.mjs
Normal file
240
src/data/screens.mjs
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* 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. 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 how many players are online.',
|
||||
caption:
|
||||
'The shard console. Every number on it came over the bridge from a running game server — nothing here is stored by hand.',
|
||||
family: 'web',
|
||||
},
|
||||
{
|
||||
id: 'marketplace',
|
||||
route: '/uo/market',
|
||||
admin: false,
|
||||
scrollY: 470,
|
||||
alt: 'The marketplace page, listing items for sale by player vendors with their prices, shop names and locations, 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.',
|
||||
family: 'web',
|
||||
},
|
||||
{
|
||||
id: 'spawn-atlas',
|
||||
route: '/uo/atlas',
|
||||
admin: false,
|
||||
scrollY: 430,
|
||||
alt: 'The spawn atlas, listing creatures with how many of them spawn and on which facets, above a search box and facet filters.',
|
||||
caption:
|
||||
"The spawn atlas is parsed from the shard's own spawn files, so it stays accurate whether or not the server is up.",
|
||||
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-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 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 activity list is live — those two houses entered their final decay stage while this 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);
|
||||
}
|
||||
Reference in New Issue
Block a user