docs(website): record slice 3, and MODULE_API 1.3.0 #138

Merged
whitlocktech merged 1 commits from docs/module-extract-client into main 2026-08-11 19:35:17 -05:00
5 changed files with 204 additions and 288 deletions

View File

@@ -85,7 +85,7 @@ change/set password (the SSO-account "no current password" path from `has_passwo
list/unlink SSO identities — each mutation folding its `ApiResult` into a section-scoped, localized
banner (§7). **Game-account linking** (§6.3) — the in-game `[link` one-time code (`POST
/player/shard/link`) plus the hybrid signup (`POST /player/shard/account`, shown only when the public
`gameAccountSignup` flag is set), and the linked-accounts list. **Own game data**, text-only (§6.3):
`gameAccountSignup` flag is set — see the OWED note below), and the linked-accounts list. **Own game data**, text-only (§6.3):
per-account character roster → a character sheet (attributes, vitals, resistances, best-first skills,
equipment with AOS mods, and guild/governor standing chips — bare cliloc-number titles/item names are
skipped, as the app ships no cliloc table, matching the website's `CharacterSheet.jsx`); player
@@ -99,6 +99,18 @@ spec-aligned (as recorded for M1); 17 new JVM unit tests cover the account + pla
**No backend/API change** — the `/auth/me/*` and `/player/shard/*` surfaces the app consumes were the
§8 prerequisites, already landed.
> **OWED — `gameAccountSignup` moved and the app has not followed (2026-08-11).** Phase 3 slice 3 of
> the website's module extraction (`docs/website/MODULE_SYSTEM.md` §2.7.1) moved game-account signup
> policy out of core: the flag is no longer on `GET /public/settings` and is now on module-uo's
> `GET /public/shard/features`, beside the visibility flags. `PublicDto.kt:80` defaults it to
> `false`, so **nothing crashes** — `CharactersViewModel.kt:72` reads `false` and the app simply
> stops offering game-account creation. The fix is one DTO field, one ViewModel line and two tests,
> reading the flag from the features endpoint the app already calls.
>
> Deferred rather than coordinated into the slice (org lead, 2026-08-11): the website work lands on
> `edge` and reaches `main` as one cutover, so there is room. **It must land before that cutover** —
> after it, a shipped app quietly loses a feature.
✅ **M5 — design pass** (2026-07-20, `RunicGateway/Android-app#10`): the shard-website theme applied
across every screen, restyling the working M1–M4 UI with **no architecture, data-flow, endpoint, or DTO
change** (§2.1). The design was produced in Claude Design (`Runic Gateway Screens.dc.html`) and

View File

@@ -908,7 +908,7 @@ from the per-route **siteMode** middleware (§5), never from an auth gate.
| Method | Path | Notes |
|---|---|---|
| GET | `/settings` | whitelisted public keys, derived `registration`/`gameAccountSignup` flags, the per-shard **`brand`** block (name, `accent` color, logo/hero/favicon) a client themes itself from — one image runs as any shard, asset fields may be site-relative paths (resolve against the base URL); these are **effective** values, so an admin theme (`theme_visual`) beats `BRAND_ACCENT_COLOR` and an uploaded `brand_assets` asset beats its `BRAND_*` path — an optional **`theme`** block, the resolved CSS custom properties for that admin theme (absent when the instance was never themed, which is what makes it render from the shipped stylesheet unchanged) — and a **`push`** block `{ ntfyUrl }` (M7): the client-facing ntfy relay URL the app's embedded distributor registers its device topic against, from `NTFY_PUBLIC_URL` / first `NTFY_ALLOWED_ORIGINS` (never the internal `NTFY_BASE_URL`); `null` when push isn't configured for the shard. |
| GET | `/settings` | whitelisted public keys, the derived `registration` flags (`gameAccountSignup` was one of these until the module extraction moved game-account policy to module-uo — it is on that module's `GET /public/shard/features` now, and the `game_account_signup` settings row is unchanged), the per-shard **`brand`** block (name, `accent` color, logo/hero/favicon) a client themes itself from — one image runs as any shard, asset fields may be site-relative paths (resolve against the base URL); these are **effective** values, so an admin theme (`theme_visual`) beats `BRAND_ACCENT_COLOR` and an uploaded `brand_assets` asset beats its `BRAND_*` path — an optional **`theme`** block, the resolved CSS custom properties for that admin theme (absent when the instance was never themed, which is what makes it render from the shipped stylesheet unchanged) — and a **`push`** block `{ ntfyUrl }` (M7): the client-facing ntfy relay URL the app's embedded distributor registers its device topic against, from `NTFY_PUBLIC_URL` / first `NTFY_ALLOWED_ORIGINS` (never the internal `NTFY_BASE_URL`); `null` when push isn't configured for the shard. |
| GET | `/status` | status message + current mode, **plus a `version` block** (`{ service:'runic-gateway', api, server }`) so a client first-run probe recognizes the backend and can run a version-mismatch guard |
| GET | `/version` | lightweight, **DB-free** backend identity/version (`{ service, api, server }`) — the canonical target for the version guard and a cheap liveness check |
| GET | `/modules` | `{ modules: [{ id, name, version, capabilities }] }` — the modules this backend is currently **serving**, in scan order (module system, `MODULE_API.md` §2.9). A module that is disabled or failed to load is **absent**, not listed with a state: its routes and nav are absent too, so the client renders a site without that capability rather than advertising one that 503s. The recorded failure stage and reason are admin-panel detail and are never published here. `capabilities` are opaque strings the module declares — feature-detect against them and treat an unknown one as absent. Like `/status` and `/version` it is **DB-free and not site-mode gated**, so a client can still feature-detect during maintenance. It is *not* how a module's client chunk loads — `htmlShell` injects a `<script type="module">` per started module. |

View File

@@ -26,13 +26,20 @@ here extends the contract first, in this file, before the module is written agai
Core exports a single integer-major semver string from `server/src/modules/version.js`:
```js
const MODULE_API_VERSION = '1.2.0'
const MODULE_API_VERSION = '1.3.0'
```
The client half carries the same number (`client/src/modules/version.js`) and a test asserts the two
agree. Duplicated rather than fetched because the value has to be on `window.__rg` before the first
module chunk evaluates, which is earlier than any network round trip could answer.
**1.3.0 — Phase 3 slice 3, the client half's move.** Three client additions, each because the
extraction needed it: a nav item may carry an **`icon`** component (§3.3), core declares a third slot
**`player.invite.accepted`** (§3.7), and `window.__rg.api` gained **`BASE`** — which §3.5 specified
from the first draft and `shared.js` had never actually published, because nothing needed it until a
module had to build an EventSource URL. Additions only; the server half is untouched and bumps
because the two halves state ONE version.
**1.2.0 — Phase 3, the client half.** `registry` gained `registerExtension` and core gained client
extension slots (§3.7). An addition only, and the first change to `window.__rg` since 1.0.0 — the
server half is untouched, and both files bump because the two halves state ONE version.
@@ -156,7 +163,12 @@ Three narrowings from `MODULE_SYSTEM.md` §2.1, all deliberate:
`setAuthCookie` and the TOTP challenge primitives. Minting sessions is core's job; a module that
needs an identity needs to *read* one.
- **`ctx.settings` is three functions, not the model.** The model exports 24 names, most of them
registration/game-signup/app-links policy that is core's business.
registration and app-links policy that is core's business. It said *game-signup* policy too until
Phase 3 slice 3, which is when that turned out to be wrong in both directions: the setting's own
help text names Bridge.cfg, so it was never core's — and slice 1 had shipped a ported controller
calling `settings.isGameAccountSignupEnabled()`, which this narrowing does not expose, so
`POST /player/shard/account` answered 500 for every caller until slice 3 found it. A narrowing is
only as safe as the tests that cross it.
- **`ctx.posts` is four functions.** `create`/`update`/`remove` are the CMS, not a module's.
And one addition the spike forced: **`ctx.express` and `ctx.validator`**. A module lives at
@@ -579,7 +591,27 @@ registry.registerNav('uo', {
`group` names an existing core group; an unknown group name appends a new group at the end rather
than dropping the item. `order` sorts within the group, core items keeping their current positions.
`feature` names a flag resolved by the provider below.
`feature` names a flag resolved by the provider below. **`icon`** (1.3.0) is a component core renders
exactly as it renders its own rows' icons.
**Core supplies no fallback icon, and both layouts tolerate a row without one.** A module that omits
`icon` gets no icon, the same as a core row that omits it — inventing one would be core making a
presentation choice for content it knows nothing about. The field exists because without it the six
UO rows would have extracted as the only text-only entries in a sidebar where every other row has a
glyph, which reads as breakage rather than as a design; `icon` was already among the fields an
override may **not** touch, so the concept predates a module being able to send one.
A module should match the nav its row lands in rather than ship one glyph for everywhere: the admin
sidebar draws at 18px with a 1.6 stroke and the player portal at 16px with a 2. That is presentation,
deliberately not in the kit — putting core's icon frame in the contract would make changing it a
major bump.
**The tolerance is newer than the field.** `PlayerPortalLayout` rendered `<n.icon />` unguarded,
which was correct for as long as every row in it was core's own and had one; the first module row
without an icon blanked the entire portal with React error #130. Both layouts guard now, and
module-uo's `registration.test.js` asserts an icon on every admin and player row it registers —
belt and braces, because the failure is invisible to a DOM-less test runner and the smoke caught it
only by chance of registering that row last.
Six details settled when this was built (Phase 2 PR 8, `client/src/modules/nav.js`):
@@ -697,6 +729,12 @@ Core exposes the primitive, not the object:
window.__rg.api = { request, ApiError, BASE } // request(path, { method, body, headers, raw })
```
`BASE` is `/api/v1`, and it was specified here from the first draft while `shared.js` published only
the first two — nothing needed it until slice 3, when a module first had to build an **EventSource**
URL. `request` is fetch-only, so an SSE subscriber constructs its own; the alternative is a module
hardcoding `/api/v1`, which asserts something about where core mounts its API that core has never
promised to keep. Published as of 1.3.0.
`request` is `client.js`'s existing `req` — same-origin `/api/v1`, `credentials: 'include'`, JSON
in/out, throwing `ApiError(status, message, body)`. A module builds its own namespace over it and
owns the paths it calls, which is correct: it owns the routes at the other end.
@@ -769,7 +807,8 @@ by checking each imported binding is **identity-equal** to the one core publishe
### 3.7 Extension slots — module content inside a core page
Added in **1.2.0** (settled 2026-08-11). The client twin of §2.4's `registerExtension`, and the same
Added in **1.2.0** (settled 2026-08-11), third slot added in **1.3.0**. The client twin of §2.4's
`registerExtension`, and the same
rule in both halves: **core declares a slot, only core may declare one, and at most one module may
fill it.**
@@ -795,12 +834,13 @@ unfilled case and wrong about the failed one: the slot *is* filled, so the separ
the component then throws into the boundary and leaves the separator behind on its own. Found in a
browser with this exact footer separator, which is the only place it could have been found.
The slots in 1.2.0:
The slots:
| Slot | Rendered in | Props core passes |
| --- | --- | --- |
| `site.footer.status` | `components/SiteFooter.jsx`, in the info row | `linkStyle` — the row's own link styling |
| `admin.users.detail` | `routes/admin/views/UserDetail.jsx`, below the security panel | `userId` |
| `player.invite.accepted` (1.3.0) | `routes/player/AcceptInvite.jsx`, after an invite is accepted | `onDone` — send the invitee on to the portal |
**A slot is named for a PLACE, never for a meaning.** `site.footer.status` is "the status-ish spot in
the footer", not a declaration that core knows what a game server's status is: core supplies the
@@ -819,6 +859,24 @@ binding that leaves core in the client half of Phase 3, so a slot that handed it
something core is about to delete. The extension builds its own client for the routes it registered
at the other end, which is §3.5's rule applied to a slot.
**`player.invite.accepted` is the one slot whose emptiness core reads.** Core rendered a UO
game-account step on that page until slice 3 — it read a `gameAccountSignup` flag out of its own
settings and posted to a shard route — and an invite is a core concept that staff receive too, so the
page stays and its optional step becomes a slot. With the slot unfilled there is no screen to show at
all, so core navigates straight on; with it filled, core renders its shell, the slot, and a "skip"
control *outside* the boundary, because an extension that throws must not take the way out with it.
That makes `extensionFor` legitimate for a core page to call — once, and only for this reason. The
rule is not "never ask whether a slot is filled", it is **ask only when the answer changes control
flow, never when it changes decoration**; decoration goes inside `<Slot wrap>`. `AcceptInvite` is the
only caller in core, and the difference between the two cases is exactly the footer-separator bug
above.
Core's subtitle for that screen says nothing about what the step is. Naming it would be core
describing content it does not own, and there is no wording that stays true for the next game.
**Whether the step should appear at all is the module's decision too** — it is made from a setting
core no longer reads, so the filling component either renders or calls `onDone` itself.
**Errors are contained.** Core renders a filled slot inside an error boundary: a component that
throws costs its own section and a console error, never core's page. That asymmetry is deliberate and
is where the client differs from the server — a module route that throws costs the module's own page,
@@ -1280,3 +1338,23 @@ chunk executes under the **enforced** `script-src 'self'` with no CSP report, wh
(`htmlShell.init`), so rebuilding the client without restarting the server serves an `index.html`
pointing at a hashed bundle that no longer exists — core never runs, `window.__rg` is undefined, and
the failure looks exactly like a contract violation in the module.
**Run it against the real module too, not only a throwaway.** A hand-written chunk proves the
delivery path; it does not prove the module. Slice 3's run — module-uo checked out into
`website/modules/uo`, the deletion branch checked out in `website/` — found two things a throwaway
never would have, and neither is visible to any test in either repo:
- **`PlayerPortalLayout` rendered `<n.icon />` unguarded.** `icon` is optional in the nav contract
and every core row in that sidebar had one, so the difference from `AdminLayout` cost nothing until
a module registered a row without — then it was React error #130 and a **blank player portal**,
not a missing glyph. Both layouts guard now.
- **A relative `MODULES_DIR` failed every module** with `client.entry escapes the module directory`,
because `resolveClient` compared an absolute resolved path against a relative directory. Running
from `server/` is what produces one, so this recipe was the thing that triggered it. `MODULES_DIR`
is resolved absolute now, and the recipe works either way.
Two operational notes for the run itself. The module's own `server/package.json` has dependencies
(`ws`), so a module copied into place needs `npm ci --omit=dev` in its `server/` before it will
`register` — the release tarball carries them, a working copy does not. And **copy the module
directory, do not symlink it**: the loader's `filter(e => e.isDirectory())` reports a Windows
junction as a symlink and skips it silently.

View File

@@ -780,7 +780,7 @@ Each slice is one `module-uo` PR (adds), one `website` PR (deletes), and one `do
| 0 | **The bundle skeleton** | `module.json`, both `package.json`s, `server/index.js` registering nothing, the Vite library build + the four shared-dep shims, CI armed, the §5.1 zero-internal-imports check. `website` untouched. |
| 1 | **The whole server half** | 40 files / ~9,674 lines, 25 of core's 82 test files, 27 of its 68 tables — every UO model, util, router and controller, `config/shardStreams.js`, `scripts/importSpawnAtlas.js` and the art JSON. **One merge, five commits** (below). |
| 2 | **Client extension slots** | Core only, and the one slice that adds rather than moves: the client twin of `declareSlot`/`registerExtension` (API §3.7), the `site.footer.status` and `admin.users.detail` slots, and core filling both itself. `module-uo` untouched. |
| 3 | **The whole client half** | 51 files / ~3,700 lines — all twelve public pages (`Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`) under `/uo/*`, every admin and player view under `/admin/uo/*` and `/player/uo/*`, `PlayersOnline`, `VendorSales`, `CharacterStats`, `GameAccounts`, the `data/` and `lib/` UO leaves, the public nav rows, the feature provider, and both slot fills. |
| 3 | **The whole client half** | **35 files / 5,332 lines** (measured; the 51/~3,700 above was counted differently) — all twelve public pages (`Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`) under `/uo/*`, every admin and player view under `/admin/uo/*` and `/player/uo/*`, `PlayersOnline`, `VendorSales`, `CharacterStats`, `GameAccounts`, the `data/` and `lib/` UO leaves, the public nav rows, the feature provider, and all three slot fills. |
| 4 | **De-UO core's copy** | `About`, `Screenshots`, `Website`, `SiteFooter`'s prose, `heroLayout`'s defaults, `api/client.js`'s `shard`/`atlas` namespaces, and the comments in `navOverrides.js` — plus the §5.2 CI grep that keeps them out |
| 5 | **Close the phase** | `module-uo`'s frozen route manifest and release workflow; `docs/modules/uo/` and `docs/modules/rust-dryrun.md` |
@@ -1019,6 +1019,112 @@ junction as a symlink, so a module linked rather than copied into `modules/` is
with nothing logged. Not a defect for a real install — `modules/` is a bind mount of real
directories (§2.5) — but it is the first thing to check when a module fails to appear.
#### Slice 3 — the whole client half (Module-uo#4 + website#139, 2026-08-11)
35 files / 5,332 lines and two of core's thirteen client tests, leaving core with **620 server + 161
client** tests, a manifest unchanged at 158 public + 2 internal, and an OpenAPI diff of exactly one
property. `MODULE_API_VERSION` 1.2.0 → **1.3.0**. The estimate in the table above said 51 files /
~3,700 lines; the real closure is fewer files and more lines, and the table is corrected rather than
the count re-derived to match it.
**The dependency claim held exactly.** Every one of the 35 imports the seven §3.4 kit members plus
`lib/format.js`, and nothing else — so the kit needed no additions and `client/src/core.js` is the
whole boundary. Unlike the server's `core.js` it is a plain read, because `window.__rg` is published
before any module chunk evaluates and there is no gap to defer around; a ported component keeps its
ordinary import shape instead of being wrapped in an accessor that would cost it its identity. Only
`ago()` was vendored, not all six of core's date helpers — the vendoring line from slice 1 (pure leaf
helpers may be copied, security controls may not) plus "copy what you use".
##### The three things core owed first
`player.invite.accepted`, the third slot (API §3.7). `icon` on a nav item (§3.3). And `api.BASE`,
which §3.5 had specified from the first draft and `shared.js` had never published — nothing needed it
until a module had to build an EventSource URL, `request` being fetch-only.
##### `/player` had no index, and the answer generalises
`PlayerCharacters` was the portal's index route *and* its first nav row, both UO. Rather than name a
replacement or invent a landing screen — the org lead is merging the two logged-in areas into one
permission-driven area, so a new core page would be something to unwind — `/player` resolves to the
**first row of the base player nav this viewer can reach** (`firstDestinationFor`, beside
`allowedPathsFor`). With the module installed that is still Characters, so a player's first screen
after signing in does not change; with nothing installed it is Account.
From the BASE nav, never the override-merged one, and here that rule is sharper than it is for
`allowedPathsFor`: an override is presentation, and where everybody lands is behaviour. It also means
an admin cannot move the landing page somewhere a role cannot follow.
##### `game_account_signup` was never core's, and slice 1 had broken it
The setting's help text names *Bridge.cfg* and says the game server's own SignupMode must agree —
core cannot own a sentence about a UO shard. The mode list, the derived public flag, the validation
and the Site Settings field all moved; the **row keeps its key and value**, so a configured instance
finds its mode where it left it, the same grandfathering as `spawn_atlas_servuo_path` (API §6.5).
Moving it surfaced a defect slice 1 had shipped: the ported controller called
`settings.isGameAccountSignupEnabled()`, which `ctx.settings` does not expose, so
`POST /player/shard/account` and its staff twin answered **500 for every caller** and no test in
either repo reached the branch. Both regression tests were shown to fail before the fix went in.
**A known break, accepted:** the shipped Android app reads `gameAccountSignup` off
`/public/settings` (`PublicDto.kt:80`). The field defaults to `false`, so nothing crashes — the app
silently stops offering game-account creation until it reads the module's `/public/shard/features`
instead. Out of scope for this slice, recorded in the Android plan, and it lands well before this
workstream's cutover reaches `main`.
##### What the browser found this time
The §7.7 smoke, run against the real pair, earned its place again. `PlayerPortalLayout` rendered
`<n.icon />` **unguarded** where `AdminLayout` guarded its equivalent — correct for as long as every
row in that sidebar was core's own and had an icon, and **React error #130 with a blank portal** the
moment a module registered a row without one. Core is guarded now and module-uo asserts an icon on
every admin and player row; the public header is text buttons and is excluded.
It also found that a **relative `MODULES_DIR`** — which §7.7's own recipe produces when run from
`server/` — failed every module with `client.entry escapes the module directory`, because
`resolveClient` compares an absolute resolved path against a relative one. `MODULES_DIR` is resolved
absolute now.
Verified in Chrome, zero CSP reports: the public pages under `/uo/*` with core's chrome, the nine
header rows, the six sidebar rows with their glyph in core's Moderation and System groups and My
Characters in its own trailing group, `/admin/uo/link` with the migrated signup field showing the
live value, the user-detail slot filled with real data, the footer slot with its separators intact,
`/player` resolving to `/player/uo/characters`, and the invite flow end to end — core's shell and
skip control, the module's prose and form. The SSE feed connected, which is `api.BASE` working.
##### The nav-override cost, in the wild
Decision 9 (clean break, no nav-override migration) has a visible price and the review instance shows
it exactly. Overrides are keyed by `to`, so all fifteen changed paths orphan and are dropped. On that
instance the operator had gathered the nine UO rows into a **dropdown section**; after the extraction
those keys match nothing, the section is empty, and the nine rows render flat across two lines of the
header. A stored order on a core row that survived (`/site/about` at 8) now interleaves with the
module rows rather than preceding them.
So the release note is stronger than "a hidden row may reappear": **an operator's nav customisation
of the UO rows — order, labels, hidden state and section grouping — is lost and must be redone.**
Accepted rather than migrated, on the grounds that the site is not public yet.
##### Two checks that had never met a real chunk
`checkExternals.js` **rejected the build outright**, naming a fragment of minified JSX as an imported
specifier: a button reading "Approve and import" puts the token immediately before a quote, and no
regexp distinguishes a keyword from the same letters inside a string. The server's `checkImports.js`
hit this from the other side and answered it the same way — a character walk. Here it is a *mask*
rather than a rewrite, because a real import has its keyword outside a string and its specifier
inside one, so blanking strings would take the answer with the noise.
Writing the test for that false positive found the false **negative** underneath it: the pattern
required whitespace after `import`, so it could never see `import{useState}from"react"` — the one
shape a minified build actually emits, and the likeliest way for a missed alias to reach production.
A check tested against only one of its two answers is half a check.
`registration.test.js` is new and closes the gap those two left: stand up a fake `window.__rg` with a
recording registry and the real React, import the **built** chunk, and read back what it asked for —
no DOM, because nothing renders. It holds the agreement that rots quietly (every nav row points at a
route this module registered) rather than restating both lists. CI now builds before it tests, since
both artifact-reading tests skip without a build and were otherwise green while asking nothing.
**Phase 4 — Delivery.** The admin-panel Modules screen (install, enable, disable, retry, purge,
`startup_failed` with its recorded reason) and the Docker-environment path from §2.5. Deliberately
last, so loader, packaging, schema and chunk-loading problems are not all being debugged at once.

View File

@@ -257,134 +257,10 @@
"method": "POST",
"path": "/api/v1/admin/settings/brand-asset/:slot"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/atlas"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/approve"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/import"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/atlas/path"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/atlas/reject"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/audit"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/ban"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/broadcast"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/char/:serial"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/clilocs"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/clilocs/import"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/clilocs/path"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/houses"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/kick"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/link"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/pages"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/pages/:id/close"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/pages/:id/respond"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/roster/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/sales"
},
{
"method": "POST",
"path": "/api/v1/admin/shard/unban"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/vendors/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/shard/visibility"
},
{
"method": "PUT",
"path": "/api/v1/admin/shard/visibility"
},
{
"method": "PUT",
"path": "/api/v1/admin/site-mode"
},
{
"method": "GET",
"path": "/api/v1/admin/uo-link/config"
},
{
"method": "PUT",
"path": "/api/v1/admin/uo-link/config"
},
{
"method": "GET",
"path": "/api/v1/admin/uo-link/stream"
},
{
"method": "POST",
"path": "/api/v1/admin/uo-link/towncrier"
},
{
"method": "DELETE",
"path": "/api/v1/admin/uo-link/towncrier/:id"
},
{
"method": "POST",
"path": "/api/v1/admin/uploads"
@@ -413,30 +289,6 @@
"method": "POST",
"path": "/api/v1/admin/users/:id/mfa/reset"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/houses"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/shard/link/:account"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/online"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/sales"
},
{
"method": "GET",
"path": "/api/v1/admin/users/:id/shard/standing"
},
{
"method": "DELETE",
"path": "/api/v1/admin/users/:id/trusted-devices"
@@ -721,62 +573,6 @@
"method": "GET",
"path": "/api/v1/player/appeals/eligible"
},
{
"method": "POST",
"path": "/api/v1/player/shard/account"
},
{
"method": "GET",
"path": "/api/v1/player/shard/accounts"
},
{
"method": "GET",
"path": "/api/v1/player/shard/char/:serial"
},
{
"method": "GET",
"path": "/api/v1/player/shard/houses"
},
{
"method": "POST",
"path": "/api/v1/player/shard/link"
},
{
"method": "GET",
"path": "/api/v1/player/shard/roster/:account"
},
{
"method": "GET",
"path": "/api/v1/player/shard/sales"
},
{
"method": "GET",
"path": "/api/v1/player/shard/vendors/:account"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/champions"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/creatures"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/creatures/:slug"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/landmarks"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/meta"
},
{
"method": "GET",
"path": "/api/v1/public/atlas/regions"
},
{
"method": "POST",
"path": "/api/v1/public/contact"
@@ -805,82 +601,6 @@
"method": "GET",
"path": "/api/v1/public/settings"
},
{
"method": "GET",
"path": "/api/v1/public/shard/champs"
},
{
"method": "GET",
"path": "/api/v1/public/shard/economy"
},
{
"method": "GET",
"path": "/api/v1/public/shard/features"
},
{
"method": "GET",
"path": "/api/v1/public/shard/feed"
},
{
"method": "GET",
"path": "/api/v1/public/shard/governors"
},
{
"method": "GET",
"path": "/api/v1/public/shard/governors/:city/history"
},
{
"method": "GET",
"path": "/api/v1/public/shard/guilds"
},
{
"method": "GET",
"path": "/api/v1/public/shard/houses"
},
{
"method": "GET",
"path": "/api/v1/public/shard/idoc"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market/meta"
},
{
"method": "GET",
"path": "/api/v1/public/shard/market/vendors/:serial"
},
{
"method": "GET",
"path": "/api/v1/public/shard/online"
},
{
"method": "GET",
"path": "/api/v1/public/shard/points"
},
{
"method": "GET",
"path": "/api/v1/public/shard/points/:system"
},
{
"method": "GET",
"path": "/api/v1/public/shard/presence"
},
{
"method": "GET",
"path": "/api/v1/public/shard/ruleset"
},
{
"method": "GET",
"path": "/api/v1/public/shard/status"
},
{
"method": "GET",
"path": "/api/v1/public/shard/stream"
},
{
"method": "GET",
"path": "/api/v1/public/status"