Files
website/modules/uo/SPIKE.md
wtclaude bf470c7658 spike(modules): carry /public/atlas/* behind the proposed module surface
THROWAWAY BRANCH — evidence for the Phase 1 contract, never merged. See
modules/uo/SPIKE.md and docs/website/MODULE_API.md Part 7.

The six public spawn-atlas routes now live in modules/uo/, reached only through
the ctx/register surface, with the client half loading as a prebuilt ESM chunk.
All three exit criteria met:

  • zero internal-file imports from the module into core; the built chunk has
    zero bare import specifiers and bundles no React
  • routes.manifest.json AND routes.guards.json are byte-identical
  • /uo/atlas renders from /modules/uo/entry.js under script-src 'self' with
    zero CSP violation reports

729 core tests and 81 module tests pass. Verified end to end against the real
database: the schema fragment replays after core's, onBoot runs the atlas
refresh, and the six API URLs answer unchanged.

Two things the spike changed in the contract:

  • ctx.express / ctx.validator. A module lives outside server/, so Node never
    reaches server/node_modules and require('express') fails outright — the
    server-side twin of the one-React rule, which §2.6 had only for the client.
  • window.__rg.jsxRuntime, so a module can build with the automatic JSX
    runtime its tooling already assumes rather than being forced to classic.

And it confirmed §6.1 empirically: regenerating the OpenAPI spec silently
deleted all 361 lines of the atlas paths with "Swagger-autogen: Success", while
the route manifest kept all six in the same run. That is exactly the
static-analysis-vs-runtime split the fragment merge exists to prevent.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-10 05:29:35 -05:00

2.5 KiB

module-uo — the Phase 1 spike

This branch is evidence, not implementation. spike/module-atlas is cut from edge and is never merged. Phase 2 rebuilds the loader properly, with the installed_modules table, the full state machine and the admin panel behind it; Phase 3 does the real extraction.

What it demonstrates, and the results, are written up in docs/website/MODULE_API.md Part 7. In one line: the six public spawn-atlas routes now live in a module, at byte-identical URLs, with the client half loading as a prebuilt ESM chunk under script-src 'self'.

Reproducing it

# 1. build the module's client chunk (its CI would do this and ship the result)
cd modules/uo/client && npm install && npm run build     # → dist/entry.js

# 2. build core's client
cd ../../../client && npm install && npm run build

# 3. run the server against the local MariaDB
cd ../server && npm start

Then:

  • /uo/atlas and /uo/atlas/lizardman render from the module's chunk.
  • GET /api/v1/public/atlas/* answers exactly as before — npm run routes:manifest -- --check reports the surface unchanged.
  • npm test in server/ (core, 729) and node --test in modules/uo/server/ (module, 81).

dist/entry.js is committed here only because this branch is the evidence for a design decision and a reviewer should be able to inspect the built artifact without a toolchain. A real module publishes it from CI into its release bundle and never commits it.

What in here is not design

Three things are consequences of stopping at six routes, spelled out in MODULE_API.md §7.5:

  1. Core reaches into this module twiceserver/src/router/v1/admin/shardAtlas.controller.js and server/test/atlasController.test.js. The five admin atlas routes sit inside the /shard admin prefix core still owns, so they cannot move until the whole prefix does.
  2. server/utils/visibility.js is a copy of core's utils/shardVisibility.js, which core still needs for the shard routes not yet extracted. Two caches over one table, briefly.
  3. There is no swagger-fragment.json — it needs core's merge helper on the other side, which is Phase 2.

Also note the table names here are shard_*, not uo_*. That is deliberate and grandfathered by an allowlist in the loader: renaming twenty-seven live tables is a data migration this workstream does not do. Every module written after this one carries its id as a table prefix.