Skip to content

Bug: handle_info in game_live/show.ex always sets requested_round = rounds_played + 1, breaking finished game summary view #2548

Description

@immortal71

Describe the bug

In copi.owasp.org/lib/copi_web/live/game_live/show.ex, the handle_info/2 callback that processes game:updated broadcasts always sets requested_round to updated_game.rounds_played + 1 — regardless of whether the game has finished. This contradicts handle_params/3, which correctly uses rounds_played (without +1) for finished games. As a result, any game:updated broadcast received while viewing a finished game summary resets the view to a non-existent round.

Affected file

copi.owasp.org/lib/copi_web/live/game_live/show.ex

Code snippet

# handle_params — CORRECTLY handles finished games (line 24-28):
current_round = if game.finished_at do
  game.rounds_played          # ← correct: last actual round
else
  game.rounds_played + 1      # ← correct: current in-progress round
end

# handle_info — ALWAYS does +1, ignores finished_at (line 48-51):
def handle_info(%{topic: message_topic, event: "game:updated", payload: updated_game}, socket) do
  cond do
    topic(updated_game.id) == message_topic ->
      {:noreply, assign(socket, :game, updated_game) |> assign(:requested_round, updated_game.rounds_played + 1)}
      # ↑ BUG: adds +1 even when updated_game.finished_at is set
    true ->
      {:noreply, socket}
  end
end

Exact failure scenario

  1. Game finishes with rounds_played = N and finished_at set.
  2. handle_params correctly renders the game summary with requested_round = N.
  3. Any subsequent game:updated broadcast fires (e.g., from a late-arriving player reconnect, the transition broadcast itself, or any other event).
  4. handle_info sets requested_round = N + 1 — a round that does not exist.
  5. The game summary view now tries to show round N + 1, finds no cards, and renders a blank/broken summary page.

Expected behavior

handle_info should mirror handle_params in how it computes requested_round:

def handle_info(%{topic: message_topic, event: "game:updated", payload: updated_game}, socket) do
  cond do
    topic(updated_game.id) == message_topic ->
      requested_round =
        if updated_game.finished_at do
          updated_game.rounds_played
        else
          updated_game.rounds_played + 1
        end
      {:noreply, assign(socket, :game, updated_game) |> assign(:requested_round, requested_round)}
    true ->
      {:noreply, socket}
  end
end

Steps to reproduce

  1. Create and play a game to completion (all rounds done, finished_at set).
  2. While on the game summary page (/games/:game_id), trigger any action that causes a game:updated broadcast (or simply wait if there is any background activity).
  3. Observe: the summary page goes blank / shows "round not found" because requested_round is now one past the last round.

Additional context

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions