Skip to content

making arena types exhaustive and updating non-drafted implementations#12

Merged
muzzlol merged 2 commits into
mainfrom
fix/make-types-exhaustive
Jan 12, 2026
Merged

making arena types exhaustive and updating non-drafted implementations#12
muzzlol merged 2 commits into
mainfrom
fix/make-types-exhaustive

Conversation

@muzzlol

@muzzlol muzzlol commented Jan 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Cursor position now includes an optional timestamp for more precise interaction tracking.
  • Improvements

    • More robust arena data initialization and player-data cleanup across arena types.
    • Stricter arena configuration typing for safer, clearer behavior.
    • Server state messages standardized (payload field renamed to "data") for consistent client handling.

✏️ Tip: You can customize this high-level summary in your review settings.

Greptile Summary

Refactors arena type system from conditional types to mapped object types (ArenaDataMap, ActionMap, EventMap), adds exhaustive switch checks in helper functions, and moves createEmptyData and removePlayerDataHelper from the server into the shared protocol module. The ServerMsg "state" payload field is renamed from arenaState to data.

  • ArenaConfig is now generic (ArenaConfig<T>) but lacks a default type parameter, which will cause TypeScript compilation errors in src/routes/arena/-hooks/use-arena-socket.ts where ArenaConfig, ServerMsg, and ClientMsg are imported without type arguments.
  • The exhaustive switch patterns with never checks are a good improvement for catching future arena type additions at compile time.

Confidence Score: 2/5

  • This PR introduces breaking changes to shared types that will prevent client-side code from compiling
  • The type system improvements (mapped types, exhaustive switches) are solid, but making ArenaConfig generic without a default type parameter breaks all existing consumers that don't supply a type argument. The client hook file (use-arena-socket.ts) uses ArenaConfig, ServerMsg, and ClientMsg without type parameters, which will fail TypeScript strict compilation. Combined with previously-noted issues (testResults cleanup regression, arenaState→data rename not reflected in client), this PR needs additional work before merging.
  • shared/arena-protocol.ts needs default type parameters on generic types; src/routes/arena/-hooks/use-arena-socket.ts (not in PR) needs updates to match the new protocol

Important Files Changed

Filename Overview
shared/arena-protocol.ts Refactored arena types to use mapped types (ArenaDataMap, ActionMap, EventMap) with exhaustive switches. Renamed arenaState to data in ServerMsg. Made ArenaConfig generic. Moved helper functions here from server. However, ArenaConfig lacks a default type parameter, breaking client code that uses it without a type argument.
arenas/server.ts Simplified server by delegating data initialization and player cleanup to shared helpers. Uses ArenaConfig<T> for stricter typing. Renamed state message field from arenaState to data. Server-side changes look correct.

Flowchart

flowchart TD
    subgraph SharedProtocol["shared/arena-protocol.ts"]
        ArenaDataMap["ArenaDataMap\n{draw: DrawData, code: CodeData, typing: TypingData}"]
        ActionMap["ActionMap\n{draw: CursorUpdate|CanvasUpdate, ...}"]
        EventMap["EventMap\n{draw: Attributed&lt;...&gt;, ...}"]
        createEmpty["createEmptyData(type)\nexhaustive switch"]
        removePlayer["removePlayerDataHelper(id, type, data)\nexhaustive switch"]
    end

    subgraph Server["arenas/server.ts"]
        handleInit["handleInit()\nconfig → createEmptyData()"]
        removeData["removePlayerData()\n→ removePlayerDataHelper()"]
        sendState["sendState()\n{type: 'state', data: ...}"]
    end

    subgraph Client["use-arena-socket.ts (NOT UPDATED)"]
        onMessage["onMessage()\nexpects message.elements ❌\nneeds message.data ✓"]
        types["Uses ArenaConfig, ServerMsg\nwithout type params ❌"]
    end

    handleInit -->|calls| createEmpty
    removeData -->|calls| removePlayer
    handleInit --> sendState
    sendState -->|WebSocket| onMessage
Loading

Last reviewed commit: f485bce

Context used:

  • Context from dashboard - AGENTS.md (source)

@coderabbitai

coderabbitai Bot commented Jan 11, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Refactors arena protocol types to be generic and map-based, moves createEmptyData and removePlayerDataHelper to shared/arena-protocol, renames server message field arenaStatedata, and updates arenas/server.ts to use the shared helpers and tightened generics.

Changes

Cohort / File(s) Summary
Shared arena protocol
shared/arena-protocol.ts
Introduced generic ArenaConfig<T> and ArenaDataMap with ArenaData<T> = ArenaDataMap[T]. Added ActionMap and EventMap, optional timestamp on CursorPos, renamed ServerMsg payload arenaStatedata, and exported helpers createEmptyData<T> and removePlayerDataHelper.
Server usage and state
arenas/server.ts
Removed local createEmptyData and CodeData usage; imported createEmptyData and removePlayerDataHelper from shared protocol. Tightened ArenaState.config to `ArenaConfig

Sequence Diagram(s)

(skipped — changes are primarily type/system refactors and helper relocation without new multi-component control flow)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through types with keen delight,

Mapped arenas snugly, made generics right,
Shared helpers bundled for every race,
A nimble hare leaving tidy trace,
🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: making arena types exhaustive through generics and map-based types, while updating implementations like arenas/server.ts and shared/arena-protocol.ts to use these stricter types.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fba7e44 and f485bce.

📒 Files selected for processing (1)
  • shared/arena-protocol.ts

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.

@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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

sdf

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
arenas/server.ts (1)

219-224: Make config optional in ClientMsg type.

The ClientMsg type declares config: ArenaConfig<T> as required, but the actual protocol semantics require it only when the first player initializes the arena. Non-host players joining an existing arena don't send config. Change line 106 in shared/arena-protocol.ts to config?: ArenaConfig<T> to match the runtime behavior where the handler checks if (!config) on line 220.

🤖 Fix all issues with AI agents
In @shared/arena-protocol.ts:
- Around line 105-108: The init variant of the ClientMsg union requires config:
ArenaConfig<T> even though only hosts send config; change the init payload in
ClientMsg to make config optional (config?: ArenaConfig<T>) so non-host clients
can send init without config, and update any consumers (e.g., arenas/server.ts
handlers) to assert/validate that config is present when acting as host (or when
creating a new arena) and handle its absence for joiners. Ensure references to
ClientMsg, the "init" case, and ArenaConfig<T> are updated accordingly.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 380dc62 and fba7e44.

📒 Files selected for processing (2)
  • arenas/server.ts
  • shared/arena-protocol.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/alchemy_cloudflare.mdc)

Durable Object classes must extend DurableObject from 'cloudflare:workers' and implement async methods for state management

**/*.{ts,tsx}: Make use of function guards whenever possible in TypeScript
Keep logic in one function unless the code is composable or reusable in TypeScript
Resolve all TypeScript errors - type checking is strict
Do not use unnecessary destructuring of variables in TypeScript
Do not use else statements unless necessary in TypeScript
Do not use try/catch if it can be avoided in TypeScript
Avoid using any type in TypeScript
Avoid let statements in TypeScript - prefer const
Prefer single word variable names where possible in TypeScript
Only create an abstraction if it is actually needed in TypeScript
Prefer clear function/variable names over inline comments in TypeScript

Files:

  • arenas/server.ts
  • shared/arena-protocol.ts
🧠 Learnings (1)
📚 Learning: 2025-12-16T09:34:02.880Z
Learnt from: muzzlol
Repo: muzzlol/spectra PR: 9
File: arenas/server.ts:151-151
Timestamp: 2025-12-16T09:34:02.880Z
Learning: In arenas/server.ts, storage.put for element changes (handleElementChange) intentionally does not await to avoid performance bottlenecks from frequent updates; fire-and-forget persistence is acceptable for element state.

Applied to files:

  • arenas/server.ts
🧬 Code graph analysis (2)
arenas/server.ts (2)
convex/schema/arena.ts (1)
  • ArenaType (19-19)
shared/arena-protocol.ts (2)
  • ArenaConfig (25-32)
  • removePlayerDataHelper (187-211)
shared/arena-protocol.ts (1)
convex/schema/arena.ts (1)
  • ArenaType (19-19)
🔇 Additional comments (12)
arenas/server.ts (4)

16-20: LGTM!

Clean import of shared helpers createEmptyData and removePlayerDataHelper from the centralized arena-protocol module. This properly delegates data initialization and cleanup logic to shared utilities.


24-28: LGTM!

The generic ArenaConfig<T> type properly constrains the configuration to match the arena type parameter, improving type safety throughout the state management.


248-253: LGTM!

The field rename from arenaState to data is consistent with the updated ServerMsg type definition in the shared protocol.


401-407: LGTM!

Good use of guard pattern per coding guidelines. The delegation to removePlayerDataHelper properly centralizes type-specific cleanup logic. Based on learnings, the fire-and-forget storage.put is intentionally non-awaited to avoid performance bottlenecks.

shared/arena-protocol.ts (8)

21-21: LGTM!

The optional timestamp field is a clean, backward-compatible addition to CursorPos.


25-32: LGTM!

Making ArenaConfig generic with type: T enables proper type narrowing, so consumers can infer the specific arena type from the configuration.


36-42: LGTM!

The ArenaDataMap with indexed access ArenaData<T> = ArenaDataMap[T] is a clean, exhaustive pattern that ensures all arena types have corresponding data types defined.


91-101: LGTM!

Consistent mapped-type pattern for actions. The type aliases (DrawAction, CodeAction, TypingAction) maintain API compatibility while leveraging the centralized ActionMap.


112-122: LGTM!

The EventMap and ServerEvent<T> follow the same exhaustive mapped-type pattern, providing consistent server event typing across arena types.


135-141: LGTM!

The rename from arenaState to data is cleaner and the generic ArenaData<T> ensures type-safe state payloads.


165-185: LGTM!

Good use of exhaustive switch with never type for compile-time safety. The as ArenaData<T> casts are necessary due to TypeScript's limitation in narrowing generic parameters within switch cases.


187-211: Verify if additional player data should be removed.

The helper only removes playerCursors for draw/code arenas. When a player leaves, should their playerElements (draw) or playerCode (code) also be cleaned up, or is retaining this data intentional (e.g., for final results)?

Comment thread shared/arena-protocol.ts
Comment on lines 105 to 108
export type ClientMsg<T extends ArenaType> =
| { type: "init"; userId: string; username: string; config?: ArenaConfig }
| { type: "init"; userId: string; username: string; config: ArenaConfig<T> }
| { type: "leave" }
| ClientAction<T>

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 | 🟡 Minor

Consider making config optional for non-host players.

The init message now requires config: ArenaConfig<T>, but based on arenas/server.ts logic, only the host provides the config during initialization. Non-host players joining an existing arena would not have config to send.

Suggested fix
 export type ClientMsg<T extends ArenaType> =
-  | { type: "init"; userId: string; username: string; config: ArenaConfig<T> }
+  | { type: "init"; userId: string; username: string; config?: ArenaConfig<T> }
   | { type: "leave" }
   | ClientAction<T>
🤖 Prompt for AI Agents
In @shared/arena-protocol.ts around lines 105 - 108, The init variant of the
ClientMsg union requires config: ArenaConfig<T> even though only hosts send
config; change the init payload in ClientMsg to make config optional (config?:
ArenaConfig<T>) so non-host clients can send init without config, and update any
consumers (e.g., arenas/server.ts handlers) to assert/validate that config is
present when acting as host (or when creating a new arena) and handle its
absence for joiners. Ensure references to ClientMsg, the "init" case, and
ArenaConfig<T> are updated accordingly.

@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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@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.

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread shared/arena-protocol.ts Outdated

export type ClientMsg<T extends ArenaType> =
| { type: "init"; userId: string; username: string; config?: ArenaConfig }
| { type: "init"; userId: string; username: string; config: ArenaConfig<T> }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

config was changed from optional to required, but the server code still checks if (!config) at line 220 in arenas/server.ts. This breaks the initialization flow for players joining an already-initialized arena, who should not provide a config.

Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/arena-protocol.ts
Line: 106:106

Comment:
`config` was changed from optional to required, but the server code still checks `if (!config)` at line 220 in `arenas/server.ts`. This breaks the initialization flow for players joining an already-initialized arena, who should not provide a config.

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

Comment thread shared/arena-protocol.ts
Comment on lines +198 to +201
case "code": {
const codeData = data as CodeData
delete codeData.playerCursors[participantId]
return codeData

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing deletion of testResults[participantId]. The old implementation at arenas/server.ts line 412 deleted both testResults and playerCursors, but this only deletes playerCursors. This causes test results to persist after a player leaves.

Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/arena-protocol.ts
Line: 198:201

Comment:
Missing deletion of `testResults[participantId]`. The old implementation at `arenas/server.ts` line 412 deleted both `testResults` and `playerCursors`, but this only deletes `playerCursors`. This causes test results to persist after a player leaves.

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

Comment thread shared/arena-protocol.ts
type: "state"
participants: Participant[]
arenaState: ArenaData<T> | null
data: ArenaData<T> | null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Field renamed from arenaState to data, but the client code at src/routes/arena/-hooks/use-arena-socket.ts line 87 still references message.elements instead of message.data. This will cause the client to receive undefined for arena state.

Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/arena-protocol.ts
Line: 139:139

Comment:
Field renamed from `arenaState` to `data`, but the client code at `src/routes/arena/-hooks/use-arena-socket.ts` line 87 still references `message.elements` instead of `message.data`. This will cause the client to receive `undefined` for arena state.

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

@muzzlol
muzzlol merged commit 3aaaa47 into main Jan 12, 2026
1 of 3 checks passed
@muzzlol

muzzlol commented Jan 29, 2026

Copy link
Copy Markdown
Owner Author

@greptile can you re-review this pr

@greptile-apps

greptile-apps Bot commented Jan 29, 2026

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR refactors arena type handling to use exhaustive mapped types instead of conditional types, improving type safety. Helper functions createEmptyData and removePlayerDataHelper were moved to shared/arena-protocol.ts and the server state message field was renamed from arenaState to data.

Key Changes:

  • Replaced conditional arena types with mapped types (ArenaDataMap, ActionMap, EventMap) for exhaustive checking
  • Added exhaustive checks in switch statements using never type assignment
  • Moved helper functions to shared protocol file for reuse
  • Made ArenaConfig generic with type parameter <T extends ArenaType>
  • Renamed server state message field from arenaState to data
  • Added optional timestamp field to CursorPos type

Critical Issues:

  • The removePlayerDataHelper function is missing the testResults deletion for code arenas, causing test results to persist after players leave
  • The field rename breaks client compatibility - client code expects message.elements but server now sends message.data

Confidence Score: 1/5

  • This PR contains critical bugs that will break client-server communication and cause data leaks
  • Two critical logic errors exist: (1) missing testResults cleanup causes player data to persist after leaving, and (2) the arenaState to data field rename breaks client code that expects message.elements, causing clients to receive undefined arena state
  • Both shared/arena-protocol.ts (missing cleanup logic) and client code at src/routes/arena/-hooks/use-arena-socket.ts (outdated field reference) need immediate fixes

Important Files Changed

Filename Overview
arenas/server.ts Refactored to use shared helper functions from arena-protocol; renamed arenaState to data in state messages
shared/arena-protocol.ts Made arena types exhaustive using mapped types; added helper functions; renamed state field; missing testResults cleanup in helper

Sequence Diagram

sequenceDiagram
    participant Client
    participant ArenaWSS
    participant Helper as Protocol Helpers
    participant Storage
    
    Client->>ArenaWSS: init message (with config)
    ArenaWSS->>Helper: createEmptyData(arenaType)
    Helper-->>ArenaWSS: Empty arena data
    ArenaWSS->>Storage: Store state
    ArenaWSS->>Client: state message (data field)
    
    Note over Client,ArenaWSS: Player leaves
    Client->>ArenaWSS: WebSocket close
    ArenaWSS->>Helper: removePlayerDataHelper(participantId, type, data)
    Note over Helper: ⚠️ Missing testResults cleanup
    Helper-->>ArenaWSS: Updated data
    ArenaWSS->>Storage: Store updated state
    ArenaWSS->>Client: participant_left message
    
    Note over Client: ⚠️ Client expects message.elements
    Note over ArenaWSS: But server sends message.data
Loading

@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.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread shared/arena-protocol.ts
Comment on lines +199 to +200
const codeData = data as CodeData
delete codeData.playerCursors[participantId]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing testResults deletion - old code deleted both testResults[participantId] and playerCursors[participantId] but this only deletes cursors

Suggested change
const codeData = data as CodeData
delete codeData.playerCursors[participantId]
const codeData = data as CodeData
delete codeData.testResults[participantId]
delete codeData.playerCursors[participantId]
Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/arena-protocol.ts
Line: 199:200

Comment:
Missing `testResults` deletion - old code deleted both `testResults[participantId]` and `playerCursors[participantId]` but this only deletes cursors

```suggestion
      const codeData = data as CodeData
      delete codeData.testResults[participantId]
      delete codeData.playerCursors[participantId]
```

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

Comment thread shared/arena-protocol.ts
type: "state"
participants: Participant[]
arenaState: ArenaData<T> | null
data: ArenaData<T> | null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Field renamed from arenaState to data breaks client code at src/routes/arena/-hooks/use-arena-socket.ts:87 which expects message.elements

The client needs to be updated to use message.data instead of message.elements

Prompt To Fix With AI
This is a comment left during a code review.
Path: shared/arena-protocol.ts
Line: 139:139

Comment:
Field renamed from `arenaState` to `data` breaks client code at `src/routes/arena/-hooks/use-arena-socket.ts:87` which expects `message.elements`

The client needs to be updated to use `message.data` instead of `message.elements`

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

@muzzlol

muzzlol commented Jan 29, 2026

Copy link
Copy Markdown
Owner Author

@greptile can you tell me about the alchemy.ts file

@greptile-apps

greptile-apps Bot commented Jan 29, 2026

Copy link
Copy Markdown

The repository contains two alchemy-related files:

alchemy.run.ts - Main Infrastructure Configuration

This is the core infrastructure-as-code file that defines the entire Spectra deployment using the Alchemy framework. It sets up:

Main Resources:

  • Durable Object Namespace: arenas - Creates a namespace for the ArenaWSS durable objects with SQLite support
  • Arena Worker: arenaHost - A Cloudflare Worker that serves the arena backend (arenas/server.ts) with bindings for Convex integration
  • Website: website - A TanStack Start application deployed to spectra.muzzkhan.dev with environment bindings

Key Features:

  • Uses CloudflareStateStore for state management
  • Configures secrets for Convex URLs and service credentials
  • Sets up Turnstile site key for CAPTCHA
  • Includes automatic GitHub PR preview comments when PULL_REQUEST env var is set
  • Properly calls app.finalize() for resource cleanup

.cursor/rules/alchemy_cloudflare.mdc - Documentation & Guidelines

This is a comprehensive Cursor IDE rule file containing best practices and patterns for using Alchemy with Cloudflare. It covers:

  • Core concepts (resources, bindings, state management)
  • Worker patterns and type safety setup
  • Durable Object implementation examples
  • KV, R2, Queue resource configurations
  • Migration patterns from wrangler.toml to Alchemy
  • Advanced patterns like framework integration and resource scoping
  • Best practices and common pitfalls

The infrastructure follows modern TypeScript-native IaC patterns, with the main alchemy.run.ts handling the actual deployment configuration while the rules file provides guidance for development.

@muzzlol

muzzlol commented Feb 18, 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.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread shared/arena-protocol.ts
Comment thread shared/arena-protocol.ts
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