Skip to content

fix(client): guard additionalCostChoices Choice branch against a short payload#5175

Open
jeffrey701 wants to merge 1 commit into
phase-rs:mainfrom
jeffrey701:fix/costlabel-choice-guard
Open

fix(client): guard additionalCostChoices Choice branch against a short payload#5175
jeffrey701 wants to merge 1 commit into
phase-rs:mainfrom
jeffrey701:fix/costlabel-choice-guard

Conversation

@jeffrey701

Copy link
Copy Markdown
Contributor

Problem

additionalCostChoices (client/src/viewmodel/costLabel.ts) formats the Choice branch by indexing straight into the cost tuple:

case "Choice":
  return { title: "Choose additional cost", options: [
    { id: "pay", label: formatAbilityCost(cost.data[0]) },
    { id: "decline", label: formatAbilityCost(cost.data[1]) },
  ] };

data is typed [SerializedAbilityCost, SerializedAbilityCost], but it is deserialized from the engine. A malformed/short payload leaves cost.data[1] undefined; formatAbilityCost switches on cost.type, so undefined.type throws a TypeError and hard-crashes the cost-choice modal instead of degrading. The sibling Kicker branch already guards exactly this (const label = first ? … : "kicker").

Fix

Destructure and guard each option with a fallback label ("Pay" / "Decline").

Test

client/src/viewmodel/__tests__/costLabel.test.ts — a well-formed two-cost Choice still labels both options; a short payload no longer throws and falls back to "Decline".

@jeffrey701 jeffrey701 requested a review from matthewevans as a code owner July 6, 2026 04:41
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@jeffrey701 jeffrey701 force-pushed the fix/costlabel-choice-guard branch from 93ab267 to 23aa004 Compare July 6, 2026 08:34
…t payload

The "Choice" additional-cost branch formats cost.data[0] and cost.data[1]
directly. The type is a 2-tuple, but data is deserialized from the engine, so a
malformed/short payload leaves cost.data[1] undefined. formatAbilityCost
switches on cost.type, so passing undefined throws a TypeError and hard-crashes
the cost-choice modal instead of degrading. Guard each option with a fallback
label, mirroring the sibling Kicker branch's existing defense.
@jeffrey701 jeffrey701 force-pushed the fix/costlabel-choice-guard branch from 23aa004 to 5a30d6e Compare July 6, 2026 09:05
@matthewevans matthewevans added the defer-fe Frontend/client/UI PR deferred to Matt's direct review label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

defer-fe Frontend/client/UI PR deferred to Matt's direct review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants