Phase 6: the UI for the Phase 5 provisioning backend.
- CreateGameAccountForm: reusable game-account form (own username + password),
mapping the sidecar errors (409/429/403/503) to friendly messages. Wired into
GameAccounts (self-serve) — shown alongside the [link flow when the
game_account_signup flag is on (exposed via public settings), so a registered
player can create + link a game account from their portal.
- Admin Invites view (/admin/invites, admin-only): send an invite at a chosen
access level, list invites with status, revoke pending ones. When email isn't
configured the create response's accept link is surfaced to copy manually.
- Public accept page (/invite/:token): validates the invite, sets username +
password (email + role pre-assigned), creates the account at that role and logs
in; for a player invite it then offers the built-in "create game account" step
before the portal. Honeypot-guarded like registration.
- Admin unlink wired into UserDetail via GameAccounts (per-account Unlink button,
confirm + reconcile).
- Backend: expose gameAccountSignup availability in public settings.
Client build clean; server 193/193.
Refs .plans/protocol2-integration.md (Phase 6). Completes the Protocol 2.0/2.1 integration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5: the account-provisioning backend — link-only stays, plus hybrid
self-signup, an admin email-invite tool, and site-side unlink.
- uoLinkClient.createAccount / unlinkAccount (v2). Password is forwarded to the
shard (hashed there) and never stored/logged; the end-user browser IP is passed
for the shard's per-IP cap; actor is stamped server-side.
- Hybrid signup: POST /player/shard/account provisions a game account (its own
username + password) for the signed-in user and mirrors the link locally. Gated
by the new game_account_signup setting AND the shard's own mode (mapped 403/409/
429/400/503). Serves both self-serve signup and the invite-accept game step.
- Email invites: user_invites table (sha256 token hash, single-use, expiring);
invites model + admin CRUD (POST/GET/DELETE /admin/invites, admin-only) +
mailer.sendInvite (falls back to returning the accept link if email is off);
public token-gated accept (GET /auth/invite/:token, POST .../accept) creates the
user at the invite's preset role and logs them in, bypassing the registration
gate. Accept is race-safe (atomic single-use; rolls back the user if it loses).
- Admin unlink: DELETE /admin/users/:id/shard/link/:account (admin-only) + local
mirror drop; account.unlinked ingest reconciles the mirror when a player runs
[unlink in game. account.audit / account.unlinked are logged (admin channel
only — never on the public SSE allowlist).
Tests: invites model (hashing, single-use, expiry, revoke) + account.* ingest
reconcile/visibility. Full suite 193/193; swagger regenerated.
Refs .plans/protocol2-integration.md (Phase 5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First phase of the hero canvas editor (see HERO_EDITOR.md).
- settings.model: add hero_layout to PUBLIC_KEYS so the portal receives it
(corrects the design doc — public settings is a whitelist, not getAll();
hero_layout_draft stays admin-only)
- new HeroElement.jsx: renders one layout element by type (text_block,
buttons, moon, badge, image); absolute % positioning with anchor; shared
by the portal now and the editor canvas later
- MoonDot: optional color override for the hero moon element
- Portal.jsx: parse hero_layout (version-checked, try/catch), render elements
sorted by z; fall back to a DEFAULT_LAYOUT built from the current hero so the
page is byte-for-byte unchanged until staff publish their own
No schema change. Verified: default render matches the old hero; publishing a
hero_layout re-renders the portal; the draft key is not exposed publicly; client
builds; no console errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>