// ── One clan ────────────────────────────────────────────────────────────── // // A first-party Rust clan is a Team (R5), and this is its page. Core owns the // Team — the reconciler, the access rules, the activity feed, the forum — but // not the word "clan", so it publishes no Team page of its own (MODULE_API.md // §3.7a). The page is this module's, and the three parts only core can render // are contributed into places this page names: // // rust.clan.header ← core's `team.notify` (above the roster: an action ON the page) // rust.clan.detail ← core's `team.activity` (the members-only feed, D49) // rust.clan.forum ← core's `team.forum` // // One slot per PLACE, as module-uo does, so core never decides the layout of a // page it does not own. **Every slot may be empty** — a core without Teams, a // deployment with the forum switched off, a clan whose Team core has not created // yet — and the page has to read correctly anyway. That is the phase criterion, // and it is why nothing here says "see below" about something core may not put // below. // // The roster comes from this module's own board, through the same function core // asks when it projects a roster (D48), so the two cannot disagree about who may // look. Below the audience the clan is still described — its name, score and // count are public (D58) — and the roster says who may see it instead. import { useParams, Link } from 'react-router-dom' import { ErrorState, Loading, PageHeader, PublicLayout, Slot, useAsync } from '../../core.js' import Empty from '../../components/Empty.jsx' import { Swatch } from '../../components/Clans.jsx' import { count, day } from '../../lib/format.js' import api from '../../api.js' const ID = 'rust' export default function Clan() { const { externalId } = useParams() const { data, loading, error } = useAsync(() => api.clans.get(externalId), [externalId]) if (loading) { return ( ) } // A mistyped or out-of-date address is not an outage, and must not read as // one — the same rule the server page learned in phase 4. if (error || !data || !data.clan) { const missing = !error || error.status === 404 return ( {!missing && }

Back to the server list

) } const { clan, roster } = data const serverLink = `/rust/servers/${encodeURIComponent(clan.serverId)}?tab=clans` return (

← Clans on {clan.serverName || clan.serverId}

{clan.gone && (

This clan has been disbanded, or has left its server’s clan list. What is shown is the last the site heard.

)}

Members

) } function describe(clan) { const parts = [ , ` ${count(clan.memberCount)} ${clan.memberCount === 1 ? 'member' : 'members'}`, clan.maxMembers ? ` of ${count(clan.maxMembers)}` : '', ` · ${count(clan.score)} points`, clan.founded ? ` · founded ${day(clan.founded)}` : '', ] return {parts} } function Roster({ roster, memberCount, gone }) { if (!roster || !roster.visible) { return } if (roster.members.length === 0) { return gone ? : } return (
    {roster.members.map((m, i) => (
  • {m.name || 'Unknown player'} {m.leader && ( Leader )} {m.role && !m.leader && ( {m.role} )} {/* Inside the roster audience by construction (D48): a viewer who may not see the roster sees no row to hang this on. */} {m.online ? 'online' : ''}
  • ))}
) } /** Why the roster was withheld, in words a visitor can act on. */ function withheld(audience) { if (audience === 'signed_in') return 'Sign in to see who is in this clan.' if (audience === 'public') return 'This site is not showing clan rosters right now.' return 'Only this clan’s own members, with a linked Rust account, and this site’s staff can see who is in it.' }