feat(guilds): a UO guild is a Team (Teams cutover 5/6) #16

Merged
whitlocktech merged 22 commits from edge into main 2026-08-19 09:02:40 +00:00
4 changed files with 39 additions and 5 deletions
Showing only changes of commit 990a50b491 - Show all commits

View File

@@ -202,6 +202,13 @@ registry.declareModuleSlot(ID, 'uo.guild.detail')
// empty.
registry.declareModuleSlot(ID, 'uo.guild.forum')
// And a THIRD, at the top of the same page, for core's per-Team notification
// control (TEAMS.md §6.3). Same reasoning as the other two and a different place:
// muting a guild is an action ON this page, so it sits with the page's heading
// rather than after its content. Core resolves whether this viewer is in the
// Team at all — this module neither knows nor asks.
registry.declareModuleSlot(ID, 'uo.guild.header')
// `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

@@ -75,6 +75,13 @@ export default function Guild() {
{data.alliance && ` · ${data.alliance}`}
</p>
{/* A third place for core, up here rather than below the roster: core
puts this guild's notification control in it, and a control that
acts on the page belongs beside the page's title and not after its
content. Empty for a visitor with no membership, and on a core
that fills nothing. */}
<Slot name="uo.guild.header" externalId={String(id)} moduleId="uo" />
{roster.length > 0 && (
<div style={{ overflowX: 'auto', marginTop: 18 }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>

View File

@@ -214,10 +214,16 @@ it('declares its own guild slots, for core to fill', () => {
// 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'])
// THREE slots rather than one because a slot holds one component: stacking the
// feed, the forum and the notification control into a single fill would take
// away this module's ability to place them separately on its own page — and it
// does place them separately, the control above the roster and the other two
// below it.
assert.deepEqual([...registered.declaredSlots], [
'uo.guild.detail',
'uo.guild.forum',
'uo.guild.header',
])
})
it('every declared slot is rendered by the page that owns it', () => {

View File

@@ -317,4 +317,18 @@ async function projectRoster(externalId, members, viewer) {
}
}
module.exports = { getTeams, getTeamMembers, getTeamLeaders, projectRoster, boardIsCurrent }
// Where core should point a link at a guild (MODULE_API 1.6.0, TEAMS.md §6.4).
//
// **Core cannot work this out for itself, and it is not supposed to.** Teams are
// a contract primitive with no core surface — this module owns the guild page,
// because core does not own the word "guild" — so the one thing core needs back
// is where the page it does not own actually lives. A notification email that
// cannot link to the thread it is about is most of the way to useless.
//
// A relative path with `{externalId}` substituted, matching `Guild.jsx`'s route
// (`/uo/guilds/:id`). Core does the substitution and nothing else with it; a
// template naming its own host is refused at registration, which is why this is
// data and not a callback.
const pageUrlTemplate = '/uo/guilds/{externalId}'
module.exports = { getTeams, getTeamMembers, getTeamLeaders, projectRoster, boardIsCurrent, pageUrlTemplate }