Skip to content

Frontend

The frontend is a SvelteKit application under frontend/, built with Vite and TypeScript and deployed as static files. It uses raw CSS rather than a component or utility framework.

Main files

PathResponsibility
src/routes/+page.sveltePrerendered home page and room creation
src/routes/room/[code]/Client-rendered join and room lifecycle
src/routes/+layout.svelteGlobal styles and theme initialization
src/views/Home, join, full-table room, and focused phone view composition
src/components/Shared brand and theme controls
src/components/room/Room session view, header, active table, shared vote cards, estimate deck, and session summary
src/lib/room/Room domain types, session state machine, and pure table layout calculations
src/lib/transport/Shared transport contract and hardened SSE/WebSocket implementations
src/lib/api.tsTyped room API operations and status-aware errors
src/app.cssGlobal design tokens, resets, and shared control styles
vite.config.tsDevelopment proxy for API and real-time routes
../backend/internal/adapters/webappProduction static serving and dynamic-route fallback

View lifecycle

The app has four views:

  1. Home: collect room and facilitator names, then create a room.
  2. Join: collect a display name for an invite URL such as /room/A1B2C3.
  3. Room: display participants, topic, voting cards, completion state, revealed results, and the final session summary on a shared or desktop screen.
  4. Mobile room: provide a focused card-selection interface for participants who can see the full table on a shared screen.

SvelteKit supplies filesystem routing. The home page is prerendered to index.html; /room/[code] is client-rendered because it depends on browser identity storage and realtime connections. The static adapter emits 200.html, which the Go web adapter serves for direct room URLs. The route keys RoomSessionView by room code so navigation between room URLs always closes the old transport and initializes fresh state.

Identity storage

After create or join, the participant token is stored under:

text
poker:{ROOM_CODE}

in localStorage. Reloading a room recovers the token and opens a new event connection. This is intentionally lightweight MVP identity, not user authentication; see Security.

State updates

  • Command responses may immediately replace the current room.
  • Real-time events also carry complete room snapshots.
  • The client accepts a snapshot only when its version is at least the currently rendered version.
  • A locally selected card is retained for feedback because unrevealed server snapshots intentionally omit vote values.
  • A snapshot that removes the participant's submitted vote clears the local selection, including resets initiated by another client.
  • A failed vote restores the previously selected card, and pending commands disable controls that could submit duplicates.
  • Room initialization has explicit loading, joining, connected, and unavailable states. Temporary failures retain stored identity; definitive authorization or not-found responses remove it.
  • When the facilitator ends a session after a reveal, every client receives the persisted terminal snapshot and renders shared awards plus a per-participant statistics table.

Appearance

The appearance control cycles through system, light, and dark modes. System mode follows prefers-color-scheme and responds when the operating-system preference changes. A manual light or dark override is stored in localStorage under poker:theme; returning to system mode removes the override. A small script in app.html applies the effective theme before hydration to avoid a light/dark flash.

Table layout

During an active round, participants are distributed evenly around an elliptical table in server join order, rotated so the current participant sits at the bottom of their view. The keyed participant seats transition to new positions when the team changes. Submitted votes animate from each seat to a face-down card position on the nearby table edge. When the facilitator reveals the round, the cards flip and move into centered vertical piles for equal estimates; those piles follow the configured deck order from left to right, while equal estimates retain seating order within their pile. Participant and card sizes become progressively more compact for large teams and narrow screens; reduced-motion preferences disable these transitions without changing the final state.

Phone voting

RoomSessionView owns one room session and chooses its presentation using a media query. Narrow portrait viewports, and coarse-pointer devices with limited landscape height, receive the focused mobile view by default. Participants can temporarily open the full table and return to phone mode without reconnecting or creating a second session.

The phone view shows room identity, connection state, vote status, errors, and a large touch-friendly card grid. It intentionally omits the topic, participant table, results, summary statistics, transport switcher, and facilitator controls because those remain visible and operable on the shared screen. After reveal or session completion, the phone directs the participant back to that screen.

Transport abstraction

RoomTransport exposes three operations:

ts
interface RoomTransport {
  connect({ onRoom, onStatus, onError }): void
  command(type, payload?): Promise<Room | undefined>
  close(): void
}

HttpSseTransport and WebSocketTransport both satisfy this contract. Components and room state do not need to know which network protocol is active. The connection badge switches transport at runtime for demonstration and parity testing. SSE uses the browser's native reconnection; WebSocket reconnects with bounded exponential backoff and rejects pending commands when a connection closes. Session generation checks prevent callbacks from replaced transports from mutating current state.

Styling

Global CSS defines light and dark color palettes, document defaults, and shared form and control styles. Views and components own their scoped layout, responsive, dark-mode, and animation styles. Interactive controls include visible keyboard focus styles, semantic button labels, pressed-state semantics for vote cards, and non-color vote indicators.

Backlog Hold'em project documentation