Skip to content

refactor(arena): split mechanic events from control messages and genericize socket hook#13

Merged
muzzlol merged 2 commits into
mainfrom
exhaustisize-server-events
Feb 6, 2026
Merged

refactor(arena): split mechanic events from control messages and genericize socket hook#13
muzzlol merged 2 commits into
mainfrom
exhaustisize-server-events

Conversation

@muzzlol

@muzzlol muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner

add typed mechanic envelope to ServerMsg (ArenaEvent)
broadcast draw updates via broadcastMechanic on the DO server
refactor useArenaSocket to generic arena data/state and typed arena_over handling
replace mode-specific senders with sendAction(action: ClientAction)
begin adapting arena-active to new hook/event API (WIP)

Summary by CodeRabbit

  • Refactor
    • Consolidated arena event broadcasting and standardized server protocol messaging for improved system architecture.
    • Unified socket communication handling and action API to reduce complexity in arena interactions while maintaining current functionality.

Greptile Overview

Greptile Summary

This PR refactors the arena event system by introducing a typed ArenaEvent<T> envelope within ServerMsg, replacing the previous flat event union. The server now uses broadcastMechanic() to wrap mechanic events, and the client hook useArenaSocket was genericized to handle ArenaData<T> with a unified sendAction(action: ClientAction<T>) API.

Key Changes:

  • Split mechanic events from control messages in protocol types
  • Added { type: "mechanic", event: ArenaEvent<T> } to ServerMsg<T>
  • Replaced mode-specific senders (sendElements, sendCursor) with generic sendAction
  • Refactored hook to pass events via onArenaEvent callback
  • Updated onGameOver signature to include ArenaResults

Critical Issues:

  • arena-active.tsx has multiple runtime errors from incomplete refactoring - references undefined variables (elements, cursors, codeByParticipant, testResultsByParticipant, typingProgress) and removed functions (sendElements, sendCursor)
  • Wrong type signature for handleGameOver callback (expects string instead of ArenaEndReason)

The protocol and hook refactoring is clean and well-typed, but the integration layer is broken and will cause immediate runtime failures.

Confidence Score: 1/5

  • This PR contains critical runtime errors that will break the application immediately
  • While the protocol refactoring and hook changes are well-designed and type-safe, arena-active.tsx has 7+ undefined variable references and incorrect function signatures that will cause immediate runtime failures. The file is marked WIP but these errors prevent the code from running at all.
  • Pay close attention to src/routes/arena/-components/arena-active.tsx - it has multiple critical errors that must be fixed before merging

Important Files Changed

Filename Overview
arenas/server.ts Added broadcastMechanic helper to wrap mechanic events in typed envelope - clean implementation with proper generic typing
shared/arena-protocol.ts Restructured protocol types: separated ArenaEvent<T> from ServerMsg<T> and renamed ServerEvent to ArenaEvent for clarity
src/routes/arena/-hooks/use-arena-socket.ts Refactored to generic ArenaData<T>, replaced mode-specific senders with unified sendAction, added onArenaEvent callback - well-typed and clean
src/routes/arena/-components/arena-active.tsx WIP file with multiple runtime errors: undefined variables (elements, cursors, codeByParticipant, etc.) and wrong function signatures

Sequence Diagram

sequenceDiagram
    participant Client as Client (React)
    participant Hook as useArenaSocket
    participant WS as WebSocket
    participant DO as Durable Object
    participant Storage as DO Storage

    Note over Client,DO: Connection & Initialization
    Client->>Hook: useArenaSocket({ config, onArenaEvent })
    Hook->>WS: connect(arenaId)
    WS->>DO: WebSocket Upgrade
    DO->>Storage: load state
    Hook->>WS: ClientMsg { type: "init", userId, username, config }
    DO->>Storage: save config & initialize ArenaData<T>
    DO->>DO: start alarm (tick loop)
    DO->>WS: ServerMsg { type: "state", participants, data, timeRemaining }
    WS->>Hook: state message
    Hook->>Client: return { participants, data, timeRemaining, sendAction }

    Note over Client,DO: Game Action Flow
    Client->>Hook: sendAction({ type: "cursor", x, y })
    Hook->>WS: ClientAction<T>
    DO->>DO: handleDraw/handleCode/handleTyping
    DO->>Storage: update ArenaData<T>
    DO->>DO: broadcastMechanic(event, ws)
    DO->>WS: ServerMsg { type: "mechanic", event: ArenaEvent<T> }
    WS->>Hook: mechanic message
    Hook->>Client: onArenaEvent(event)
    Client->>Client: update UI based on event

    Note over Client,DO: Tick Updates
    DO->>DO: alarm() fires every 1s
    DO->>WS: ServerMsg { type: "tick", timeRemaining }
    WS->>Hook: tick message
    Hook->>Client: update timeRemaining

    Note over Client,DO: Game End
    DO->>DO: alarm() detects timeRemaining <= 0
    DO->>Storage: finalize & save results
    DO->>WS: ServerMsg { type: "arena_over", reason, results }
    WS->>Hook: arena_over message
    Hook->>Client: onGameOver(reason, results)
    DO->>WS: close all connections
Loading

@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

The changes refactor the arena protocol and client-server messaging architecture. They introduce a broadcastMechanic helper to centralize mechanic event dissemination, consolidate ServerMsg<T> with a mechanic variant, unify the client hook's data structure to ArenaData<T>, and replace element/cursor-specific APIs with a generic sendAction mechanism while adding typed event callbacks and connection state awareness.

Changes

Cohort / File(s) Summary
Server Messaging Architecture
arenas/server.ts, shared/arena-protocol.ts
Introduces broadcastMechanic helper method to wrap cursor/canvas events in "mechanic"-typed messages. Refactors protocol types: ServerMsg<T> replaces ClientMsg with variants (state, tick, participant_joined, participant_left, arena_over, error, mechanic); ArenaEvent<T> replaces ServerEvent<T> for broadcast-centric event typing.
Client Hook Refactoring
src/routes/arena/-hooks/use-arena-socket.ts
Introduces generic useArenaSocket<T extends ArenaType> with new return structure. Replaces legacy elements/cursors with unified ArenaData<T>, introduces sendAction(ClientAction<T>) replacing separate sendElements/sendCursor, adds onArenaEvent callback and connectionState (ReadyState) exposure, updates onGameOver signature to include reason and results.
Component Integration
src/routes/arena/-components/arena-active.tsx
Removes currentPlayerCount from ArenaActiveProps, adds handleArenaEvent callback wired to new useArenaSocket API, replaces direct element/cursor method calls with unified sendAction, removes local state for participant-specific data (codeByParticipant, testResultsByParticipant, typingProgress).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #12: Overlaps on shared/arena-protocol type refactoring (ServerMsg/EventMap → ArenaEvent/ArenaEventMap) and server broadcast handling in arenas/server.ts.
  • PR #8: Both modify arenas/server.ts and ArenaWSS DurableObject class; this PR's broadcastMechanic extends the class structure introduced in that PR.
  • PR #9: Directly extends the ArenaEvent typing and mechanic message shape changes introduced earlier in shared/arena-protocol.ts and arenas/server.ts.

Poem

🐰 A rabbit hops through protocol trees,
Where messages now dance with such ease,
Mechanic broadcasts, typed and so clean,
The finest refactoring ever seen!
Data unified, actions one and true,
Huzzah! The arena's reborn anew! 🎪

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: refactoring arena code to separate mechanic events from control messages and generalize the socket hook, which aligns with the file-level changes across server.ts, arena-protocol.ts, arena-active.tsx, and use-arena-socket.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch exhaustisize-server-events

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@muzzlol

muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner Author

@greptile review

@muzzlol

muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 5, 2026

Copy link
Copy Markdown
Additional Comments (2)

src/routes/arena/-components/arena-active.tsx
Stale props cause runtime error

ArenaActive no longer defines/receives elements, cursors, sendElements, sendCursor, codeByParticipant, testResultsByParticipant, or typingProgress, but the JSX still references them (e.g. elements={elements} / cursors={cursors}), which will throw at runtime / fail type-checking. This component needs to be updated to derive these from state.data + handleArenaEvent + sendAction, or restore the removed state/handlers so the props passed to DrawArena/CodeArena/TypingArena are defined.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 146:148

Comment:
**Stale props cause runtime error**

`ArenaActive` no longer defines/receives `elements`, `cursors`, `sendElements`, `sendCursor`, `codeByParticipant`, `testResultsByParticipant`, or `typingProgress`, but the JSX still references them (e.g. `elements={elements}` / `cursors={cursors}`), which will throw at runtime / fail type-checking. This component needs to be updated to derive these from `state.data` + `handleArenaEvent` + `sendAction`, or restore the removed state/handlers so the props passed to `DrawArena`/`CodeArena`/`TypingArena` are defined.

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
onGameOver signature mismatch

useArenaSocket now calls onGameOver?.(reason, results) (use-arena-socket.ts), but ArenaActive still defines handleGameOver = (reason: string) => ..., so the hook is being given a callback with the wrong arity/types. Update handleGameOver to (reason: ArenaEndReason, results: ArenaResults) => void (or adjust the hook API), otherwise this will fail type-checking and the results payload is silently ignored.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 50:52

Comment:
**`onGameOver` signature mismatch**

`useArenaSocket` now calls `onGameOver?.(reason, results)` (`use-arena-socket.ts`), but `ArenaActive` still defines `handleGameOver = (reason: string) => ...`, so the hook is being given a callback with the wrong arity/types. Update `handleGameOver` to `(reason: ArenaEndReason, results: ArenaResults) => void` (or adjust the hook API), otherwise this will fail type-checking and the results payload is silently ignored.

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/routes/arena/-components/arena-active.tsx`:
- Around line 56-68: Destructure data from the useArenaSocket call (const {
data, connectionState, participants, timeRemaining, error, sendAction } =
useArenaSocket(...)) and import ArenaData, DrawData, CodeData, TypingData from
~/shared/arena-protocol; then use a type guard on data?.type to narrow to
DrawData/CodeData/TypingData and derive the arena-specific props (for draw:
elements, cursors, sendElements/sendCursor wrappers that call sendAction with
the draw action shape; for code: codeByParticipant, testResultsByParticipant and
a sendCode/sendTest wrapper; for typing: typingProgress and sendTyping wrapper).
Keep handleArenaEvent and sendAction usage but wrap sendAction into per-arena
helper functions to translate UI payloads into the server action format and
ensure all referenced variables (elements, cursors, sendElements, sendCursor,
codeByParticipant, testResultsByParticipant, typingProgress) are defined based
on the narrowed data.
🧹 Nitpick comments (1)
src/routes/arena/-hooks/use-arena-socket.ts (1)

79-131: Add a lightweight type guard before switching on parsed messages.
Right now JSON.parse is asserted as ServerMsg<T> without validation. A small guard avoids malformed payloads triggering the default branch.

♻️ Suggested guard
+  const isServerMsg = (value: unknown): value is ServerMsg<T> => {
+    if (!value || typeof value !== "object") return false
+    return typeof (value as { type?: unknown }).type === "string"
+  }
+
     onMessage: (event) => {
-      const message = JSON.parse(event.data) as ServerMsg<T>
+      const parsed: unknown = JSON.parse(event.data)
+      if (!isServerMsg(parsed)) {
+        setState((prev) => ({ ...prev, error: "Invalid server message" }))
+        return
+      }
+      const message = parsed

As per coding guidelines: Make use of function guards whenever possible in TypeScript.

Comment on lines +56 to +68
const handleArenaEvent = useCallback((event: ArenaEvent<ArenaType>) => {
console.log("Arena event:", event)
}, [])

const { connectionState, participants, timeRemaining, error, sendAction } =
useArenaSocket({
arenaId,
userId: user?._id ?? "",
username: user?.username ?? "Anonymous",
config,
onGameOver: handleGameOver,
onArenaEvent: handleArenaEvent
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the arena-active.tsx file
find . -name "arena-active.tsx" -type f

Repository: muzzlol/spectra

Length of output: 106


🏁 Script executed:

# Check the structure of the arena directory
fd "arena" -type d | head -20

Repository: muzzlol/spectra

Length of output: 229


🏁 Script executed:

# Get the full content of arena-active.tsx to see the actual code
cat -n src/routes/arena/-components/arena-active.tsx | head -200

Repository: muzzlol/spectra

Length of output: 7110


🏁 Script executed:

# Search for the useArenaSocket hook definition
fd "useArenaSocket" --type f
rg "useArenaSocket" --type tsx --type ts -l

Repository: muzzlol/spectra

Length of output: 86


🏁 Script executed:

# Search for child component definitions
fd -e tsx -e ts | xargs grep -l "DrawArena\|CodeArena\|TypingArena" | head -10

Repository: muzzlol/spectra

Length of output: 205


🏁 Script executed:

cat -n src/routes/arena/-hooks/use-arena-socket.ts 2>/dev/null || cat -n src/routes/arena/-hooks/use-arena-socket.tsx

Repository: muzzlol/spectra

Length of output: 4692


🏁 Script executed:

# Search for useArenaSocket implementation
rg "export.*useArenaSocket|function useArenaSocket|const useArenaSocket" -A 50

Repository: muzzlol/spectra

Length of output: 3605


🏁 Script executed:

# Check child component signatures - DrawArena
cat -n src/routes/arena/-draw/arena.tsx | head -50

Repository: muzzlol/spectra

Length of output: 1905


🏁 Script executed:

# Check child component signatures - CodeArena
cat -n src/routes/arena/-code/arena.tsx | head -50

Repository: muzzlol/spectra

Length of output: 1976


🏁 Script executed:

# Check child component signatures - TypingArena
cat -n src/routes/arena/-typing/arena.tsx | head -50

Repository: muzzlol/spectra

Length of output: 2043


🏁 Script executed:

# Search for arena-protocol to understand data types
find . -name "*arena-protocol*" -o -name "*arena*protocol*" | head -5

Repository: muzzlol/spectra

Length of output: 85


🏁 Script executed:

cat -n ./shared/arena-protocol.ts | head -200

Repository: muzzlol/spectra

Length of output: 6355


Wire data into hook destructuring and derive arena props from it (current code won't compile).
useArenaSocket returns data: ArenaData<T> | null, but line 60–68 doesn't destructure it. The JSX below references undefined variables (elements, cursors, sendElements, sendCursor, codeByParticipant, testResultsByParticipant, typingProgress) that should be derived from data and sendAction. This is a build blocker.

Extract data from the hook, use type guards to narrow the data type based on type, then derive per-arena props and wrap sendAction for each arena's action format.

Fix pattern using type guards (adjust action payloads to match your server protocol)
-  const { connectionState, participants, timeRemaining, error, sendAction } =
+  const { connectionState, participants, timeRemaining, error, data, sendAction } =
     useArenaSocket({
       arenaId,
       userId: user?._id ?? "",
       username: user?.username ?? "Anonymous",
       config,
       onGameOver: handleGameOver,
       onArenaEvent: handleArenaEvent
     })

+  const isArenaData = <K extends ArenaType>(
+    arenaType: ArenaType,
+    arenaData: ArenaData<ArenaType> | null,
+    expected: K
+  ): arenaData is ArenaData<K> => arenaType === expected && arenaData !== null
+
+  const drawElements = isArenaData(type, data, "draw") ? (data as DrawData).playerElements[user?._id ?? ""] ?? [] : []
+  const drawCursors = isArenaData(type, data, "draw") ? (data as DrawData).playerCursors ?? {} : {}
+  const codeByParticipant = isArenaData(type, data, "code") ? (data as CodeData).playerCode ?? {} : {}
+  const testResultsByParticipant = isArenaData(type, data, "code") ? (data as CodeData).testResults ?? {} : {}
+  const codeCursors = isArenaData(type, data, "code") ? (data as CodeData).playerCursors ?? {} : {}
+  const typingProgress = isArenaData(type, data, "typing") ? (data as TypingData).progress ?? {} : {}
+
+  const sendElements = useCallback(
+    (elements: DrawData["playerElements"][string]) => {
+      sendAction({ type: "canvas_update", elements } as ClientAction<"draw">)
+    },
+    [sendAction]
+  )
+
+  const sendCursor = useCallback(
+    (x: number, y: number) => {
+      sendAction({ type: "cursor", x, y } as ClientAction<ArenaType>)
+    },
+    [sendAction]
+  )

   return (
     <div className="flex h-screen flex-col">
       ...
       {type === "draw" && (
         <DrawArena
           userId={user._id}
           participants={participants}
           isSpectator={isSpectator}
-          elements={elements}
-          cursors={cursors}
+          elements={drawElements}
+          cursors={drawCursors}
           onElementsChange={sendElements}
           onCursorMove={sendCursor}
           prompt={prompt}
         />
       )}

       {type === "code" && (
         <CodeArena
           userId={user._id}
           participants={participants}
           isSpectator={isSpectator}
           codeByParticipant={codeByParticipant}
           testResultsByParticipant={testResultsByParticipant}
-          cursors={cursors}
+          cursors={codeCursors}
           onCodeChange={(code) => {
-            console.log("Code changed:", code.length, "chars")
+            sendAction({ type: "code_update", code } as ClientAction<"code">)
           }}
           onCursorMove={(line, col) => {
             sendCursor(line, col)
           }}
           prompt={prompt}
         />
       )}

       {type === "typing" && (
         <TypingArena
           userId={user._id}
           participants={participants}
           isSpectator={isSpectator}
           prompt={prompt}
           progressByParticipant={typingProgress}
           onProgressUpdate={(progress) => {
-            console.log("Progress update:", progress)
+            sendAction({ type: "progress", progress } as ClientAction<"typing">)
           }}
         />
       )}

Import types from ~/shared/arena-protocol: ArenaData, DrawData, CodeData, TypingData.

🤖 Prompt for AI Agents
In `@src/routes/arena/-components/arena-active.tsx` around lines 56 - 68,
Destructure data from the useArenaSocket call (const { data, connectionState,
participants, timeRemaining, error, sendAction } = useArenaSocket(...)) and
import ArenaData, DrawData, CodeData, TypingData from ~/shared/arena-protocol;
then use a type guard on data?.type to narrow to DrawData/CodeData/TypingData
and derive the arena-specific props (for draw: elements, cursors,
sendElements/sendCursor wrappers that call sendAction with the draw action
shape; for code: codeByParticipant, testResultsByParticipant and a
sendCode/sendTest wrapper; for typing: typingProgress and sendTyping wrapper).
Keep handleArenaEvent and sendAction usage but wrap sendAction into per-arena
helper functions to translate UI payloads into the server action format and
ensure all referenced variables (elements, cursors, sendElements, sendCursor,
codeByParticipant, testResultsByParticipant, typingProgress) are defined based
on the narrowed data.

@muzzlol

muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner Author

@greptile it's clearly stated that arena-active is a WIP, can you review the changes we did make?

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 5, 2026

Copy link
Copy Markdown
Additional Comments (2)

src/routes/arena/-components/arena-active.tsx
Type mismatch for onGameOver
useArenaSocket now calls onGameOver with (reason: ArenaEndReason, results: ArenaResults), but handleGameOver is still typed as (reason: string). This will be a TS error and will also drop the results payload at runtime.

  const handleGameOver = useCallback((reason: ArenaEndReason, results: ArenaResults) => {
    console.log("Game over:", reason, results)
  }, [])
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 52:55

Comment:
**Type mismatch for onGameOver**
`useArenaSocket` now calls `onGameOver` with `(reason: ArenaEndReason, results: ArenaResults)`, but `handleGameOver` is still typed as `(reason: string)`. This will be a TS error and will also drop the `results` payload at runtime.

```suggestion
  const handleGameOver = useCallback((reason: ArenaEndReason, results: ArenaResults) => {
    console.log("Game over:", reason, results)
  }, [])
```

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
Broken props after refactor
This component still references elements, cursors, sendElements, and sendCursor, but the new useArenaSocket return type no longer provides them (it returns data and sendAction). As-is, this won’t compile.

Fix by either (a) reintroducing these fields in the hook, or (b) update ArenaActive to derive elements/cursors from state.data and send actions via sendAction (e.g. sendAction({ type: "cursor", x, y })).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 146:156

Comment:
**Broken props after refactor**
This component still references `elements`, `cursors`, `sendElements`, and `sendCursor`, but the new `useArenaSocket` return type no longer provides them (it returns `data` and `sendAction`). As-is, this won’t compile.

Fix by either (a) reintroducing these fields in the hook, or (b) update `ArenaActive` to derive `elements/cursors` from `state.data` and send actions via `sendAction` (e.g. `sendAction({ type: "cursor", x, y })`).

How can I resolve this? If you propose a fix, please make it concise.

@muzzlol

muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner Author

@greptile re-review

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +128 to +131
default: {
const _exhaustive: never = message
throw new Error(`Unknown message type: ${_exhaustive}`)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Throwing on unknown message
JSON.parse(event.data) is a network payload, but it’s cast to ServerMsg<T> and the default switch branch throws. If the server ever sends an unexpected/extra message type (e.g., protocol mismatch during rollout), this will throw from the websocket handler and can break the arena UI.

Consider ignoring/logging unknown messages instead of throwing, or explicitly handle all message variants you actually send.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-hooks/use-arena-socket.ts
Line: 128:131

Comment:
**Throwing on unknown message**
`JSON.parse(event.data)` is a network payload, but it’s cast to `ServerMsg<T>` and the `default` switch branch throws. If the server ever sends an unexpected/extra message type (e.g., protocol mismatch during rollout), this will throw from the websocket handler and can break the arena UI.

Consider ignoring/logging unknown messages instead of throwing, or explicitly handle all message variants you actually send.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

good catch

@greptile-apps

greptile-apps Bot commented Feb 5, 2026

Copy link
Copy Markdown
Additional Comments (1)

src/routes/arena/-components/arena-active.tsx
Stale socket API references
This component still uses elements, cursors, sendElements, and sendCursor, but the updated useArenaSocket return value no longer provides these (it returns data + sendAction). As written, this should fail typecheck/build.

You’ll need to finish the migration here (derive draw/code/typing props from data and/or wire sendAction), or revert these usages until the new API is fully adopted.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 146:156

Comment:
**Stale socket API references**
This component still uses `elements`, `cursors`, `sendElements`, and `sendCursor`, but the updated `useArenaSocket` return value no longer provides these (it returns `data` + `sendAction`). As written, this should fail typecheck/build.

You’ll need to finish the migration here (derive draw/code/typing props from `data` and/or wire `sendAction`), or revert these usages until the new API is fully adopted.

How can I resolve this? If you propose a fix, please make it concise.

@muzzlol

muzzlol commented Feb 5, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit
it's clearly stated that arena-active is a WIP, can you review the changes we did make?

@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown

@muzzlol, I'll review the completed changes while skipping the WIP arena-active component.

Key Changes Review

1. arenas/server.ts - Mechanic Event Broadcasting ✅

The introduction of broadcastMechanic is a good abstraction that:

  • Centralizes mechanic event wrapping with consistent { type: "mechanic", event } structure
  • Reduces duplication in cursor and canvas update handlers
  • Makes the intent clearer

Observation: The method signature uses ArenaEvent<ArenaType> which is appropriately generic.

2. shared/arena-protocol.ts - Protocol Refactoring ✅

The type system improvements are solid:

  • Clear separation between server messages (ServerMsg<T>) and arena events (ArenaEvent<T>)
  • Type safety with discriminated unions using the type field
  • Arena-type-specific event mapping through ArenaEventMap

Question: I notice ClientMsg<T> was removed in the summary but I see references to it in the hook. Can you confirm if ClientMsg<T> still exists in the protocol file?

3. use-arena-socket.ts - Hook Genericization 🎯

The genericization is well-executed:

  • Type-safe arena data handling with ArenaData<T>
  • Unified action sending via sendAction(action: ClientAction<T>)
  • Proper ReadyState exposure for connection management
  • Callback pattern with onArenaEvent is flexible

Suggestions:

  1. Consider adding error boundaries for the onArenaEvent callback to prevent crashes from consumer bugs
  2. The exhaustive switch default case is good, but you might want to log unexpected message types for debugging

Minor: The onOpen handler still has a TODO comment about initial state sync - is this being tracked?

Overall Assessment

This is a solid refactoring that improves:

  • Type Safety: Generic types ensure arena-specific data is properly typed
  • Separation of Concerns: Mechanic events vs control messages are now distinct
  • API Simplicity: Single sendAction vs multiple send methods reduces cognitive load

The changes are well-structured and should make adding new arena types easier going forward.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@muzzlol
muzzlol marked this pull request as ready for review February 6, 2026 13:04
@muzzlol
muzzlol merged commit e9d733d into main Feb 6, 2026
1 check was pending

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 6, 2026

Copy link
Copy Markdown
Additional Comments (7)

src/routes/arena/-components/arena-active.tsx
reason param should be ArenaEndReason, not string

  const handleGameOver = useCallback((reason: ArenaEndReason, results: ArenaResults) => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 52:52

Comment:
`reason` param should be `ArenaEndReason`, not `string`

```suggestion
  const handleGameOver = useCallback((reason: ArenaEndReason, results: ArenaResults) => {
```

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
elements is not defined - was removed in refactor but still referenced here

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 153:153

Comment:
`elements` is not defined - was removed in refactor but still referenced here

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
cursors is not defined - was removed in refactor but still referenced here

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 154:154

Comment:
`cursors` is not defined - was removed in refactor but still referenced here

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
sendElements and sendCursor no longer exist - need to use sendAction instead

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 155:156

Comment:
`sendElements` and `sendCursor` no longer exist - need to use `sendAction` instead

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
codeByParticipant, testResultsByParticipant, and cursors are undefined - removed in refactor but still referenced

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 166:168

Comment:
`codeByParticipant`, `testResultsByParticipant`, and `cursors` are undefined - removed in refactor but still referenced

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
sendCursor no longer exists

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 175:176

Comment:
`sendCursor` no longer exists

How can I resolve this? If you propose a fix, please make it concise.

src/routes/arena/-components/arena-active.tsx
typingProgress is not defined - was removed in refactor but still referenced here

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/arena/-components/arena-active.tsx
Line: 187:187

Comment:
`typingProgress` is not defined - was removed in refactor but still referenced here

How can I resolve this? If you propose a fix, please make it concise.

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.

1 participant