feat(guilds): a guild detail page, and the slot core puts the feed in
The module's half of the org lead's correction: Teams is the contract, guilds are the presentation, and the presentation is this module's. Adds `/uo/guilds/:id` — the detail view the board never had — with the roster from this module's OWN board, which is the same data it answers core's Team provider from. Reading core's projection of our own answer back would be a round trip through a staler copy of it. The page declares `uo.guild.detail` and core fills it with the Team activity feed. That is the one part of this page core cannot hand over: only core can resolve whether the viewer is inside the Team, and the public/members split on that feed is a security boundary. The guild is named in OUR terms — core maps its own Team from the module id and the external id — so this module never holds core's row id or slug. `TeamOverviewStrip` is deleted with the core Team page it filled. `team.member.row` is not declared here either: the useful thing to put in a roster row is a link to the character behind it, and nothing core could supply identifies one. `GET /public/shard/guilds/:id` backs the page, gated and projected through the same `guilds` feature as the board — so an operator who raises that audience raises this too, and the locked acct/webId fields never survive below admin. A roster is where those appear in bulk, which makes this the endpoint where getting the projection wrong would matter most. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import Shard from './routes/public/Shard.jsx'
|
||||
import ShardActivity from './routes/public/ShardActivity.jsx'
|
||||
import ChampSpawns from './routes/public/ChampSpawns.jsx'
|
||||
import Guilds from './routes/public/Guilds.jsx'
|
||||
import Guild from './routes/public/Guild.jsx'
|
||||
import Governors from './routes/public/Governors.jsx'
|
||||
import Houses from './routes/public/Houses.jsx'
|
||||
import Rules from './routes/public/Rules.jsx'
|
||||
@@ -52,7 +53,6 @@ import PlayerCharacter from './routes/player/PlayerCharacter.jsx'
|
||||
import ShardStatusLink from './components/ShardStatusLink.jsx'
|
||||
import UserShardSections from './routes/admin/UserShardSections.jsx'
|
||||
import InviteGameAccountStep from './components/InviteGameAccountStep.jsx'
|
||||
import TeamOverviewStrip from './components/TeamOverviewStrip.jsx'
|
||||
|
||||
const ID = 'uo'
|
||||
|
||||
@@ -82,6 +82,7 @@ registry.registerRoutes(ID, {
|
||||
{ path: 'shard/activity', element: <ShardActivity /> },
|
||||
{ path: 'champs', element: <ChampSpawns /> },
|
||||
{ path: 'guilds', element: <Guilds /> },
|
||||
{ path: 'guilds/:id', element: <Guild /> },
|
||||
{ path: 'governors', element: <Governors /> },
|
||||
{ path: 'houses', element: <Houses /> },
|
||||
{ path: 'rules', element: <Rules /> },
|
||||
@@ -181,18 +182,17 @@ registry.registerFeatureProvider(ID, ID, useShardFlags)
|
||||
registry.registerExtension(ID, 'site.footer.status', ShardStatusLink)
|
||||
registry.registerExtension(ID, 'admin.users.detail', UserShardSections)
|
||||
registry.registerExtension(ID, 'player.invite.accepted', InviteGameAccountStep)
|
||||
// The fourth, and the first that was never core's: `team.overview` is new in
|
||||
// 1.6.0 and core renders the whole Team page without it (TEAMS.md §3.4). This
|
||||
// adds a live reading beside core's stored one, and renders nothing when it has
|
||||
// nothing true to say.
|
||||
// ── The inverted slot: this module DECLARES, core fills ────────────────────
|
||||
//
|
||||
// `team.member.row` is declared by core and deliberately LEFT UNFILLED. Its
|
||||
// useful contents would be a link to the character behind a roster row, and the
|
||||
// props core can supply do not identify one: the member key and the site account
|
||||
// id are withheld from every public roster (§3.2), so this module would be
|
||||
// guessing from a display name. Filling it with a guess is worse than an empty
|
||||
// cell.
|
||||
registry.registerExtension(ID, 'team.overview', TeamOverviewStrip)
|
||||
// The other three above are core's slots that this module fills. This one is the
|
||||
// reverse (TEAMS.md Part 3): Teams are a core primitive that this module
|
||||
// populates, but core does not own the word "guild" and publishes no Team page of
|
||||
// its own — so the page is ours and core contributes the activity feed to it.
|
||||
//
|
||||
// Declared under this module's own namespace, which core enforces. Core's fill is
|
||||
// applied after every module chunk has evaluated, so declaring it here is early
|
||||
// enough; on a core that knows nothing of Teams it simply stays empty.
|
||||
registry.declareModuleSlot(ID, 'uo.guild.detail')
|
||||
|
||||
// `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
|
||||
|
||||
Reference in New Issue
Block a user