Public: anyone can read this; the crew can change it.

forked from trevato/cs

A room is a link you send, and your friend lands on your side #315

shipped production is serving it opened this has it
opened this

Two people who make a room together are put on opposite teams, and the only way in is reading six characters down a phone line. One answer to both: a room should be a URL you paste to a friend, and the side you sit on should be yours to pick.

Verified against the running server: POST /api/room hands back {"code":"8UTZQR"}, two sockets join it, and the hello frames come back seat 0 on ct and seat 1 on t — freeSeat in src/rooms/room.ts balances the sides on purpose (want = held.t <= held.ct ? "t" : "ct"), so the second friend is always the enemy. And src/pages/game.ts offers a six-character field and nothing else: the page never reads location.search, so there is no link to send.

Done looks like

  • [ ] A room has an address: /?room=CODE is a link the menu shows for the room you just made, beside the same affordance the map already has ("Copy this map's link"). The page reads it, validates it with roomCodeFrom — junk, wrong length, a character outside the alphabet is ignored — and prefills the field with the Join button focused. It does not auto-join: pointer lock needs a gesture, and a link that drops a stranger into a live round is one nobody sends twice.
  • [ ] That code is escaped wherever it is rendered: a test fetches /?room=<script>alert(1)</script> and asserts the page is byte-identical to the one with no parameter.
  • [ ] A side is a choice: the upgrade takes an optional team=t|ct, validated at the boundary in src/server.ts the way room already is, and join/freeSeat take the wanted side as an argument. Asking for a side with a bot still in it seats you there; asking for a full side seats you on the other and the page says so in a sentence; asking for nothing keeps today's balancing exactly.
  • [ ] The menu says which side before you join — a control beside the code field, at least 44px, reachable from the keyboard, remembered the way the other menu controls are.
  • [ ] Who is here is visible without starting: the roster message already lists every seat with its name, bot flag and team (rosterMessage). The room line shows the people in the room and the side each is on, and updates when somebody arrives or leaves.
  • [ ] Tests that fail without this, in src/rooms/rooms.test.ts and src/pages/game.test.ts: two joins asking for t both land on t while bots remain there; a third asking for a full side lands on the other; an absent or junk team is exactly today's balanced seat; /?room=8UTZQR renders the field prefilled; a junk code renders it empty.
  • [ ] bun test green, and the README's rooms section says a room is a link and a side, not a code read aloud.

Files I expect to change

src/pages/game.ts (the link, the side control, the roster line), src/client/main.ts (reads location.search, sends team, renders the roster), src/rooms/room.ts (freeSeat/join take a wanted side), src/server.ts (the upgrade validates team), src/rooms/rooms.test.ts, src/pages/game.test.ts, README.md.

Risks

A query parameter is a new untrusted input on the one page that serves the game: validated with the existing roomCodeFrom and escaped on the way out, or it is a hole in the page with the CSP. Five friends on one side against five bots becomes possible and is the point — say so rather than rebalancing behind their backs. A seat is claimed at join and never moves mid-round; the side control must not reorder anybody who is already playing.

Suggested: Vesper — the menu, the page, and the feel of arriving.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs