1
0
Fork 0

fix(poker): dev panel card pickers ignored the first selection

CardPicker propagated null into onChange when no card was selected yet,
so the board picker's parent coerced every first pick to 2 of spades and
the hole-card pickers could never hold a value, keeping Queue hand
disabled forever. Picks now build a complete card, preserving any
already-chosen half, with regression tests on the extracted helper.

Co-authored-by: Sisyphus <sisyphus@ohmyopencode.dev>
This commit is contained in:
Jason Fraley 2026-09-16 19:10:37 -04:00
parent 074fdb02bf
commit dce6f8f2ee
2 changed files with 37 additions and 13 deletions

View file

@ -58,6 +58,24 @@ function cardLabel(card: Card): string {
return `${rank} ${suit}`;
}
/**
* Builds a complete card from one picker interaction: an empty selection
* starts from the 2-of-spades baseline for the not-yet-chosen half, and any
* already-chosen half is preserved. Returning a full card (never null) is
* what lets a single pick land; the previous null-propagating version made
* first picks no-ops and hole-card queuing impossible.
*/
export function applyCardPick(
value: Card | null,
part: "rank" | "suit",
next: string,
): Card {
if (part === "rank") {
return { rank: Number(next) as Rank, suit: value?.suit ?? "s" };
}
return { rank: value?.rank ?? 2, suit: next as Suit };
}
function CardPicker({
label,
value,
@ -67,13 +85,13 @@ function CardPicker({
readonly value: Card | null;
readonly onChange: (card: Card | null) => void;
}) {
const setRank = (next: string) => onChange(applyCardPick(value, "rank", next));
const setSuit = (next: string) => onChange(applyCardPick(value, "suit", next));
return (
<div className="flex items-center gap-1.5">
<span className="w-10 text-xs text-muted-foreground">{label}</span>
<Select
value={value ? String(value.rank) : ""}
onValueChange={(next) => onChange(value ? { ...value, rank: Number(next) as Rank } : null)}
>
<Select value={value ? String(value.rank) : ""} onValueChange={setRank}>
<SelectTrigger className="h-8 w-20 text-xs">
<SelectValue placeholder="Rank" />
</SelectTrigger>
@ -85,10 +103,7 @@ function CardPicker({
))}
</SelectContent>
</Select>
<Select
value={value?.suit ?? ""}
onValueChange={(next) => onChange(value ? { ...value, suit: next as Suit } : null)}
>
<Select value={value?.suit ?? ""} onValueChange={setSuit}>
<SelectTrigger className="h-8 w-24 text-xs">
<SelectValue placeholder="Suit" />
</SelectTrigger>
@ -191,11 +206,7 @@ export function PokerDevPanel({
<section className="flex flex-col gap-2 border border-border p-3">
<h3 className="text-xs font-semibold uppercase tracking-wider">Board queue</h3>
<div className="flex flex-col gap-1.5">
<CardPicker
label="Card"
value={boardDraft}
onChange={(card) => setBoardDraft(card ?? { rank: 2, suit: "s" })}
/>
<CardPicker label="Card" value={boardDraft} onChange={setBoardDraft} />
<Button
type="button"
variant="outline"

View file

@ -6,6 +6,7 @@ import {
type PokerTableState,
} from "@/lib/minigames/poker";
import type { Card } from "@/lib/minigames/poker";
import { applyCardPick } from "@/components/frontend/minigames/poker/PokerDevPanel";
import {
adjustStack,
createTable,
@ -148,3 +149,15 @@ describe("dev stack adjustment", () => {
expect(() => adjustStack(tableId, 9611, 9, 100)).toThrow();
});
});
describe("dev panel card picker", () => {
it("builds a complete card from a first rank pick and preserves the chosen suit", () => {
expect(applyCardPick(null, "rank", "13")).toEqual(card(13, "s"));
expect(applyCardPick(card(13, "h"), "rank", "14")).toEqual(card(14, "h"));
});
it("builds a complete card from a first suit pick and preserves the chosen rank", () => {
expect(applyCardPick(null, "suit", "c")).toEqual(card(2, "c"));
expect(applyCardPick(card(14, "h"), "suit", "d")).toEqual(card(14, "d"));
});
});