TableGather Hub is a browser-first React/Vite PWA for local table games. V1 ships Werewolf as the playable game. Imposter and Undercover are registered in the hub as coming-soon games so the registry and UI already support multiple games.
The product is designed for two table-play modes plus an optional public display:
- Pass-and-play: one device is used by the host/table. Players privately reveal roles one at a time, then the host runs night and day phases.
- Room mode: the host creates a room with a room code, join link, and QR code. Players join from their own phones. The host still runs the game; player devices only receive role-filtered private/public snapshots.
- Stage mode: in a Werewolf room, the host can create a separate read-only stage link for a TV, projector, or second screen. The stage client receives only public snapshots and follows the host-controlled reveal queue.
Requirements:
- Node.js
>=22.12.0 - npm
Commands:
npm install
npm run dev # Vite frontend on all interfaces
npm run server:dev # WebSocket room server
npm run server:start # WebSocket room server without watch mode
npm run dev:all # frontend and server together
npm run lint
npm run test
npm run buildRoom mode needs the WebSocket server. By default, the server listens on port 8787, and the browser connects to ws://<current-hostname>:8787/ws.
Configuration:
- Copy
.env.exampleto.envfor local defaults or.env.production.localfor production secrets.npm run server:startloads.env,.env.local,.env.production, and.env.production.localin that order. PORTorTABLEGATHER_PORTchanges the room server port.TABLEGATHER_ADMIN_TOKENenables the protected admin room overview API and/admindashboard. Open/admin#token=<token>only as a transient way to load the token; the client immediately stores it insessionStorageand removes it from the fragment so it is not kept in the address bar. Subsequent admin visits should use/adminwithout the token.TABLEGATHER_ADMIN_ALLOWED_ORIGINSoptionally allows comma-separated cross-origin admin UI origins. Production defaults to same-origin admin access; set this only for split-origin admin deployments.TABLEGATHER_WS_ALLOWED_ORIGINSoptionally allows comma-separated browser WebSocket origins for split frontend/server deployments. Production accepts same-origin browser WebSocket requests by default; development also allows the local Vite origins.TABLEGATHER_SERVE_STATICcontrols whether the room server serves the builtdist/frontend. It defaults to enabled whenNODE_ENV=productionand disabled otherwise.TABLEGATHER_TRUSTED_PROXIESoptionally allows comma-separated exact proxy peer IPs whoseCF-Connecting-IPorX-Forwarded-Forheaders may identify room rate-limit clients. Leave it empty unless the room server is behind a trusted reverse proxy.VITE_WS_URLoverrides the browser WebSocket URL. It acceptsws://,wss://,http://, orhttps://; HTTP(S) values are converted to WS(S). In production, leave it empty for a same-origin/wsendpoint behind your reverse proxy.- For phone/tablet testing on the same local network, run
npm run dev:all, open the Vite URL from the host machine's LAN address, and make sure the room server port is reachable from the other devices.
Production can run as a single Node room server that also serves the built frontend. The process manager and reverse proxy are deployment-specific; they only need to start the current server script and forward traffic to it.
Use .env.example as the reference for production values. The usual production environment is:
NODE_ENV=productionso the room server serves the builtdist/frontend by default.PORTorTABLEGATHER_PORTfor the room server listen port.TABLEGATHER_ADMIN_TOKENwhen the protected/admindashboard should be enabled.TABLEGATHER_ADMIN_ALLOWED_ORIGINSonly when/adminis served from a different origin than/admin/rooms.TABLEGATHER_WS_ALLOWED_ORIGINSonly when the browser app is intentionally served from a different origin than/ws.TABLEGATHER_SERVE_STATIC=falseonly for split deployments where another service servesdist/.TABLEGATHER_TRUSTED_PROXIESonly when a trusted reverse proxy overwrites forwarding headers before traffic reaches the room server.
Build and start with the repository scripts:
npm ci --include=dev
npm run build
npm run server:startThe current scripts build and start from TypeScript, so install the full dependency set needed by both build and runtime. Configure any process manager to run npm run server:start with the production environment available.
For same-origin production deployments, leave VITE_WS_URL empty. The built browser app connects to /ws on the current origin, and the room server exposes the matching WebSocket endpoint. In the single-process setup, route /, SPA routes, /ws, /health, and /admin/rooms to the same TableGather server. No separate static host or admin API location is required.
Set VITE_WS_URL only when the browser app and room server are intentionally split across different origins or path prefixes. It accepts ws://, wss://, http://, or https://; HTTP(S) values are converted to WS(S). For split-origin browser deployments, add the frontend origin to TABLEGATHER_WS_ALLOWED_ORIGINS on the room server.
Main routes:
/- hub game/mode selection and device-local room sessions./play/<gameId>- local pass-and-play for a playable game./room/create/<gameId>- create a room for a playable game as host./room/<CODE>- join or resume a room. The app resolves the room's game id through the room server, then opens the registered host or player view./stage/<CODE>/<TOKEN>- open a registered stage screen from a host-created stage link./admin- protected room overview dashboard whenTABLEGATHER_ADMIN_TOKENis configured on the room server.
- Werewolf is fully playable in local and room mode.
- Werewolf room mode supports host, player, and read-only stage clients.
- Room state is in-memory on the server. Rooms expire after 48 hours of inactivity, and a server restart clears active server-side room state.
- Browser-stored reconnect tokens, stage tokens, and local session tokens remain on the client until explicitly invalidated by the server or user, but may no longer map to an active room after expiry or restart.
- The Hub Session tab shows active host/player rooms known to the current browser by validating stored local tokens with the room server; locally stored tokens can outlive the server-side room they belonged to.
- Local pass-and-play Werewolf persists its current game in
localStorage. - Privacy is enforced by snapshots: host snapshots can contain full game state, player snapshots only expose the requesting player's private role data and public table status, and stage snapshots expose only public room/game information.
- No placeholder role art is bundled for Werewolf. Icon fallbacks remain until real assets are added.
docs/architecture.md- shared project architecture, game registry, runtime, tests, and extension boundaries.docs/online-mode.md- shared room lifecycle, WebSocket protocol, snapshots, tokens, and adapter responsibilities.docs/styling-and-theming.md- shared styling layers, theme tokens, and future-game styling guidance.docs/games/README.md- game documentation ownership and per-game doc set expectations.docs/games/werewolf/README.md- Werewolf-specific flow, role, room, Stage, and styling docs.CONTRIBUTING.md- contribution workflow and local verification expectations.NOTICE.md- project branding and attribution notice.SECURITY.md- supported scope and private vulnerability reporting guidance.
src/App.tsxowns route parsing and delegates game screen selection to the route component registry.src/games/registry.tsregisters games and exposes playable game adapters.src/games/routeComponents.tsxmaps playable game definitions to client-only route screens.src/games/types.tsdefinesGameDefinition, structured player constraints, theme tokens, and the room adapter contract, including command validators and optional stage snapshots.src/games/werewolf/contains the Werewolf definition, domain engine, room adapter, stage snapshot builder, components, i18n, theme, and game-specific CSS.src/stage/contains reusable client-only Stage display state for fullscreen and Screen Wake Lock.src/online/contains client-side WebSocket message types, room session storage helpers, and the room socket hook.server/contains the in-memory WebSocket room runtime.test/contains Vitest coverage for domain behavior, room behavior, i18n, registry, clipboard, and UI rendering.
This repository is intended to be shared as source code, not published as an npm package. package.json keeps "private": true to prevent accidental package publication.
The source code is licensed under the GNU Affero General Public License v3.0 only. See LICENSE.
The TableGather name, logo, and branding are not licensed for reuse without prior written permission. See NOTICE.md.
Please read CONTRIBUTING.md before opening changes and use SECURITY.md for vulnerability reports.
Run the full check set after behavior, room, role, UI, i18n, or documentation changes:
npm run lint
npm run test
npm run buildOn this Windows sandbox, npm run test or npm run build can fail with spawn EPERM while loading native Vite/Tailwind bindings. If the failure is only that native binding error, rerun the same command outside the sandbox.