Design notes
Design notes and current-status records for the live room — a chat where real people talk with a cast of AI personas, voiced by one model. Four sections: what it does (Product), how it's built (Architecture), how its off-spec output is kept honest (Harness), and how it looks (Front end) — plus a legacy shelf of superseded notes and frozen design specimens. Kept in step with the latest committed prototype.
Current functions
A user's-eye view of everything the live room does today — rooms & cast, the confer-then-speak panel, the room's furniture for play (a fair shared die, the sealed circle, a ballot nobody can count early, the wall clock, sealed notes, the pinned board), multi-human rooms (invite people, roles, private history), invite/retire personas, the Character vividness dial, the dictating composer (browser-direct speech-to-text), a multi-language UI (English + 简体中文), message actions (long-press to select, react, reply, forward, delete), rich rendering + artifacts, the Notebook, notifications, the cost meter. Kept current with the app.
看见 / Seen
New. An on-demand read that lands in your Notes tab and opens to a fuller page — a batch of chats + saved lines distilled into something you'd actually keep. A graphic Card (the preview) opens to one long read: two honest bars + a warm mirror in several sections, each showing why it read you that way. Signed by a voice from your own room (梁宁). Now live in the app (a persona writes you the piece in their own form); this page is the original look-and-feel study.
Room dials
Now one creation-time control: Character vividness — how vividly each persona plays itself (True to life → Larger than life, 3 notches), baked into the cached prefix. The old Temperament (panel-debate) dial was retired: conflict is the floor producer's call now — read from the room, steered in plain language, with Cues to teach it. Live in the new-chat modal.
Roadmap
The explored, agreed-direction features from idea.md: the floor producer (✓ shipped — per-turn staging, rhythm for the megaprompt room), room dispatches (✓ shipped — the @Web Search brief), and listen mode (accent-bucket TTS, never voice cloning; media never touches the box). The reasoning behind each, kept until built.
Proven · Better · New
The prototype audited through Mark Pincus's framework (Life at the Speed of Play, ch. 4): we're building in a platform shift, where Proven + Better alone can win — yet running the playbook inverted (≈7 New ideas live, 13 proven chat mechanics named — 8 shipped, 5 open). The thirteen Proven gaps each get a phone-frame mockup (shipped cards fold to a header) — push notifications (Web Push · lock-screen) (✓) · voice bubbles · quote-reply (✓) · reactions (✓) · images · discovery · in-chat search · the long-press action bar (✓) · DMs (human↔human, personas invitable later) (✓) · the dismissal-grammar sweep (muscle memory: ×/back/Done where each platform puts them) (✓) · user profiles (avatar · about line · the contact page) (✓) · the emoji library (app-rendered like WhatsApp's own set — picker · text pass · jumbo) (✓) · contacts (the friend layer over the user pool — request + accept, WeChat's ceremony on WhatsApp's substrate); the Better ledger (3 real, 6 mislabeled as New); and the one New worth declaring: humans + personas at one table, measured Zynga-style.
Cues
The user-facing surface of the floor producer: contextual suggestion chips that teach you to steer the panel in plain language — "have them disagree", "push me on this" — then fade as you learn. Timed to the read-my-mind moment (a pause, a delete, a scroll-back), with the content drawn from the producer's own read of the turn. Resolves the dials question — character intensity stays a dial, while debate & challenge become the producer reading intent plus a Cue to teach you. A concept note.
The poll
The ballot, rebuilt from an owner brief: the card belongs to whoever opened it, each voter's face sits on the option they picked with a bar behind it, and a waiting for row names who is still out. What makes showing all that safe is the reveal gate — the server hands nobody the standings until they have voted themselves, so 「—」 on a count is a refusal to answer where 「0」 would be an answer, and no number you can see could have moved you. Plus multi-choice, where the picks legitimately sum past the people. Deliberately absent: a leader star — the server states counts, the room decides what wins.
Toolbox UI — field study
How mobile chat-based game apps put tools in the chat window — verified across Telegram dice, Discord components, WhatsApp polls, Foundry VTT, D&D Beyond, Among Us, Jackbox (25 claims, 3-vote adversarial verification). The headline: tools live in the stream as interactive cards; no verified app ships a pinned HUD stack — which is exactly what our four-strip dock became. Six ranked field laws, the Chinese 狼人杀/剧本杀 leads (labeled unverified), a gap analysis against our six tools, and five redesign directions: the chip strip, the live card that rides the stream, act-now-is-the-thing-itself, ceremony-only-at-peak-moments, and the earned takeover.
Toolbox — build plan
The execution plan for the orthogonal toolbox: seven self-contained step contracts — T1 clock · T2 tally · T3 the deal · T4 touch drawer · T5 cards (the hinge) · T6 floor · T7 the door + outcome bridge — each written so a cold CC session (Opus 4.8) can ship it alone: the house laws, the rituals, the testing recipe, data models, grammar, acceptance batteries, explicit don'ts, and a paste-ready session opener per step. The werewolf card is T7's finale, composing all seven. Status pills + findings log update in the same commit as each ship.
The orthogonal toolbox
The owner's re-architecture after a week of live play: the six moves are the product's face, but underneath sit six mechanisms — the randomizer (a draw from a distribution, budget default 1), the state recorder (Currently + Next step), the safe (sealed commits, timestamp-proven), the checklist (condition → next step: all voted · all rolled · all spoke · N spoke), the clock (standalone wall-clock), and the floor (mute/unmute) — plus the room itself as the visibility scope (the werewolf den is a temp room, not a whisper). One law learned the hard way: the mechanism states facts; the room's contract owns meaning. Includes the werewolf trace, the composition proofs (the circle and the collect-roll are the same shape — every round game is one loop), the face-down deal (deck × safe), the mode-vs-composition line, the two-layer architecture (precoded mechanisms · on-the-fly orchestration: invoke, never construct), the completeness argument (tally · anonymous reveal · ledger), and ruling ⑧: the tools are the room's, not the persona's.
Host moves — build plan
The execution tracker for the host-moves platform: eleven owner-triggered steps with per-step acceptance tests, the furniture lane feeding in, and status pills flipped in the same change as each ship. Step 1 (host @-mentions + the self-mention pill) shipped v536–v537; step 2 (host reactions) in test.
Host moves
The target beyond chat: rooms that host structured sessions — mock interviews, book clubs with the author, coached dates, facilitated brainstorms, game nights. The platform ships six fixed moves — the capabilities of a good human host: the board, the note (→ the envelope ritual), the aside, the go-around, the follow-up, server truth — and scenarios ship as cards (skills for the room: procedure + enabled moves, loaded per-session, persona × card orthogonal), never code. Humans learn nothing: the persona operates every move. Includes the furniture principle (why fixed beats ephemeral: familiarity, social safety, credible neutrality, polish), the two-closed-axes basis argument for "is six enough", the furniture-corner 2×2 vs chatbots and Claude Code, the games shelf with five seed cards, and the build as two seams (the wildcard router + the turn gate) and a die. Grew out of the game-tools study, same day. A concept note.
Notifications
Live in the app (v401→v433). Every event writes a durable notices row, mirrored live over the existing per-user SSE stream — a toast on whatever tab you're on, a dot on ≡ (count in the Me sheet), and the notification centre grown from the Vibes message list into one mixed-kind inbox, all sharing one compact form (small avatar · byline name · action · CTA). Three kinds: build done / failed and room invite — and build done is a MESSAGE from the persona (v424): the build lands, the server opens her greenroom (a private 1:1) and she speaks first; that bubble is the notice, with a filled Reply. It's an inbox not a log — collapse-key coalescing, revoke on the counter-event, read-on-use; tasks vs records. One noticeSpec module (v429) now defines each kind once and drives both surfaces, under a shared toast duration (v430) and contrast (v431) norm. Plus the mutex-aware Confirm that unlocks in place, a "you can leave — we'll notify you" building page with a breathing Studio-tab dot, and an admin test fixture (no LLM spend). And since v489 a fourth surface — lock-screen Web Push (standard VAPID, guarded so it stays off where pywebpush/keys are absent): the same rail events plus a message-while-away bubble, mirrored to the lock screen where the browser's push service reaches (Android/desktop Chrome & Firefox; iOS 16.4+ once installed), suppressed for the room you're viewing and coalesced per room. A mainland device that can't reach Google's push service degrades silently — the in-app rail still has everything.
Feedback widget
How we measure the floor producer with no user base and PII-heavy conversations: collect the signal in-product — a per-turn thumb + lever-mapped tags (too long · too soft · no real debate · wrong person) + a note, plus a ~20-turn check-in survey. Runs under three blind arms (no-FP / v0 / v1), and logs each tag with the staging that produced it (the seed of a trained policy) — while shipping real value to the user. Shipped and live; the first piece of v1.
Smart panel selector
Shipped 2026-06-18 — kept as the interactive mockup of roadmap §5: describe your situation and the room curates a panel you can edit, instead of browsing the whole library. Removable rows with the reason on the right, three panels a tap apart, hand-pick reusing today's library dialog, and the dials folded behind Advanced. Phone-first, themed, with the project's real persona roster.
Studio
Built · live at /builder. "Build a persona" as an app function: name a figure (or describe one), give one identity-confirmation tap, and the machine runs unattended — gather with verbatim VERIFY, curate, author, mechanical checks, two cold auditors — and it lands in your Studio. One machine, not three build types: a referent switch (real · character · invented), one evidence pool (web + your sources, different axes), a coverage meter, a system-set imagination dial (faithful · extended · free), and a provenance made-from bar. The 11-stage spine collapses to level-one progress (Identity lock → Built) for regular users; the Studio home lists your builds with soft delete + Restore. Folds together the four earlier builder notes into one current-implementation record.
Persona visibility
Settled + built 2026-07-13 (v396) — the one model for who sees and uses every persona: 2 axes × 2 values (private | public · alive | deleted) + immutable created_by, five verbs (build · Delete/Restore · clone · promote · demote), one predicate (may_discover) every discovery surface calls, and one invariant — an admin never raises the visibility of someone else's persona; promotion crosses ownership by copy (clone → test → promote in place, identity kept). Shipped: the smart-suggester leak closed (it read the library unfiltered — and so did the Vibes brew), promote ownership-guarded, take-off repo-only, Delete-requires-private + Restore with a Studio Deleted section, the persona-page room grant, admins keep demoted personas in their picker (the staging shelf). Norms pinned: seated = shared, no true deletion + the erasure carve-out, room-level moderation. Clone shipped too (v397) — "Clone to my Studio" in the console: fresh slug + ULID, born private, lineage credit on the card; publishing another user's work is now clone → test → promote, exactly as designed. v398 closed the last gaps: /api/config strictly filtered (no hidden cards in a non-admin payload — the devtools soft leak), seated personas ride the room's own /state cards (the grant made concrete), and clones get a credit anonymize (console "Remove credit" — the dissociation lever). v399, from a live hand-test hole: the seating door — a member could start a fresh private chat with a granted private persona from its profile page; now every seating endpoint (create · invite · moment-join) requires each slug be discoverable by the seater, and the granted page's chat door greys out with "This is a private persona" (v400) — an explained lock, not a vanished button. Nothing from the spec remains open.
Vibes content engine
Design 2026-07-07 · extractor shipped — how the cast's 朋友圈 feed (famous masters and user-built personas alike) gets its posts in production, with no hand-authoring: DeepSeek writes, the system curates. Rules sorted by the problem they solve — taste lives in a ~230-word English prompt + exemplars; memory (the used-anchor ledger), distribution (a dice-roll composer) and verification (a standby Gemini judge) live in code around the model. Step 1, the persona extractor, is live and validated across all tiers — famous (anchor mining), supplied-corpus and zero-priors builder personas (31/31 verbatim cites). Plus the presentation side: a WeChat-Moments look on a 今日头条 drip, one shared room per moment.
Deployment topology
How traffic reaches the box from mainland China: grey-cloud DNS to a Singapore Ali ECS instance (Hong Kong is a trap — Anthropic blocks it), Caddy + TLS on the box, and the outbound path to the Anthropic API. The three flows — DNS, the direct data path, and the API call.
Function map
How the running system is wired: the three tiers (browser SPA · one server process · SQLite + flat files + LLM), a message's journey from send to reply (gate → queue → batched drain → tool-output → SSE), the two channels that never cross, the data model, and a file-by-file map of which file owns what.
Megaprompt anatomy
The exact payload behind one panel reply, dissected byte by byte — the production room 战队CW-LDL's last round (王强 + Plato + two humans), pulled from the box and colour-coded by who wrote every segment: the spec, run_room's scaffolding, two persona files, the humans' keystrokes, the floor producer's whisper, one web dispatch, the panel's own memory. Punchline: the humans typed 2.7% of what the model reads — for $0.0004 a round.
Prompt caching
How the room reuses the model's cached prefix: built front-to-back, what keeps vs busts it when a voice is added or retired mid-room, and the §9 compaction plan. The mechanic — its economics live in Cache TTL.
Cache TTL economics
A measured ledger: on a deliberative human room the 1-hour TTL default runs ~58% cheaper than the 5-minute window ($1.78 → $0.75). Why 1h is the default, and where the boundary flips.
Dev loop
How a change ships: built on localhost:8011 → I self-test (simulate real users; DeepSeek cheap, Sonnet sparingly) → you test → push to the SG box. The 8011 dev-data norm, the service-worker gotcha, the smoketest, and the batch-deploy rule.
Harness
One model call now confers, speaks, and hands over docs & charts at once — so the ways its output arrives off-spec multiply, and anything that trusts the format breaks. The harness is the chain that catches it: a degrade-never-delete decode → heal → type → attribute → store on the server, plus a degrade-then-recover render on the client. P0–P3 shipped & deployed, then production-validated — the before→after map, the probe battery, and the surprise the live traffic sprang (the fallback path is the main path).
QA test run
A live pass on real rooms: the user×AI cardinality matrix (1/N humans × 1/N personas — all four hold, multi-human addressing works), artifacts (Mermaid lifts to a pane card + link and survives the floor; long markdown stays inline; LaTeX is inline KaTeX), and a full red-team of the floor producer — parser, both injection hops, the turn-decode, and client XSS. The headline: v1f (Flash) was prompt-injectable (it staged the panel to parrot "just say 42") — now closed, the panel hardened to resist poisoned directives, the lint made language-agnostic with no second LLM, and the artifact render verified XSS-clean. Every breach found is fixed and re-tested; kept fixtures listed.
Conversation quality
The first big production exam: all 80 live rooms pulled, the 39 multi-host conversations scored on a rubric built from real user feedback — a mechanical pass + an LLM judge reading every transcript + users' own in-room complaints. The verdict: the panel is too long (median reply ~314 words), too agreeable (40% of non-lead bubbles add no angle), and the extreme dial delivers a third of the conflict it promises — a staging problem, not a voice one. The build spec for roadmap §1, the floor producer.
Floor producer
The per-turn director that sets staging — who speaks, how long, what to focus on, how hard to push — never the content. Built on one idea: a trusted function f reads a rich signal vector and emits the staging; no mode taxonomy in the path. v0 (deterministic) built + A/B'd; v1 the smart f (v1p / v1f) shipped to production (2026-07-03, v1p default). Now with the v1p quality study folded in: 22 rooms · 122 turns · 6 languages, blind-judged — v1p meets user intent on five of six dims (intent-fit 4.76/5, chorus 0.16 vs a 0.40 baseline, silence that scales, a lead rotating in 85%), evenly across languages, on 100% of turns. Two debts: soft length (the opening overruns) and ~3s latency (earned on substance, a poor trade on trivia). v2 memory + v3 a trained policy sketched.
Seven steerings
The review verdict on the collaborator branch ui/floorproducer — seven behaviour rules, all about how a turn ends, sorted into 3 bug fixes (never a bare no · joke back · the room never says goodbye first, + the wind-down stake reclaimed from #7), 2 insurance (drop-it is final · arguers commit), 1 new capability (say what they couldn't say — the mirror), and 1 parked (the every-turn open ending — waits for the time trigger + push, code not prose). The six now ship as v2 "manners": a clean-slate ~13-line re-authoring in two variant prompt files, stamped per room at creation (fork inherits; old rooms stay v1), console → Settings picks the default for new chats — judged on return rate. Live probes are the next gate.
Systemic check (v349)
One pass over the whole prototype — the routine safety net (smoketest PASS), then the four asked-for dimensions. Performance: the one slow path is the cold first open (1.76 s of shell wire from China) plus two disk-scanning endpoints and three scaling time-bombs. Language: 8 of 10 core concepts carry two-plus names — the new-chat picker says character · cast · panel in a single flow. UI reuse: ~8 dialog scaffolds, ~11 close-× treatments (top 10–24 px / right 6–28 px), the same button under 3 names in 3 files — because there are no shared tokens. Formats: 4 relative-time formatters, faux-bold 700, dark-mode bypasses. All of it distilled into 38 numbered fix points — tap rows to pick, the tray collects serials, then say “fix #2 #7 #22”. Outcome: all 38 fixed and deployed (v350–v374 — the terminology canon, the theme.css/fmt.js shared layer, the perf batch, one format voice).
Room language
Every room has a target language — detected from the Smart-selector's Describe box, or set in more settings. The goal: every persona speaks it, while staying themselves. Measured: 90% overall, but the misses are concentrated — strongly language-anchored figures (Confucius) and hard targets (Japanese). The plan, in two layers: stop the prompt contradicting itself (separate voice from language, drop the schema's "their own language"), then a translate-on-divergence net — a slipped line shown original ─ hairline ─ translation, persisted with the message (saveable to Notes, with go-to-line), while the model's memory keeps the target. Layers A + B shipped & deployed to production (Japanese opening 1/4→4/4; Confucius speaks French; the translate net persists + is saveable with go-to-line).
Skeleton
The running app, stripped to UI only — and it opens in a browser. Generated from lib/room-ui.html by scripts/build_skeleton.py: ~2,900 lines of the app's own stylesheet and ~840 of its markup kept verbatim, ~11,900 lines of behaviour dropped. No network, no auth, no back-stack — but every colour, radius, font and layout is the real one, because it is the real one. The app's own chrome navigates it — tapping a real button opens the layer it opens — with a bar along the bottom as the fallback into any of the 31 surfaces, each deep-linkable (#new-chat, #reactions). Plus inspect mode (every component stamped with the selector you'd grep for) and a states gallery: each component in the states it can be in, the half a walkthrough can't show you. Cross 760px to watch the dismissal grammar switch. Rebuild after any UI change — one command, one second.
UI Kit
The companion index: every module named, with the one thing about it that isn't obvious from looking — why one glyph stands for a whole group in the chat list, why the emoji panel is a keyboard-space occupant, why your own bubble has no colour bar. Each card carries the app's real selectors (grep them) and deep-links into that surface in the skeleton. It deliberately draws nothing: the first version hand-reproduced components and got three of them wrong, so the drawing moved to the generated file and this page kept only what a generator can't produce.
Lessons
What the gap 10 build actually taught, in eleven lessons. The expensive parts were never the code: a test that skips the mechanism under test passes however broken that mechanism is; a maintained list is the wrong shape for a universal rule (it failed three separate times); a registered layer you cannot see is worse than an unregistered one. Plus when to stop — the back-gesture slide turned out to be the browser's, unfixable, and proving that was cheaper than a fourth attempt. Grouped by cause: only one of twelve versions was a feature.
Style Guide
Every part of the running chat, named and shown in its current form — the egg, the dispatch card, the Notebook, the seat colours, the skeleton, the confer capsule, the infinity button. Each card renders the real component in all three themes — a living reference, kept in sync every ship. Current as of live v422.
Console redesign
The admin console's System (a five-job junk drawer) and Models pages regrouped into three single-purpose tabs: Status (read-only health), Models (every model role — panel, both floor producers, dispatch, curator — in one place), and Settings (the writable knobs + a grouped visibility grid: Costs · Room setting · Feedback). A faithful, interactive mock in all three themes; the live console is unchanged. A proposal.
The virtual keyboard
The nine-round war (v469–v477) over the on-screen keyboard: bubbles covered, an iOS gap, the page-shove flash. Three separate root causes, one mental model — Android resizes, iOS pans — and the final architecture: one viewport-meta key for Android, and on iOS a measured pre-shrink + programmatic-focus tap routing so Safari never has a reason to pan. The seven traps that each cost a round, and the #kbdebug regression recipe.
Text selection on touch
The ten-round build (v490–v499) of the double-tap text sheet — WeChat's 双击 → full-screen selectable text to Save or Copy. Why it's a dark corner: the OS's own selection UI (Android's handles, iOS's callout, Chrome's search bar) can't be styled or hidden while a native selection exists — so the only fix is to give it up entirely (user-select:none) and rebuild highlight, handles, word-select and hit-testing yourself. Plus the iOS killer: caretRangeFromPoint returns null under user-select:none, so you hit-test by measurement. The device is the only truth; the emulator lies about touch.
The dismissal sweep
Where every layer's close sits, and what the back gesture does to it — one card per dismissible surface, classified into the four types a phone already teaches: a page goes back top-left, a task dialog cancels left and commits right, only overlays close top-right, sheets swipe down. Derived from registerBackLayers() + the shared .dialog-x scaffold: 4 moved · 8 stamped · 23 already compliant, all opt-in and phone-only so desktop came out unchanged. Plus the gesture half — the iOS horizontal reveal traced to one under-scoped CSS rule of our own, one Esc handler across 24 uncovered layers, and a back-gesture dead zone in the Studio iframe under a destructive confirm (recorded, deferred). The standing record of which dialog is which type.
Rendering & speed
To our users we're a chat app, judged against ChatGPT · Gemini · Claude. How the best apps render output and feel fast, and a grounded plan for ours: the four-stage pipeline (markdown · KaTeX · syntax-highlight · sanitize), the $ math-vs-money fix, never exposing <speak>, and the one big speed gap — streaming (TTFT). Steps 1–2 shipped to production; gap matrix + roadmap inside.
Perceived speed & the slow-network playbook
Why the Singapore box felt slow — measured: it's the wire, not the server (a 1.5 KB file takes 1.4 s). What WeChat (Mars · mmtls · smart heartbeat) and WhatsApp (local store · pending-queue · ✓✓) do on bad networks, then a scorecard of our app against it. Shipped to production: one-round-trip /api/boot, persistent per-user cache, optimistic everything, live-turn recache, access-loss eviction, a durable send outbox (retry + “!”), delta sync. What's left: the ~1.4 s handshake (HTTP/3 / edge). Merges the old Perceived speed + Slow-network playbook; three SVGs inside.
UX benchmark
We look like a messenger, so users arrive with messenger reflexes. The live room held against WhatsApp, WeChat, and Telegram across style · interaction · function · the title-menu hub — faithful four-app redraws, a borrow / refuse filter, and a ranked, impact×effort set of borrowings that fit a room full of AI personas (long-press menu, quote-to-panel, search, 置顶/免打扰, Telegram's title-menu hub). Plus what we deliberately won't take.
Persona colour
Colour is a per-room seat, not a persona's identity — a rotated OKLCH palette so a cast always reads distinct, and the same figure can wear different colours in different rooms. Grey is system. Live since v88.
Push-to-talk
Voice input for the composer: six patterns weighed, the Gemini-style composer + live dictation shipped. The mockups of the alternatives are kept as the record.
LLM whiteboards
How a persona hands over a drawing: Mermaid vs Excalidraw vs tldraw. Mermaid shipped as a bubble renderer (→ the wildcard pane); a shared canvas stays a parked phase-2 idea.
Kept for provenance — frozen design specimens whose pick has shipped, and pages superseded by the current docs above. Not maintained; each carries an in-page banner.
Specimens — explorations whose decision shipped
- Composer studies — 13 landing-composer takes; shipped “Coral flow”.
- Convene-button studies — the “arrow → 3 heads” glyph shipped.
- Wide-mode composer studies — resolved: the muted call-pill shipped.
- Link-unfurl mock — a working prototype; not yet built into the room.
- Chat-list avatar study — four treatments for the row's leading tile; the plain figure + linear disc-blend shipped v531–v532.
Superseded — replaced by current docs
- The build plan — phases A/B/C all shipped; now build history. → Function map.
- Architecture (old) — pre-streaming, per-room cast “planned”, exp-010. → Function map.
- Repo sync — the retired Mac-mini / branch model. → Dev loop.
- DECIDE gate — a response-gate design on the frozen exp-010 study.
- Narrow-scrollbar specimen — the call was made; #10–15 now hidden. → Style Guide.
Resolved decisions
- reply model
- Busy-state batching — idle → answer now; busy → queue, then drain the whole queue in one turn. Caps API calls at the panel's turn rate, not the human typing rate.
- signup gate
- Invite code at registration + a per-user daily cap. Public URL + paid API = open wallet without it.
- transport
- SSE-native — instant message delivery + an "X is typing…" indicator. Presence is RAM-only; it never touches
state.json. - hosting
- Singapore (Ali ECS), domain
xbbapp.com, grey-cloud DNS direct to the box, Caddy for TLS. Hong Kong is blocked by Anthropic. - seed-admin
- On migration, existing rooms are assigned to you; the second tester joins by invite.