feat(guilds): declare a second place on the guild page, for core's forum #12

Merged
whitlocktech merged 2 commits from feature/teams-phase4-forum-slot into edge 2026-08-18 14:17:52 +00:00
4 changed files with 27 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
{
"$comment": "The core this module is proved against. MODULE_API.md §5.3: the frozen-manifest job clones RunicGateway/website at this exact ref, drops this module in as modules/uo and runs CORE's own routeManifest.js — nothing else can answer whether the URLs the module claims are the URLs it actually serves. Pinned rather than tracking `edge` on purpose: core moves for reasons that have nothing to do with this module, and a bump is then a deliberate commit saying which core the module was last proved against, instead of an unexplained red X on someone else's PR. Bump it, regenerate routes.manifest.json, and commit both together.",
"repo": "https://gitea.whitlocktech.com/RunicGateway/website.git",
"ref": "87230c879aa6e9adde3507718aed6bc4e4d86009",
"refName": "edge @ phase 3 slice 4 (website#140)"
"ref": "7ed2ac99838f4bd64e1df324fe4961673648b0e6",
"refName": "edge @ Teams phase 3 (website#152)"
}

View File

@@ -194,6 +194,14 @@ registry.registerExtension(ID, 'player.invite.accepted', InviteGameAccountStep)
// enough; on a core that knows nothing of Teams it simply stays empty.
registry.declareModuleSlot(ID, 'uo.guild.detail')
// A SECOND place on the same page, for core's Team forum (TEAMS.md Part 5). Two
// declarations rather than one, because a slot holds one component and this module
// wants to decide where each of core's two contributions sits on its own page —
// the feed reads as part of the guild's story, the forum is a room you go into.
// Neither knows the other exists, and a core that fills only one leaves the other
// empty.
registry.declareModuleSlot(ID, 'uo.guild.forum')
// `module.json`'s `coreApi` range is checked by the loader before this file is
// ever served, so there is nothing to re-check here. It is logged because a
// mismatch between the core that validated the manifest and the core that

View File

@@ -102,6 +102,11 @@ export default function Guild() {
that does not fill it, or when there is nothing to show. The guild
is named in OUR terms — core maps its own Team from these two. */}
<Slot name="uo.guild.detail" externalId={String(id)} moduleId="uo" />
{/* And the Team forum, in its own place below the feed. Core resolves
who may read it — membership and manual grants are core's rules —
so this module renders the room and never its door policy. */}
<Slot name="uo.guild.forum" externalId={String(id)} moduleId="uo" />
</>
)}
</div>

View File

@@ -209,16 +209,22 @@ it('registers under exactly one module id, matching the manifest', () => {
assert.deepEqual([...owners], [manifest.id])
})
it('declares its own guild-detail slot, for core to fill', () => {
it('declares its own guild slots, for core to fill', () => {
// The inverted direction (TEAMS.md Part 3). Teams are a core primitive with no
// core page: core owns the activity feed and this module owns the word "guild",
// so this module declares the place and core puts the feed in it.
assert.deepEqual([...registered.declaredSlots], ['uo.guild.detail'])
// core page: core owns the activity feed and the forum, this module owns the
// word "guild", so this module declares the places and core puts them in.
//
// TWO slots rather than one because a slot holds one component: stacking the
// feed and the forum into a single fill would take away this module's ability
// to place them separately on its own page.
assert.deepEqual([...registered.declaredSlots], ['uo.guild.detail', 'uo.guild.forum'])
})
it('the declared slot is rendered by the page that owns it', () => {
it('every declared slot is rendered by the page that owns it', () => {
// A slot nothing renders is a slot core fills into the void. Asserted against
// the source rather than the chunk, since the chunk is minified.
const page = fs.readFileSync(path.resolve(HERE, '..', 'src', 'routes', 'public', 'Guild.jsx'), 'utf8')
assert.match(page, /name="uo\.guild\.detail"/)
for (const name of registered.declaredSlots) {
assert.match(page, new RegExp(`name="${name.replace(/\./g, '\.')}"`))
}
})