Skip to content

Adds spectator screen#8

Merged
GilbN merged 8 commits into
mainfrom
claude/issue-4-20260409-2214
Apr 12, 2026
Merged

Adds spectator screen#8
GilbN merged 8 commits into
mainfrom
claude/issue-4-20260409-2214

Conversation

@GilbN
Copy link
Copy Markdown
Owner

@GilbN GilbN commented Apr 11, 2026

Claude/issue 4 20260409 2214

github-actions Bot and others added 2 commits April 9, 2026 22:21
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>
Copilot AI review requested due to automatic review settings April 11, 2026 16:26
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opk-timer Ready Ready Preview, Comment Apr 11, 2026 6:33pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DisplayView and routes to it via a new currentView state ('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 role so 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 thread server/index.js
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
Comment thread server/index.js
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 thread src/views/HomeView.svelte
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.
@GilbN GilbN merged commit 279fbaf into main Apr 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants