feat(shard): Protocol 2.0 boards UI — guilds, governors, houses, players-online

Phase 2: the public UI for the four new boards, following the ChampSpawns live
pattern (snapshot via useAsync + merge SSE deltas with useShardFeed).

- Players Online widget (components/PlayersOnline.jsx): total + region breakdown
  rolled up into display buckets (data/regionBuckets.js — the one place to retune
  the grouping); live via presence.online. Placed on the Shard page, replacing the
  static players-online stat tile.
- Guilds (/site/guilds): searchable board of rosters/alliances/leaders with a
  "recently joined" strip from guild.join.
- Governors (/site/governors): one card per city with a placeholder crest
  (data/cityCrests.js — swap for real art without touching components), election
  phase badge + autoPickAt countdown, and an on-demand "past governors" term
  history (the look-back reads the ledger captured in Phase 1). Clean empty state
  when City Loyalty isn't enabled.
- Houses (/site/houses): searchable registry with decay badges; price labelled
  "placement value", not a for-sale flag.
- API client methods + nav links (Guilds / Governors / Houses).

Client build clean (240 modules).

Refs .plans/protocol2-integration.md (Phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 12:33:57 -05:00
parent 080478c4a1
commit e9aa19a83d
10 changed files with 711 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import { useShardFeed } from '../../lib/useShardFeed.js'
import { describe } from '../../lib/shardEvents.js'
import { ago } from '../../lib/format.js'
import { api } from '../../api/client.js'
import PlayersOnline from '../../components/PlayersOnline.jsx'
// ── Gold-supply sparkline ───────────────────────────────────────────────────
function Sparkline({ series }) {
@@ -108,12 +109,16 @@ export default function Shard() {
</section>
{/* Stat tiles */}
<section className="grid-3" style={{ gap: 14, marginBottom: 24 }}>
<Stat value={status?.onlineCount ?? '—'} label="Players online" />
<section className="grid-2" style={{ gap: 14, marginBottom: 24 }}>
<Stat value={gold != null ? `${Number(gold).toLocaleString()}` : '—'} label="Gold supply" />
<Stat value={online ? 'Up' : 'Down'} label="Shard link" />
</section>
{/* Live players-online breakdown (total + region buckets) */}
<div style={{ marginBottom: 24 }}>
<PlayersOnline />
</div>
{/* Staff online — linked staff accounts only, with location */}
<section className="panel" style={{ padding: 20, marginBottom: 24 }}>
<div className="sans" style={{ color: 'var(--accent)', fontSize: '0.7rem', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 12 }}>