feat(guilds): /guild — the module's own chat command #14

Merged
whitlocktech merged 2 commits from feature/teams-phase7-slash-commands into edge 2026-08-19 00:16:13 +00:00
Member

Phase 7 of docs/website/TEAMS.md, this module's half. The first command through api.registerSlashCommands (MODULE_API 1.6.0, §7.1).

Pairs with website#157 (the seam) and docs#161.

/guild, not /team — the org lead's call

Core does not own the word for a Team. That is what deleted its Team pages in phase 3, and it applies just as well to a channel: a core /team would publish core's invented noun beside this module's existing /uo/guilds. So the module owns the vocabulary and therefore the command, and core ships the dispatcher and zero commands.

Nothing in server/commands/guild.command.js knows what Discord is. It is handed an actor and returns an envelope; the same handler would serve a second platform unchanged.

The part that is a security boundary, not presentation

The audience rungs are re-resolved in the handler. A shard whose guilds feature is gated to staff does not become public because the question arrived over Discord. Same shardVisibility config the routes use, and the anonymous case is answered directly rather than by handing viewerLevel a synthetic request — that fall-through into auth.getUserFromRequest is the phase 3 bug.

The provider's own staleness guard is honoured too: a shard that is not connected answers "guild information may be out of date" instead of reporting a board of unknown age as current.

Smaller decisions

  • resolveUserId is exported rather than copied, so "linked" means here exactly what it means on the roster (the shard's asserted web_id or the link table).
  • Matching goes abbreviation → exact name → unique prefix. Players type the abbreviation; two matches asks rather than guesses, because a wrong-guild answer is worse than a question.
  • The board's counts are shown rather than the roster's length — the count is what the shard asserts and is the more current of the two.

The live rig found both fixes in the second commit

Real ServUO + real sidecar + the app with this module installed:

  • The link prompt was offered where linking cannot reach. Gated to staff, the refusal still read "link your account — this shard shows guild information to linked players". Signing in reaches logged_in and linking reaches player; staff and admin are roles an operator grants. Inviting someone to do something that changes nothing is worse than plainly saying no.
  • The list embed titled itself with the hostname (Guilds on localhost:5173). ctx.site carries a base URL and no brand name, so naming the deployment there could only ever print a hostname into a title on the shard's own Discord server.

Walked afterwards against two real guilds: /guild listed both, /guild name:GM returned 10 members / 0 online / 0 linked / leader Seed006C and a link to /uo/guilds/2, and a shard gated to staff refused an anonymous and a linked-player caller while serving a linked admin.

Tests

437 (+10). check:imports clean.


  • AI-assisted: Claude Code (Claude Opus 5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz

Phase 7 of `docs/website/TEAMS.md`, this module's half. The first command through `api.registerSlashCommands` (MODULE_API 1.6.0, §7.1). Pairs with **website#157** (the seam) and **docs#161**. ## `/guild`, not `/team` — the org lead's call Core does not own the word for a Team. That is what deleted its Team pages in phase 3, and it applies just as well to a channel: a core `/team` would publish core's invented noun beside this module's existing `/uo/guilds`. So the module owns the vocabulary and therefore the command, and **core ships the dispatcher and zero commands**. Nothing in `server/commands/guild.command.js` knows what Discord is. It is handed an `actor` and returns an envelope; the same handler would serve a second platform unchanged. ## The part that is a security boundary, not presentation **The audience rungs are re-resolved in the handler.** A shard whose `guilds` feature is gated to staff does not become public because the question arrived over Discord. Same `shardVisibility` config the routes use, and the anonymous case is answered *directly* rather than by handing `viewerLevel` a synthetic request — that fall-through into `auth.getUserFromRequest` is the phase 3 bug. The provider's own staleness guard is honoured too: a shard that is not connected answers "guild information may be out of date" instead of reporting a board of unknown age as current. ## Smaller decisions - **`resolveUserId` is exported rather than copied**, so "linked" means here exactly what it means on the roster (the shard's asserted `web_id` *or* the link table). - **Matching goes abbreviation → exact name → unique prefix.** Players type the abbreviation; two matches asks rather than guesses, because a wrong-guild answer is worse than a question. - The board's counts are shown rather than the roster's length — the count is what the shard asserts and is the more current of the two. ## The live rig found both fixes in the second commit Real ServUO + real sidecar + the app with this module installed: - **The link prompt was offered where linking cannot reach.** Gated to `staff`, the refusal still read "link your account — this shard shows guild information to linked players". Signing in reaches `logged_in` and linking reaches `player`; `staff` and `admin` are roles an operator grants. Inviting someone to do something that changes nothing is worse than plainly saying no. - **The list embed titled itself with the hostname** (`Guilds on localhost:5173`). `ctx.site` carries a base URL and no brand name, so naming the deployment there could only ever print a hostname into a title on the shard's own Discord server. Walked afterwards against two real guilds: `/guild` listed both, `/guild name:GM` returned 10 members / 0 online / 0 linked / leader `Seed006C` and a link to `/uo/guilds/2`, and a shard gated to `staff` refused an anonymous and a linked-player caller while serving a linked admin. ## Tests 437 (+10). `check:imports` clean. --- - [x] AI-assisted: Claude Code (Claude Opus 5) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz
wtclaude added 2 commits 2026-08-19 00:09:52 +00:00
The first command through `api.registerSlashCommands` (MODULE_API 1.6.0,
TEAMS.md §7.1). The definition and the handler both live here; the bot pulls the
definition and runs no line of this module.

`/guild` and not `/team`, deliberately. Core does not own the word for a Team —
that is what deleted its Team pages in phase 3 — so it does not publish the noun
in a channel either. Core ships the dispatcher and zero commands.

The audience rungs are re-resolved in the handler rather than assumed: a shard
that gates guilds to staff does not become public because the question arrived
over Discord. The provider's own staleness guard is honoured too, so a stale
board answers "not connected" instead of reporting what it still holds, and
`resolveUserId` is exported rather than copied so "linked" means here what it
means on the roster.

Co-Authored-By: Claude <noreply@anthropic.com>
fix(guilds): do not offer linking where linking cannot reach
Some checks failed
PR Checks / server-tests (pull_request) Successful in 28s
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / frozen-manifest (pull_request) Failing after 35s
466842c6f2
Found on the live rig, with the shard's guild feature gated to staff: the
refusal still read "link your account — this shard shows guild information to
linked players". Signing in reaches `logged_in` and linking a game account
reaches `player`; `staff` and `admin` are roles an operator grants, and no
amount of linking earns them. Inviting someone to do something that changes
nothing is worse than plainly saying no.

Also drops the host name from the list embed's title. `ctx.site` carries a base
URL and no brand name, so naming the deployment there could only ever mean
printing its hostname into a title on the shard's own Discord server.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 7d0378842b into edge 2026-08-19 00:16:13 +00:00
whitlocktech deleted branch feature/teams-phase7-slash-commands 2026-08-19 00:16:14 +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/Module-uo#14
No description provided.