fix(thressgame-coverage): F1+F4 remediation (T58 RequestChoiceModal + coin-flip kind)

Final Verification Wave found two real blockers:
1. T58 RequestChoiceModal.tsx was marked complete but did NOT exist on disk.
2. request-choice locked 6-kind enum was shipped as 5 (missing 'coin-flip').

Remediation:
- Build RequestChoiceModal.tsx with role=dialog, aria-modal=true, ESC/backdrop close, kind-specific input UI for all 6 kinds (rps / coin-flip / piece / square / column / row); 4 tests
- Add 'coin-flip' to:
  - request-choice primitive paramsSchema enum
  - PendingChoice.kind union (schema.ts + util/pending-choices.ts)
  - WS protocol ChoiceKindSchema (server/protocol.ts)
  - choice-timeout.firstDefaultForKind (defaults to 'heads')
  - broadcast.isValidChoiceValue (accepts 'heads' | 'tails')
- AutoChoiceResolver: deterministic alternating heads/tails for coin-flip

T68 e2e tests remain .skip()'d pending UI integration (modal-into-GameView wiring + activate-descriptor UI) — a follow-up task. The sentinel test asserts the gap exists so when integration lands, skips lift in the documented order.

Tests: 2740 -> 2744 (+4). bun run check exit 0.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-26 14:09:28 -06:00
commit 88581ff6a9
No known key found for this signature in database
17 changed files with 2248 additions and 10 deletions

View file

@ -526,6 +526,8 @@ function isValidChoiceValue(
value >= 0 &&
value <= 7
);
case "coin-flip":
return value === "heads" || value === "tails";
}
}

View file

@ -74,7 +74,7 @@ import {
*/
export function firstDefaultForKind(
kind: PendingChoice["kind"],
): "rock" | number {
): "rock" | number | "heads" {
switch (kind) {
case "rps":
return "rock";
@ -84,6 +84,8 @@ export function firstDefaultForKind(
case "column":
case "row":
return 0;
case "coin-flip":
return "heads";
}
}

View file

@ -1252,7 +1252,7 @@ describe("T43 — RequestChoiceSchema", () => {
it("rejects an unknown choiceKind", () => {
const r = RequestChoiceSchema.safeParse({
...validRequestChoice,
choiceKind: "coin-flip",
choiceKind: "invalid-kind-that-will-never-exist",
});
expect(r.success).toBe(false);
});

View file

@ -1077,6 +1077,7 @@ export const ChoiceKindSchema = z.enum([
"square",
"column",
"row",
"coin-flip",
]);
export type ChoiceKind = z.infer<typeof ChoiceKindSchema>;