Skip to content

[DevEx]: Convert the game store to TypeScript #1427

Open

Description

@itsalaidbacklife

Part of #1399. The last and largest of six store issues. Do this after #1422, #1423, #1424, #1425, #1426 — the conventions and the shared interfaces should be settled before touching this file.

Summary

Convert src/stores/game.js to TypeScript. 935 lines, 36 ref declarations, 54 functions — the largest and highest-value file on the frontend, and the source of most of the unknown types that surface as errors in components.

Why this is the one that matters

The untyped-prop errors across the component tree mostly originate here. spectatingUsers is unknown inside SpectatorListMenu.vue because it is ref([]) in this store. Typing the prop fixes the symptom at one component; typing the store fixes it at the source for every consumer.

The state is declared almost entirely as empty or null literals:

const id = ref(null);
const chat = ref([]);
const deck = ref([]);
const log = ref([]);
const players = ref([]);
const spectatingUsers = ref([]);
const scrap = ref([]);
const twos = ref([]);
const oneOff = ref(null);
...

so the checker learns nothing about any of them.

The real work is the payload boundary

updateGame(newGame) takes an untyped parameter and fans it out across ~20 assignments:

spectatingUsers.value = newGame.spectatingUsers ?? spectatingUsers.value;
players.value = newGame.players?.map((player) => setPlayers(player)) ?? players.value;
oneOff.value = createGameCard(newGame.oneOff) ?? null;
status.value = newGame.status ?? GameStatus.ARCHIVED;

Every field access on newGame is any. Declaring that payload shape is most of this issue, and it is where the value is: a renamed or mistyped field is invisible today and would be caught immediately once declared. That is structurally the bug that blanked the game log for months in #1386.

docs/game-state-api.md describes the socket payload and the GameState shape, and was refreshed in #1398, so its enum lists are current. It is the right starting reference — but verify against api/helpers/game-states/create-socket-events.js, which is what actually constructs the payload.

Suggested sequencing

Reasonable to split across several PRs:

  1. The card/player shapes (createGameCard, setPlayers) — small, shared, and depended on by everything else
  2. The GameUpdate payload interface + updateGame
  3. The remaining state refs and the computeds
  4. The socket request functions

Coordination — this file is contended

Three open PRs touch src/stores/game.js: #1384 (fours rework), #1383 (socket helper refactor), #1378 (nines rework). A 935-line file conversion will conflict with all of them. Land those first, or agree a window. This is the main reason to sequence this issue last.

Acceptance

  • src/stores/game.js becomes src/stores/game.ts
  • The socket payload consumed by updateGame has a declared interface
  • No any left on state declarations; ref([]) / ref(null) replaced with explicit type parameters
  • All call sites still type-check
  • npm run build succeeds; full Cypress suite passes — this store backs the entire in-game experience, so the e2e suite is the real safety net

Why TS and not JSDoc

Measured: this store reports 0 errors under checkJs across 935 lines — silence, not correctness. Four realistic bugs injected into updateGame():

Bug checkJs +noImplicitAny TS
newGame.spectatorUsers — typo'd payload field ❌ ❌ ✅ TS2551 ... Did you mean 'spectatingUsers'?
passes.value = 'three' ✅ ✅ ✅
turn.value.toUpperCase() ✅ ✅ ✅
first.displayName on a username string ❌ ❌ ✅ TS2339: ... does not exist on type 'string'

noImplicitAny is not a cheap middle ground — it takes this file from 0 to 128 errors, all TS7006 "annotate this", while catching none of the missed bugs.

No build change required

Vite transpiles .ts today via esbuild, with no new dependency — verified, exit 0. esbuild only strips types, so checking comes from vue-tsc in CI (#1400, #1409).

Activity

  1. added
    dev experienceImprovements to the code base that make it easier/better/more enjoyable to contribute to Cuttle
    frontendRequires changes to the frontend (vue) client
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev experienceImprovements to the code base that make it easier/better/more enjoyable to contribute to CuttlefrontendRequires changes to the frontend (vue) client

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions