feat(state-machine): add inactivity timeouts to prevent stuck screens

insertingBills and selectingAmount now auto-idle after 3 minutes of
inactivity. displayingInvoice returns to amount selection after 5
minutes if the customer never pays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-31 11:11:53 -04:00
commit 5fa2dcb992

View file

@ -390,6 +390,8 @@ export function createATMMachine(
}, },
delays: { delays: {
TIMEOUT_MS: 300000, // 5 minutes TIMEOUT_MS: 300000, // 5 minutes
INACTIVITY_TIMEOUT: 180000, // 3 minutes — no-interaction screens
INVOICE_TIMEOUT: 300000, // 5 minutes — waiting for payment
COMPLETE_DELAY: 60000, COMPLETE_DELAY: 60000,
DISPENSE_TIMEOUT: 120000, // 2 min max for hardware to respond DISPENSE_TIMEOUT: 120000, // 2 min max for hardware to respond
DISPENSE_ERROR_TIMEOUT: 30000, // 30s like brain.js _timedState DISPENSE_ERROR_TIMEOUT: 30000, // 30s like brain.js _timedState
@ -435,6 +437,17 @@ export function createATMMachine(
}, },
}, },
insertingBills: { insertingBills: {
after: {
INACTIVITY_TIMEOUT: [
{
guard: ({ context }) => context.billsInserted.length === 0,
target: '#atm.idle',
},
{
target: 'confirmAbandon',
},
],
},
on: { on: {
BILL_INSERTED: { BILL_INSERTED: {
guard: 'billWithinBalance', guard: 'billWithinBalance',
@ -612,6 +625,9 @@ export function createATMMachine(
selectingAmount: { selectingAmount: {
// User selects denomination buttons to build up the cash amount // User selects denomination buttons to build up the cash amount
// UI shows: available denominations, running total, sats equivalent // UI shows: available denominations, running total, sats equivalent
after: {
INACTIVITY_TIMEOUT: '#atm.idle',
},
entry: 'clearCashOutSelection', entry: 'clearCashOutSelection',
on: { on: {
ADD_DENOMINATION: { ADD_DENOMINATION: {
@ -653,6 +669,11 @@ export function createATMMachine(
}, },
displayingInvoice: { displayingInvoice: {
// Show QR code with BOLT11 invoice, watch for payment // Show QR code with BOLT11 invoice, watch for payment
after: {
INVOICE_TIMEOUT: {
target: 'selectingAmount',
},
},
invoke: { invoke: {
src: 'watchInvoicePayment', src: 'watchInvoicePayment',
input: ({ context }) => ({ invoice: context.invoice! }), input: ({ context }) => ({ invoice: context.invoice! }),