From 13ac74ab8660f9f24144411bdebcd8d71e38f92d Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 25 Feb 2026 19:35:34 -0500 Subject: [PATCH] fix(cash-in): prevent cancel after bills inserted in insertingBills state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cancel button was still accessible during insertingBills after a bill had been stacked (physically irreversible). Now CANCEL in insertingBills is guarded: no bills → idle, bills present → confirmAbandon warning. The UI also hides the cancel button once bills are detected. Co-Authored-By: Claude Opus 4.6 --- apps/machine/src/views/CashInView.vue | 5 +++-- packages/state-machine/src/machine.ts | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/machine/src/views/CashInView.vue b/apps/machine/src/views/CashInView.vue index 146c815..2ab193f 100644 --- a/apps/machine/src/views/CashInView.vue +++ b/apps/machine/src/views/CashInView.vue @@ -20,8 +20,9 @@ const isElectron = typeof window !== 'undefined' && window.electronAPI !== undef const showCancelButton = computed(() => { const state = atmStore.nestedState const hasBills = (atmStore.context?.billsInserted.length ?? 0) > 0 - // Always show in insertingBills (before bills) and error (without bills) - if (state === 'insertingBills' || state === 'fetchingRate') return true + // Show in fetchingRate (no bills yet) and insertingBills only before bills are in + if (state === 'fetchingRate') return true + if (state === 'insertingBills' && !hasBills) return true if (state === 'error' && !hasBills) return true // Hide when bills are in the cash box return false diff --git a/packages/state-machine/src/machine.ts b/packages/state-machine/src/machine.ts index c2cc434..02edb04 100644 --- a/packages/state-machine/src/machine.ts +++ b/packages/state-machine/src/machine.ts @@ -426,8 +426,26 @@ export function createATMMachine( guard: 'hasInsertedBills', target: 'generatingNdebit', }, - CANCEL: '#atm.idle', - TIMEOUT: '#atm.idle', + CANCEL: [ + { + // No bills inserted yet: safe to cancel + guard: ({ context }) => context.billsInserted.length === 0, + target: '#atm.idle', + }, + { + // Bills already stacked: warn user before abandoning + target: 'confirmAbandon', + }, + ], + TIMEOUT: [ + { + guard: ({ context }) => context.billsInserted.length === 0, + target: '#atm.idle', + }, + { + target: 'confirmAbandon', + }, + ], }, }, generatingNdebit: {