> ## Documentation Index
> Fetch the complete documentation index at: https://hedgeem-api.qeetoto.com/llms.txt
> Use this file to discover all available pages before exploring further.

# F012 — Bet Cancellation

> Cancelling pending bets before they are committed to the server, restoring the display to the last confirmed API state

# F012 — Bet Cancellation

| Field  | Value               |
| ------ | ------------------- |
| ID     | F012                |
| Phase  | 2 — Betting         |
| Jira   | HEDGE-50            |
| Status | TS ✅ · JS ⬜ · UMA ✅ |

## Business Rule

At any point during a betting window (HOLE, FLOP, TURN), the player may cancel pending bets. Cancellation:

* Removes pending bets from the display
* Restores the game state to whatever has been committed to the server

Bets are only deducted from credits / committed to the server at different points per version:

* **JS / TS**: Bet is reflected immediately in UI; TS deducts credits immediately, JS deducts in batch at `CC_DEDUCT_BETS`
* **UMA**: Bets are NOT committed to the API until the Deal button (spin) is pressed; cancel before Deal results in zero committed bets for the current stage

## JavaScript Reference

**Source:** `hedgeem-v4/odobo/src/js/buttons.js`

* `CancelButtonPressed()` calls `ClearRoundBets()` which zeros `handBetValue[hand][GetDealStatus()]` for all hands at the current deal stage.
* Credits have not yet been deducted (deduction happens at `CC_DEDUCT_BETS`), so cancel is a pure UI reset.
* Cancel scope: **current stage only, all hands**

## TypeScript Implementation

**Source:** `standalone_reference_client/src/engine/GameEngine.ts`

* Per-hand cancel button calls `cancelBets(handIndex)` which removes bets for that hand from all stages and refunds credits immediately.
* Cancel scope: **all stages, per-hand** (intentional difference from JS — see F011 design differences)

## UMA Implementation

**Source:** `gameClient/builds/hedgeem/platform.js`

Cancel is triggered when `game.min.js` sends a `connect:GameRequest` with any non-spin action (typically `action: 'cancel'`). Platform.js responds by:

1. Fetching `/api/tables/{tableId}/state` (the current committed server state)
2. Rebuilding `buildGameStatus(currentHoleState, state)` from the API response
3. Re-publishing `Api:action:dataReceived` with the refreshed state
4. Setting `window._cancelRefreshDone = true`

Since bets are only sent to the API at spin time (Deal button press), cancel before any spin always produces a zero-bet state. Cancel after a spin preserves any bets already committed in prior stages.

```javascript theme={null}
// platform.js ~line 545
} else if (ps.type === 'connect:GameRequest') {
  fetchAPI('/api/tables/' + TABLE_ID + '/state', 'GET')
    .then(function (state) {
      var gdkData = buildGameStatus(currentHoleState, state);
      var players = gdkData.publicState.gameData[0].gameExtra.player;
      window._lastStages = players ? players.map(function (p) { return p.stages; }) : [];
      window._cancelRefreshDone = true;
      GDK.publish('Api:action:dataReceived', gdkData);
    });
}
```

**Test helper:** `window._platformApi.triggerCancel()` fires the cancel packet directly, bypassing the need to click the GDK cancel button.

## Acceptance Criteria

| #   | Criterion                                                    | JS  | TS | UMA |
| --- | ------------------------------------------------------------ | --- | -- | --- |
| AC1 | Cancel clears pending bets from display                      | ✅   | ✅  | ✅   |
| AC2 | Cancel before first Deal leaves all bet amounts at 0         | ✅   | ✅  | ✅   |
| AC3 | After cancel, game state reflects only committed server bets | N/A | ✅  | ✅   |
| AC4 | Cancel is available at HOLE, FLOP, and TURN betting windows  | ✅   | ✅  | ✅   |
| AC5 | Cancel does not advance the game stage                       | ✅   | ✅  | ✅   |

### UMA-testable ACs (via state bridge)

| #       | UMA Criterion                                                                | Notes                                     |
| ------- | ---------------------------------------------------------------------------- | ----------------------------------------- |
| AC1-UMA | After `triggerCancel()`, `window._cancelRefreshDone` becomes `true`          | Platform.js sets this after state refresh |
| AC2-UMA | After cancel at HOLE (before any spin), all `_lastStages[i][last].bet === 0` | No API bets committed before spin         |
| AC3-UMA | `_lastStages` remains defined with correct hand count after cancel           | State not wiped, just refreshed           |
| AC4-UMA | After cancel at FLOP (no bets committed), `_lastStages[i][last].bet === 0`   | Still no committed bets                   |
| AC5-UMA | `_stagePhase` does not change after cancel                                   | Cancel must not advance the stage         |

## Version Parity

| Version        | Status        | Notes                                                                              |
| -------------- | ------------- | ---------------------------------------------------------------------------------- |
| JS (reference) | ⬜ Not audited | `ClearRoundBets()` per-stage all-hands cancel                                      |
| TS             | ✅ Verified    | `cancelBets(handIndex)` — per-hand all-stages cancel; credits refunded immediately |
| UMA            | ✅ Verified    | `tests/features/F012-bet-cancellation.spec.ts` — AC1–AC5 (UMA) pass (2026-04-15)   |

## Test Coverage

Playwright spec: `tests/features/F012-bet-cancellation.spec.ts` — AC1–AC5 (UMA).

Run with:

```bash theme={null}
VERSION=uma npx playwright test tests/features/F012-bet-cancellation.spec.ts --project=features --workers=1
```

## Related Features

* F011 — Bet Placement (bets that cancel would remove)
* F010 — Chip Denominations (the chip value that was pending)
* F016 — Credits Wallet (TS only: cancel refunds credits immediately)
