feat(kit): the two shapes Teams added, taught and built (Teams phase 11) #6

Merged
whitlocktech merged 3 commits from feature/teams-phase11 into main 2026-08-19 09:03:45 +00:00
Member

Teams phase 11, the last of the bet, and the one that could only merge after the cutover: checkCoreApi is an equality against a core on main, and 1.6.0 did not exist there until RunicGateway/website#161 landed. The pin move rides in this PR, as planned.

One sentence in the book was wrong

Chapter 2 said, of extension slots: "Only core may declare a slot; a module may only fill one". 1.6.0 inverted exactly that. It is not a stale detail — a new game's module cannot implement Teams at all without the inverted direction, so it is the shape the reader needs and did not have.

The two shapes, taught and built

  • The inverted slot — a new "Slots go the other way too" section: why the direction has to invert (core owns the Team, not the word for one), the namespace rule, one slot per PLACE, the optional { core } naming which contribution goes there, and why asking for one core does not offer throws when almost everything else in that registry fails open.
  • registerTeamProvider, in "Becoming the source of Teams" — the first registration where core calls you and waits, which is where every rule in it comes from: the envelope, the ten-second budget, refusing as a normal answer, and the mistake worth naming (answering { ok: true, teams: [] } because the game is unreachable, which core reads as authoritative and acts on). projectRoster gets its own treatment as the exception that fails closed; pageUrlTemplate is the footnote it was meant to be.

The template grew a working version of each — model/clans/, its own /clans routes (deliberately not /teams, which is core's and which the loader would refuse), a clan list and a clan page declaring three slots. 47 server tests, 20 client. The purge test finally proves something: two of the three tables are now a parent and its child.

What this phase does not do: enumerate the contract. The kit teaches one path end to end and links out; it has never mentioned three pre-Teams registrations and that is the design.

It found a defect in core, which is what it is for

Core's inverted-slot fills named three of module-uo's slots literally, so the direction worked for that one module and silently did nothing for any other game — an empty page, no error, nothing logged. Fixed in website#160 / Module-uo#15 / docs#165 before this chapter could teach it, and amended into 1.6.0 in place.

And it was walked on a real core

The template was installed into a running website — real MariaDB, the real loader, a browser. Core reconciled two Teams out of the provider on first boot, /public/teams/<slug>/members answered projected: true, and the clan page rendered core's activity feed and forum in slots declared by a module named examplegame. module-uo's guild page was walked on the same core and is unchanged.

The walk found one thing no test could: PageHeader takes lead, not subtitle, and React drops an unknown prop in silence — so every page built from this template had been rendering its heading with nothing under it since the template was written. Fixed on all three pages, and the chapter now says to check prop names against §3.4 rather than guessing them, beside the paragraph about shell that exists for the same reason.

Checks

checkLinks · checkRenameSites · checkChapterPaths · checkCoreApi --core against 963d734 (1.6.0 ✓) · the two script suites · template check:imports, check:swagger, build, check:externals, and both test suites.

AI disclosure

Written with Claude Code (Opus 5). Commits carry the Co-Authored-By trailer.

**Teams phase 11**, the last of the bet, and the one that could only merge after the cutover: `checkCoreApi` is an equality against a core on `main`, and 1.6.0 did not exist there until `RunicGateway/website#161` landed. The pin move rides in this PR, as planned. ### One sentence in the book was wrong Chapter 2 said, of extension slots: *"Only core may declare a slot; a module may only fill one"*. 1.6.0 inverted exactly that. It is not a stale detail — **a new game's module cannot implement Teams at all without the inverted direction**, so it is the shape the reader needs and did not have. ### The two shapes, taught and built - **The inverted slot** — a new *"Slots go the other way too"* section: why the direction has to invert (core owns the Team, not the word for one), the namespace rule, one slot per PLACE, the optional `{ core }` naming which contribution goes there, and why asking for one core does not offer *throws* when almost everything else in that registry fails open. - **`registerTeamProvider`**, in *"Becoming the source of Teams"* — the first registration where core calls **you** and waits, which is where every rule in it comes from: the envelope, the ten-second budget, refusing as a normal answer, and the mistake worth naming (answering `{ ok: true, teams: [] }` because the game is unreachable, which core reads as authoritative and acts on). `projectRoster` gets its own treatment as the exception that fails **closed**; `pageUrlTemplate` is the footnote it was meant to be. The template grew a working version of each — `model/clans/`, its own `/clans` routes (deliberately not `/teams`, which is core's and which the loader would refuse), a clan list and a clan page declaring three slots. 47 server tests, 20 client. The purge test finally proves something: two of the three tables are now a parent and its child. **What this phase does not do: enumerate the contract.** The kit teaches one path end to end and links out; it has never mentioned three pre-Teams registrations and that is the design. ### It found a defect in core, which is what it is for Core's inverted-slot fills named three of `module-uo`'s slots **literally**, so the direction worked for that one module and silently did nothing for any other game — an empty page, no error, nothing logged. Fixed in `website#160` / `Module-uo#15` / `docs#165` before this chapter could teach it, and amended into 1.6.0 in place. ### And it was walked on a real core The template was installed into a running website — real MariaDB, the real loader, a browser. Core reconciled two Teams out of the provider on first boot, `/public/teams/<slug>/members` answered `projected: true`, and the clan page rendered core's activity feed and forum in slots declared by a module named `examplegame`. `module-uo`'s guild page was walked on the same core and is unchanged. The walk found one thing no test could: **`PageHeader` takes `lead`, not `subtitle`**, and React drops an unknown prop in silence — so every page built from this template had been rendering its heading with nothing under it since the template was written. Fixed on all three pages, and the chapter now says to check prop names against §3.4 rather than guessing them, beside the paragraph about `shell` that exists for the same reason. ### Checks `checkLinks` · `checkRenameSites` · `checkChapterPaths` · `checkCoreApi --core` against `963d734` (1.6.0 ✓) · the two script suites · template `check:imports`, `check:swagger`, `build`, `check:externals`, and both test suites. ### AI disclosure Written with Claude Code (Opus 5). Commits carry the `Co-Authored-By` trailer.
wtclaude added 3 commits 2026-08-19 09:02:51 +00:00
MODULE_API 1.6.0 expands the contract this book teaches against, so the book
owes two shapes and one correction. Chapter 2 gains both and the template grows
a working version of each, because a reader following a snippet has no way to
find out whether it runs.

ONE SENTENCE WAS WRONG. Chapter 2 said, of extension slots, "Only core may
declare a slot; a module may only fill one". 1.6.0 inverted exactly that: a
module declares a place on its OWN page and core fills it. That is not a stale
detail - a new game's module cannot implement Teams at all without the inverted
direction, so it is the shape the reader needs and did not have.

THE TWO SHAPES

- The inverted slot. A new "Slots go the other way too" section: why the
  direction has to invert (core owns the Team, not the word for one), the
  namespace rule, one slot per PLACE, the optional { core } naming which of
  core's three contributions goes there, and why asking for one core does not
  offer throws when almost everything else in that registry fails open.

- registerTeamProvider, in "Becoming the source of Teams". The first
  registration where core calls YOU and waits, which is where every rule in it
  comes from: the envelope, the ten-second budget, refusing as a normal answer,
  and the one mistake worth naming - answering with an empty list because the
  game is unreachable, which core reads as authoritative and acts on.
  projectRoster gets its own treatment because it is the exception that fails
  CLOSED. pageUrlTemplate is a footnote beside it, as intended.

WHAT THE TEMPLATE GREW

model/clans/ - the provider over two tables, with the guards that matter: an
unreachable game refuses rather than reporting no clans, an empty roster is
refused unless the game says the clan is empty (which is why the schema keeps a
member count the rows cannot supply), and the audience rule lives in one file
that both projectRoster and the module's own page consult, because a second copy
drifts in the direction that publishes what core is withholding.

Its own /clans routes, deliberately not /teams - core mounts that itself, and
the loader would refuse the collision. A clan list page and a clan page that
declares three slots for core.

12 provider tests and three registration tests, 47 server and 20 client in
total. The purge test finally proves something: two of the three tables are now
a parent and its child.

WHAT IT DOES NOT DO. Enumerate the contract. The kit teaches one path end to end
and links out; it has never mentioned three pre-Teams registrations and that is
the design, not a gap.

FOUND WHILE WRITING IT: core filled three literal uo.guild.* slot names, so the
inverted direction reached exactly one module and every other game's page came
up empty with nothing logged. Fixed in website#160 / Module-uo#15 / docs#165
before this chapter could teach it - which is what this phase is for.

The ci/core-ref.json pin moves in a later commit on this branch: checkCoreApi is
an equality against a core on main, and 1.6.0 does not reach main until the
cutover.

Co-Authored-By: Claude <noreply@anthropic.com>
The template was built into a running core - MariaDB, the real loader, a browser
- and both halves of the walk passed: core reconciled two Teams out of the
provider on the first boot, /public/teams/<slug>/members came back with
projected:true, and the clan page rendered core's activity feed and forum in the
two slots this module declared. That last one is the whole point of the phase: a
module whose id is not "uo" now gets core's Team content, which is what
website#160 fixed. module-uo's own guild page was walked on the same core and is
unchanged.

Two things the walk found, both of the kind only a browser can:

PageHeader takes `lead`, not `subtitle`. The template has been passing subtitle
since it was written, and an unknown prop on a React component is silently
dropped - so every page built from this template rendered its heading with
nothing under it, on a site where every core page has a line there. Nothing warns
anywhere. Fixed on all three pages, and chapter 2 now says to check prop names
against 3.4 rather than guessing them, beside the paragraph about `shell` that
exists for exactly the same reason.

"1 members" on the clan list.

Co-Authored-By: Claude <noreply@anthropic.com>
ci(core-ref): pin to the Teams cutover, where 1.6.0 reached main
All checks were successful
PR Checks / prose (pull_request) Successful in 11s
PR Checks / template (pull_request) Successful in 36s
1c7d6151c7
The two mechanical lines the phase owed. template/module.json already declares
^1.6.0; this is the sha that makes checkCoreApi agree with it, which it could
not do until the cutover landed - the check is an equality against a core on
main, and 1.6.0 had only ever been on edge.

The `why` block records what the check bought this time rather than just what it
is for. Writing the chapters against 1.6.0 is what found that core's inverted
slot fills named three of module-uo's slots literally, so the direction worked
for that one module and silently did nothing for any other game. A book written
for an audience outside this org is exactly the instrument that finds that, and
it was fixed in core before this pin moved.

Also: the pin skipped the Teams `edge` entirely. The kit is written against what
shipped, never against what is in flight.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 622abe9ed5 into main 2026-08-19 09:03:45 +00:00
whitlocktech deleted branch feature/teams-phase11 2026-08-19 09:03:47 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Integration-kit#6
No description provided.