All checks were successful
PR checks / checks (pull_request) Successful in 9m9s
PLAN.md §13 phase 4: /features/, /architecture/, /modules/, /integrations/, and /community/ — plus the two scope items the phase table never assigned to anyone. Six decisions taken by the org lead before coding, recorded in PLAN.md §10 as D20-D25: - D20 /features/ is the homepage's list with a `detail` line, not a second list. One data file, two renderings, so they cannot disagree about what exists. - D21 /architecture/ draws reasons, not reference: three new inline SVGs, one per boundary. No endpoint tables, no config keys — those are phase 8's and stay canonical in docs/. - D22 The deliberate absences of §2 become one tagged data file, rendered on the three pages that promise them. - D23 Phase 4 absorbs /community/ (specified in §10 and §14 N3, linked from the header since phase 1, built by no phase) and checkLinks.mjs. - D24 `needsModule`: writing the Teams detail exposed a false claim phase 3 shipped. Teams are module-sourced only — teams.module_id is NOT NULL, there is no create route, sync is gated on providerModuleId() — so the Community group no longer says a bare core does all of it. - D25 The per-capability demo affordance brand.json had promised since phase 2 is a deep link, filled at boot from data-demo-path. checkLinks.mjs reads the built HTML rather than src/, because half these links are assembled from data files and template literals. Its PLANNED_ROUTES list is checked in both directions, so it cannot rot into a permanent exemption. applyBrand.mjs gained a pass that recomputes deep links from their immutable path, making it idempotent and reversible; checkBrand.mjs lifts that pattern out and runs it against the stock markup so the two cannot drift. Both proved against a real mount, in both directions. Fixes a cascade bug the checks could not see: [data-demo-url=''] and a scoped component class are both specificity 0,1,0, so .demo-link's `display` beat the hide rule and twelve links to a nonexistent demo rendered, each resolving to the current page. The rule is now !important. The four diagrams' shared SVG vocabulary moved to src/styles/diagram.css. Verified from a clean checkout: npm ci, all five checks, astro check (0 errors), production build, and a live browser pass at desktop and 390px. Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
7.5 KiB
Plaintext
167 lines
7.5 KiB
Plaintext
---
|
|
import platform from '../../data/platform.json';
|
|
|
|
/**
|
|
* "Where the game stops and the platform starts" — the third of `/architecture/`'s diagrams
|
|
* (D21).
|
|
*
|
|
* The other two draw runtime shapes. This one draws a code boundary, and it is here because
|
|
* it is the claim the whole project rests on: that a community platform can be built once
|
|
* and pointed at any game. An evaluator has every reason to read that as marketing, so the
|
|
* page draws the seam and then says plainly what does and does not prove it — one module
|
|
* exists, the second is a paper exercise, and the exit criterion for calling the contract
|
|
* proven is written down (§2, and the entries `/modules/` renders from `notBuilt.mjs`).
|
|
*
|
|
* The Module API version is read from `platform.json` like every other number on this site
|
|
* (§12). It is the one place a version genuinely belongs in this diagram: the seam is
|
|
* literally a version check, and a module whose declared range does not match refuses to
|
|
* load rather than half-loading.
|
|
*/
|
|
---
|
|
|
|
<section class="page section diagram" id="module-seam">
|
|
<div class="diagram__head">
|
|
<p class="eyebrow">Where the game stops</p>
|
|
<h2>A seam, with a version on it</h2>
|
|
<p class="prose">
|
|
The core site does not know what a shard is, what a guild is, or that Ultima Online
|
|
exists. Everything that does lives in an installable module on the other side of a
|
|
declared interface — which is what makes "put your game on it" a shape rather than a
|
|
slogan.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="diagram__body">
|
|
<div class="diagram__figure">
|
|
<svg viewBox="0 0 380 500" class="flow" aria-hidden="true" focusable="false">
|
|
<!-- Core: what ships in the image, on every deployment, module or not. -->
|
|
<rect class="host" x="8" y="8" width="364" height="186" rx="14" />
|
|
<text class="host-title" x="28" y="42">Runic Gateway core</text>
|
|
<text class="host-sub" x="28" y="62">game-agnostic; the same image everywhere</text>
|
|
|
|
<rect class="node node--self" x="28" y="80" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="46" y="108">Accounts</text>
|
|
|
|
<rect class="node node--self" x="196" y="80" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="214" y="108">Teams</text>
|
|
|
|
<rect class="node node--self" x="28" y="134" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="46" y="162">Wiki and posts</text>
|
|
|
|
<rect class="node node--self" x="196" y="134" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="214" y="162">Admin and API</text>
|
|
|
|
<!-- The seam. Both boundary lines and the label between them: this is the one
|
|
thing in the picture that is neither core nor module. -->
|
|
<path class="boundary" d="M8 224 H372" />
|
|
<text class="seam-label" x="190" y="252" text-anchor="middle">
|
|
Module API {platform.moduleApi}
|
|
</text>
|
|
<path class="boundary" d="M8 272 H372" />
|
|
|
|
<!-- Registers upward; is asked downward. Two arrows, opposite directions, because
|
|
the traffic across a seam is not one-way and drawing it as one-way is what
|
|
makes people think a module is a plugin that only listens. -->
|
|
<path class="spine" d="M120 300 V206" />
|
|
<path class="arrow" d="M120 198 l-6 10 h12 Z" />
|
|
<text class="seam-arrow" x="136" y="216">registers</text>
|
|
|
|
<path class="spine" d="M260 200 V294" />
|
|
<path class="arrow" d="M260 302 l-6 -10 h12 Z" />
|
|
<text class="seam-arrow" x="244" y="290" text-anchor="end">calls</text>
|
|
|
|
<!-- The module: everything that knows a game exists. -->
|
|
<rect class="host" x="8" y="306" width="364" height="186" rx="14" />
|
|
<text class="host-title" x="28" y="340">Game module</text>
|
|
<text class="host-sub" x="28" y="360">one per deployment; UO today</text>
|
|
|
|
<rect class="node" x="28" y="378" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="46" y="406">Routes</text>
|
|
|
|
<rect class="node" x="196" y="378" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="214" y="406">Screens</text>
|
|
|
|
<rect class="node" x="28" y="432" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="46" y="460">Its own tables</text>
|
|
|
|
<rect class="node" x="196" y="432" width="156" height="46" rx="10" />
|
|
<text class="node-title" x="214" y="460">Nav rows</text>
|
|
</svg>
|
|
|
|
<p class="diagram__caption">
|
|
A module declares which versions of the interface it speaks. If that does not match
|
|
what the site offers, it refuses to load and the site comes up without it.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="diagram__notes">
|
|
<section>
|
|
<h3>The module brings its own everything</h3>
|
|
<p>
|
|
Not just screens: its routes, its database tables, its navigation rows, its slice of
|
|
the OpenAPI spec and its own prebuilt client bundle. Installing it is a paste in the
|
|
admin panel or a line in your environment — never a build step, because production
|
|
runs an image you pulled, and an operator who has to compile something has been
|
|
handed a maintenance job rather than a feature.
|
|
</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>Failure is contained by design</h3>
|
|
<p>
|
|
A module that will not load is marked as failed and the site starts without it.
|
|
Disabling one is a kill switch, not a visibility flag — its routes stop answering
|
|
and its live connections close. Uninstalling keeps the data, and destroying the data
|
|
is a separate, deliberate choice made in its own dialog.
|
|
</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>Teams is the shape of the contract</h3>
|
|
<p>
|
|
Core owns the Teams primitive — the roster, the forum, the notifications, the voice
|
|
channel — and does not own the <em>word</em>. A Team cannot be created in core at
|
|
all; it arrives from the module, which is why the UO module calls them guilds and
|
|
builds those pages itself. That is the pattern the whole interface is built on: core
|
|
supplies the machinery, the module supplies the meaning.
|
|
</p>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>What this does not yet prove</h3>
|
|
<p>
|
|
One module exists and it is Ultima Online. A second, for a different game, is a
|
|
written dry-run that was deliberately never implemented — it exists to test whether
|
|
the contract generalises on paper. Until somebody builds the second one, the seam is
|
|
a well-argued design rather than a demonstrated one, and this site says so wherever
|
|
it comes up.
|
|
</p>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
/* The seam label sits between the two boundary rules rather than beside them: it is
|
|
the name of the gap, not an annotation on either side of it. Gold, because it is
|
|
the one contract in the picture. */
|
|
.seam-label {
|
|
fill: var(--gold);
|
|
font-family: var(--sans);
|
|
font-size: 12.5px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* Two words, because two arrows crossing a boundary in opposite directions is
|
|
ambiguous without them — and the ambiguity is the exact misreading this diagram
|
|
exists to prevent, that a module is something core talks at. */
|
|
.seam-arrow {
|
|
fill: var(--dim);
|
|
font-family: var(--sans);
|
|
font-size: 11px;
|
|
font-style: italic;
|
|
}
|
|
</style>
|