fix(modules): core offers a contribution, never a slot name #160

Merged
whitlocktech merged 1 commits from feature/teams-slot-contributions into edge 2026-08-19 06:19:41 +00:00
Member

The inverted slot direction reached exactly one module. Core filled three literal names — uo.guild.detail, uo.guild.forum, uo.guild.header — matched by exact name in applyCoreFills. A second game declaring a place under its own id got an empty page and no error, because "a fill for a slot nobody declared is not an error" is the rule that makes an unknown name invisible. That rule is right; what was wrong is core knowing a slot's name at all.

It also put a module identifier inside core, in three string literals scripts/checkModuleIdentifiers.js masks by construction and could never have caught (§5.2).

Found by the integration kit (Teams phase 11) while writing the chapter that teaches this shape to an audience outside this org — which is what that phase is for.

The change

// module-uo, in its own vocabulary, saying what each place is for
declareModuleSlot(ID, 'uo.guild.detail', { core: 'team.activity' })

// core, naming no module and no slot
offerCoreFill('team.activity', TeamActivityFeed)

CORE_CONTRIBUTIONSteam.activity, team.forum, team.notify — is exported and fixed at build time, so asking for one core does not offer throws at the declaration. The asymmetry with an unfilled slot is deliberate: an unknown contribution is always a typo or a version skew (the module's coreApi range has already been checked), and the failure it would otherwise produce is a page that renders empty forever with nothing logged.

options.core is optional — a slot that asks for nothing stays empty, which is what a module declaring a place it fills itself wants. More than one slot may ask for the same contribution and each gets it: how many places a module wants its feed in is a layout decision on a page core does not own.

Amends MODULE_API 1.6.0 in place rather than adding 1.7.0, the same rule the eighth and ninth members were given — 1.6.0 has only ever been on edge.

Also: the UI kit is nine exports, not eight. Slot made it nine in phase 3 and the comment beside it still said eighth.

Companions

  • RunicGateway/Module-uo#… — the three declarations name their contribution
  • RunicGateway/docs#… — §3.7a, the contribution table, and the kit count

Checks

288 client tests · 1162 server tests · checkModuleIdentifiers OK · client builds.

AI disclosure

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

**The inverted slot direction reached exactly one module.** Core filled three literal names — `uo.guild.detail`, `uo.guild.forum`, `uo.guild.header` — matched by exact name in `applyCoreFills`. A second game declaring a place under its own id got an empty page and **no error**, because *"a fill for a slot nobody declared is not an error"* is the rule that makes an unknown name invisible. That rule is right; what was wrong is core knowing a slot's name at all. It also put a module identifier inside core, in three string literals `scripts/checkModuleIdentifiers.js` masks by construction and could never have caught (§5.2). Found by the integration kit (Teams phase 11) while writing the chapter that teaches this shape to an audience outside this org — which is what that phase is for. ### The change ```js // module-uo, in its own vocabulary, saying what each place is for declareModuleSlot(ID, 'uo.guild.detail', { core: 'team.activity' }) // core, naming no module and no slot offerCoreFill('team.activity', TeamActivityFeed) ``` `CORE_CONTRIBUTIONS` — `team.activity`, `team.forum`, `team.notify` — is exported and fixed at build time, so **asking for one core does not offer throws at the declaration**. The asymmetry with an unfilled slot is deliberate: an unknown contribution is always a typo or a version skew (the module's `coreApi` range has already been checked), and the failure it would otherwise produce is a page that renders empty forever with nothing logged. `options.core` is optional — a slot that asks for nothing stays empty, which is what a module declaring a place it fills itself wants. More than one slot may ask for the same contribution and each gets it: how many places a module wants its feed in is a layout decision on a page core does not own. **Amends MODULE_API 1.6.0 in place rather than adding 1.7.0**, the same rule the eighth and ninth members were given — 1.6.0 has only ever been on `edge`. Also: the UI kit is **nine** exports, not eight. `Slot` made it nine in phase 3 and the comment beside it still said eighth. ### Companions - `RunicGateway/Module-uo#…` — the three declarations name their contribution - `RunicGateway/docs#…` — §3.7a, the contribution table, and the kit count ### Checks 288 client tests · 1162 server tests · `checkModuleIdentifiers` OK · client builds. ### AI disclosure Written with Claude Code (Opus 5). Commits carry the `Co-Authored-By` trailer.
wtclaude added 1 commit 2026-08-19 06:16:41 +00:00
fix(modules): core offers a contribution, never a slot name
All checks were successful
PR Checks / client-build (pull_request) Successful in 46s
PR Checks / server-tests (pull_request) Successful in 2m50s
PR Checks / bot-tests (pull_request) Successful in 9m2s
335d69d122
The inverted slot direction reached exactly one module. Core filled three
literal names - uo.guild.detail, uo.guild.forum, uo.guild.header - matched by
exact name in applyCoreFills, so a second game declaring a place under its own
id got an empty page and no error. "A fill for a slot nobody declared is not an
error" is the rule that made the miss invisible, and it is the right rule; what
was wrong was core knowing a slot's name at all.

It also put a module identifier inside core, in three string literals
scripts/checkModuleIdentifiers.js masks by construction and could never catch.

Found by the integration kit while writing the chapter that teaches this shape
to an audience outside this org - which is what that phase is for.

So the module says WHERE, in its own vocabulary, and WHICH of core's
contributions goes there:

  declareModuleSlot(ID, 'uo.guild.detail', { core: 'team.activity' })

and core offers into the catalogue rather than into a name:

  offerCoreFill('team.activity', TeamActivityFeed)

CORE_CONTRIBUTIONS is exported and fixed at build time, so asking for one core
does not offer THROWS at the declaration. That asymmetry with an unfilled slot
is deliberate: an unknown contribution is always a typo or a version skew - the
module's coreApi range has already been checked - and the failure it would
otherwise produce is a page that renders empty forever with nothing logged.

options.core is optional; a slot that asks for nothing stays empty, which is
what a module declaring a place it fills itself wants. More than one slot may
ask for the same contribution and each gets it: how many places a module wants
its feed in is a layout decision on a page core does not own.

Amends MODULE_API 1.6.0 in place rather than adding 1.7.0 - the same rule the
eighth and ninth members were given, and 1.6.0 has only ever been on edge.

Also: the UI kit is nine exports, not eight. Slot made it nine in phase 3 and
the comment beside it still said eighth.

288 client tests, 1162 server tests.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 48a3e33be4 into edge 2026-08-19 06:19:41 +00:00
whitlocktech deleted branch feature/teams-slot-contributions 2026-08-19 06:19:42 +00:00
Sign in to join this conversation.
No description provided.