wtclaude 6b99d7e220
All checks were successful
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / server-tests (pull_request) Successful in 8m47s
test(server): port core's UO suite onto the ctx harness
22 test files moved from core, plus the two that were split out of files core
keeps. 351 tests pass.

One change runs through every moved test, and it is the boundary rather than a
chore: core internals can no longer be stubbed by requiring them, because there
are none to require. `../utils/db` and `../model/settings` do not exist here.
What a test controls instead is the ctx core would have handed over, installed
once by test/_setup.js -- which is a better seam anyway, since it is exactly the
surface the contract promises and nothing wider.

The ctx _setup installs is deliberately unfrozen. Core freezes what it hands a
module and entry.test.js still asserts against a frozen one; but a test that
needs settings.get to return a path has to be able to say so.

Two tests changed SHAPE, and that is the boundary too. fromShardEvent used to
assert through publish() into pushDevices and a captured fetch -- which
endpoints were hit, how many requests went out. None of that is this module's
any more: publish is ctx.push.publish, and the device registry and the relay are
behind it. Reaching for them from here would be reaching past ctx. What remains
is what the module owns and is the part worth guarding: a game account resolves
to a website user, a personal target that resolves to nobody is dropped rather
than published, and a sensitive kind never reaches publish at all.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 12:07:15 -05:00

module-uo — the Ultima Online module for Runic Gateway

The Runic Gateway website is becoming game-agnostic: core keeps accounts, sessions, the wiki, posts, branding, theming and the admin panel, and everything that knows what a shard is moves out into an installable module. This repo is that module — the first one, and the reference for every module that follows.

        RunicGateway/website  (core — game-agnostic)
                 │  loads modules at boot, synchronously, from the filesystem
                 ▼
        ┌───────────────────────────────────────────┐
        │  module-uo   (>>> HERE <<<)               │
        │  shard status · spawn atlas · marketplace │
        │  governors · cliloc · town crier · uo-link│
        └───────────────────────────────────────────┘
                 │  server half: routers, models, schema fragment
                 │  client half: prebuilt ESM chunk, SPA routes + nav
                 ▼
        the shard bridge (RunicGateway/link → RunicGateway/servuo-plugins)

The module's id is uo — that is what appears in module.json, in the installed_modules table, in the modules/<id>/ path on disk and in the URL segment (/uo/*, /admin/uo/*, /player/uo/*). Module-uo is the repository; module-uo is the module and its release artifact.

Status: the bundle skeleton exists; the extraction has started

The design of record is website/MODULE_SYSTEM.md and the normative contract is website/MODULE_API.md in the docs repo — read them before opening a PR here. Where the two differ, the contract wins.

Phase Where it happens State
0 — CI trigger fix, cut website edge, bootstrap this repo website, here done
1 — module API contract (docs/website/MODULE_API.md) + the atlas spike docs, website done
2 — core scaffolding: loader, installed_modules, registries, client registry website done
3 — extract the UO half of the site into this repo website, here 🟡 in progress
4 — delivery: the admin Modules screen + the Docker path website

Phase 3 moves the UO half of website/ here in ten slices (MODULE_SYSTEM.md §2.7.1), server-first and then client. Each slice is one PR here that adds, and one PR in website that deletes — this one merging first, so website's edge branch serves the feature from core right up to the moment core drops it.

Slice 0 is the bundle skeleton, and it registers nothing on purpose. What it proves is the delivery path itself: core discovers the module, validates module.json, calls register(), serves the client chunk, injects it, and reports the module started — and the chunk resolves React, the renderer and the router from core's window.__rg rather than bundling its own. Every slice after this one adds registrations to server/index.js and client/src/entry.jsx.

Working on it

npm ci --prefix server  && npm test --prefix server  && npm run check:imports --prefix server
npm ci --prefix client  && npm test --prefix client  && npm run build --prefix client
npm run check:externals --prefix client        # asks the BUILT chunk, so it runs after the build

The two check:* scripts are the contract's acceptance criteria rather than this module's own tests: no import may escape the module root (MODULE_API.md §5.1), and no bare specifier may survive into the built chunk (§3.6). The matching failure — a shared dependency being bundled — fails the build itself, from a guard inside vite.config.js.

Running it against a real core means checking this repo out as website/modules/uo, building the client half, and booting core. The four-step browser smoke in MODULE_API.md §7.7 is the only thing that proves the client half works: its real failure modes are timing and module resolution, and neither has a shape a DOM-less test runner can see.

What it contains

One repo, one bundle: the server half and the client half live side by side and version together, so a route and the screen that calls it can never be mismatched. A is in the tree today.

module.json            ✅ id, version, coreApi range, mounts, extensions
server/index.js        ✅ the entry point — register(ctx, api), synchronous, no database
server/scripts/        ✅ checkImports.js — the §5.1 boundary check
server/test/           ✅ node --test, with a fake ctx standing in for core
server/                   routers, controllers, models, utils
server/db/schema.sql      idempotent fragment, replayed by core's ensureSchema()
server/db/purge.sql       destructive; only ever run by an explicit purge
client/src/entry.jsx   ✅ the chunk's entry — registers routes, nav, feature provider
client/src/shim/       ✅ react, react-dom, react-router-dom, jsx-runtime, from window.__rg
client/vite.config.js  ✅ the library build, the aliases, the not-bundled guard
client/src/               route components, nav registrations, feature provider
client/dist/           ✅ PREBUILT ESM chunk, built by CI — never by an operator

Release artifact: module-uo-<version>.tar.gz, plus a manifest carrying its sha256.

How it reaches an operator

An operator never builds anything. Installing a module is the WordPress-plugin experience: an admin-panel action, or a directory mounted into the Docker container — never a build step, because production runs a prebuilt, pull-only image. That constraint is why the client half ships as a prebuilt ESM chunk that resolves React from a window.__rg global rather than an import map (an import map must be inline, and the site's CSP is script-src 'self').

The installer is not the delivery path. It deploys the shard side — the plugin overlay and the uo-link sidecar — and never contacts the website. Module delivery is website-side only.

Compatibility

module.json declares a coreApi semver range, checked at boot against core's MODULE_API_VERSION. A mismatch fails loudly — the module is marked startup_failed and the site comes up without it, rather than mis-loading. This is a separate number from PROTOCOL_VERSION, which versions the shard wire protocol and says nothing about a website module.

A module that fails to load must never take the site down.

Repo What
thisRunicGateway/Module-uo The UO module: the game-specific half of the website.
RunicGateway/website Core — the site, admin panel and API that loads this module.
RunicGateway/link The uo-link sidecar — the network-facing half of the game bridge this module talks to.
RunicGateway/servuo-plugins The C# ServUO plugin that feeds the sidecar.
RunicGateway/installer Deploys the shard side. Not the module delivery path.
RunicGateway/docs All project documentation, including the module system design and this module's docs under modules/uo/.

Contributing

See CONTRIBUTING.md. Contributions are welcome, AI assistance must be disclosed, and security problems go to SECURITY.md rather than a public issue.

License

GNU General Public License v3.0 or later — see LICENSE.md.

Description
No description provided
Readme 724 KiB
v0.3.0 Latest
2026-08-12 04:11:54 +00:00
Languages
JavaScript 100%