Adds spectator screen#8
Merged
Merged
Conversation
Adds a dedicated full-screen display view designed for tablets and monitors at the range. Joining as a display screen connects read-only with no name or lane required, and renders a landscape-optimised layout with a massive countdown, target status indicator, and stage/series info — no controls. - server: accept `role: 'spectator'` in JOIN_ROOM; skip PEER_JOINED/PEER_LEFT notifications to host so display screens are invisible to shooters list - SocketClient: pass `role` in JOIN_ROOM; set `isSpectator` on roomState - stores: add `isSpectator` to roomState; add 'display' to currentView - DisplayView.svelte: new full-screen view with portrait + landscape layouts; uses clamp() for giant digits that scale from phones to 4K monitors; always holds wake lock; shows phase, target, reshoot, and stage info - HomeView: add "Display Screen" toggle that expands a code-only join form; rejoin history shows "Spectator" label and restores to display view - App.svelte: route 'display' view; restore display sessions on page reload - i18n: add spectator/display mode translation keys (no + en) Closes #4 Co-authored-by: GilbN <GilbN@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new “spectator/display” mode that allows a device (monitor/tablet) to join an existing room as a read-only large-screen timer view.
Changes:
- Adds a new
DisplayViewand routes to it via a newcurrentViewstate ('display'). - Adds spectator join/rejoin flows from Home and session restore in App, plus room history labeling.
- Extends client/server join protocol to include a
roleso spectators can join without lane assignment and remain invisible to the host.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/HomeView.svelte | Adds UI + logic to join as spectator, and handles rejoining spectator sessions from history. |
| src/views/DisplayView.svelte | New large-screen spectator timer view with wake lock + disconnect behavior. |
| src/lib/stores.js | Extends room state to track isSpectator; adds 'display' to view routing comment. |
| src/lib/peer/SocketClient.js | Adds role to join payload and sets roomState.isSpectator on join. |
| src/lib/i18n.js | Adds new translation keys for spectator mode UI. |
| src/App.svelte | Restores spectator sessions on reload and routes to DisplayView. |
| server/index.js | Adds role to JOIN_ROOM handling; treats spectators specially for lane checking and host notifications. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+85
to
92
| const isSpectator = role === 'spectator' | ||
| if (!isSpectator && lane && isLaneTaken(room, lane)) { | ||
| send(ws, { action: 'ERROR', reason: 'lane_taken' }) | ||
| return | ||
| } | ||
| const peerId = generatePeerId() | ||
| room.clients.set(peerId, { ws, name: name || '', lane: lane || '' }) | ||
| room.clients.set(peerId, { ws, name: name || '', lane: lane || '', isSpectator }) | ||
| ws._roomCode = code |
| room.clients.set(peerId, { ws, name: name || '', lane: lane || '' }) | ||
| room.clients.set(peerId, { ws, name: name || '', lane: lane || '', isSpectator }) | ||
| ws._roomCode = code | ||
| ws._role = 'client' |
Comment on lines
87
to
92
| clearTimeout(timeout) | ||
| this._joined = true | ||
| this._reconnectAttempt = 0 | ||
| roomState.update((s) => ({ ...s, code: this.code, isHost: false })) | ||
| roomState.update((s) => ({ ...s, code: this.code, isHost: false, isSpectator: this.role === 'spectator' })) | ||
| this._emitStatus('connected') | ||
| this._attachMessageHandler() |
Comment on lines
+132
to
+138
| try { | ||
| const client = new SocketClient() | ||
| window.__opkClient = client | ||
| await client.joinRoom(code, { role: 'spectator' }) | ||
| saveRoomState({ code, isHost: false, isSpectator: true }) | ||
| currentView.set('display') | ||
| } catch (e) { |
- Server: spectators no longer bypass lane enforcement — isLaneTaken
skips spectator entries and handleJoinRoom forces lane='' for
spectators so a spoofed client can't reserve a lane.
- Server: handleRelay drops client→host traffic from connections
flagged ws._isSpectator, keeping spectators strictly read-only.
- SocketClient.destroy() now resets roomState with isSpectator: false
so the store shape stays consistent with its initial value.
- HomeView: joinDisplay, joinRoom, and rejoinRoom catch paths call
destroy() on the host/client before nulling the globals, preventing
leaked half-open WebSockets when a connection attempt fails.
…nnect. Adjust specator mode text size.
…plete and phase banners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude/issue 4 20260409 2214