# Lobby UI The lobby is the first authenticated view; the user lands here after the email-code login completes (see [`docs/auth-flow.md`](auth-flow.md)). This doc captures the shared shell, the Overview sections, the profile sub-screen, and the defaults baked into the create-game form. ## Shell Lobby and profile share a single chrome implemented in `lib/screens/lobby-shell.svelte`. The chrome mirrors the project site's VitePress layout: a left page-list sidebar (Overview / Profile), a top identity strip on the right, and the page content in the right-hand column. The shell uses `var(--font-mono)` so the post-login pages adopt the "nerdy" type stack that the public site already uses. The identity strip renders the caller's `display_name` (falling back to the immutable `user_name` handle, then to a loading placeholder while `user.account.get` resolves) as a `data-testid="lobby-account-name"` button. Clicking it switches the top-level screen to `profile` (`appScreen.go("profile")`); the e2e suites use that testid as their lobby-loaded signal. The logout button sits next to it (`session.signOut("user")`). The sidebar always renders both pages; clicking the active page is a no-op. The shell collapses to a horizontal scrolling strip below 640px. ## Overview sections The Overview page renders one column of sections, top to bottom. Cards inside each section take the full available width. | Section | Empty state | Source | Action | | -------------------- | --------------------- | -------------------------- | --------------------------------------------------------- | | `create new game` | (always visible) | — | Opens the create screen (`appScreen.go("lobby-create")`) | | `my games` | `no games yet` | `lobby.my.games.list` | Click → enters the game on the map view (`activeView.reset()` + `appScreen.go("game", { gameId })`) | | `pending invitations`| `no invitations` | `lobby.my.invites.list` | Accept (`lobby.invite.redeem`) / Decline (`lobby.invite.decline`) | | `my applications` | `no applications` | `lobby.my.applications.list` | Status badge (`pending` / `approved` / `rejected`) | | `public games` | `no public games` | `lobby.public.games.list` | Submit application via inline race-name form (`lobby.application.submit`) | ## Profile sub-screen `lib/screens/profile-screen.svelte` is a top-level `AppScreen` (peer of `lobby` and `lobby-create`). The browser Back stack treats it the same as the create screen — pushing a fresh history entry on entry, falling back to lobby on Back/Forward (see [`navigation.md`](navigation.md)). On mount it issues `user.account.get` through `src/api/account.ts` and renders an identity read-out (immutable `user_name`, `email`) plus a three-field form: | Field | Endpoint | Notes | | --------------------- | --------------------- | -------------------------------------------------------------- | | `display_name` | `user.profile.update` | Trimmed; empty value clears the stored name (backend PATCH semantics). | | `preferred_language` | `user.settings.update`| ``, RFC 3339 on submit | | `min_players` | Advanced toggle | `2` | `
` block | | `max_players` | Advanced toggle | `8` | | | `start_gap_hours` | Advanced toggle | `24` | | | `start_gap_players` | Advanced toggle | `2` | | | `target_engine_version` | Advanced toggle | `v1` | Falls back to `v1` if blank | On success the create screen returns to the lobby (`appScreen.go("lobby")`) and the new game shows up in `my games` once the lobby's onMount has had a chance to refresh the list (the lobby screen remounts on return, so its onMount re-fires). ## Errors Lobby errors raised by the gateway carry a canonical code (`invalid_request`, `subject_not_found`, `forbidden`, `conflict`, `internal_error`). The `LobbyError` thrown by `lobby.ts` exposes the code; the page maps it to the matching `lobby.error.` i18n key and falls back to the gateway-supplied message via `lobby.error.unknown` for any unknown code. ## Why FlatBuffers on the TS side The gateway encodes lobby payloads through `pkg/transcoder/lobby.go` into FlatBuffers bytes; the browser must decode them with the same schema. The TS integration ships: - `flatbuffers` runtime dependency in `ui/frontend/package.json`; - `make -C ui fbs-ts` driving `flatc --ts` to regenerate the bindings from `pkg/schema/fbs/*.fbs` into `ui/frontend/src/proto/galaxy/fbs/`; - a Vitest round-trip suite (`tests/lobby-fbs.test.ts`) that catches binding drift in CI. `user.account.get` decodes through the generated `AccountResponse` table, so the lobby greeting works against a real local stack as well as the mocked Playwright fixtures.