From d8841f7fe9773f8261e613602e0c51b090a2a5f1 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Fri, 6 Mar 2026 15:46:52 -0500 Subject: [PATCH] fix(hal): return DispenseResult from IPC dispense handler The hal:dispense IPC handler in main.ts did not return the result of dispenseCash(), causing the state machine guard to crash on undefined output. This left the UI stuck on "Dispensing cash..." after successful dispense. - hal-service.ts: return DispenseResult instead of void/throwing - main.ts: add missing return in IPC handler - machine.ts: defensive guard (?. instead of .) as safety net Bug found with the aid of Seoyoung at Trece Cielos. Co-Authored-By: Claude Opus 4.6 --- apps/machine/electron/hal-service.ts | 41 +++++++++++++++++++++++---- apps/machine/electron/main.ts | 3 +- packages/state-machine/src/machine.ts | 3 +- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/apps/machine/electron/hal-service.ts b/apps/machine/electron/hal-service.ts index cc0a4e4..e5b2df8 100644 --- a/apps/machine/electron/hal-service.ts +++ b/apps/machine/electron/hal-service.ts @@ -34,13 +34,19 @@ export interface ValidatorCallbacks { onError: (error: string) => void } +export interface DispenseResult { + bills: { denomination: number; dispensed: number; rejected: number }[] + dispensed: boolean + error?: string +} + export interface HalInstance { connectValidator: (callbacks: ValidatorCallbacks) => void enableValidator: () => void disableValidator: () => void stackBill: () => void rejectBill: () => void - dispenseCash: (amounts: { denomination: number; count: number }[]) => Promise + dispenseCash: (amounts: { denomination: number; count: number }[]) => Promise getInventory: () => Record cleanup: () => Promise } @@ -139,14 +145,22 @@ export async function initializeHal(config: HalConfig): Promise { stackBill: () => validator.stack(), rejectBill: () => validator.reject(), - dispenseCash: async (amounts) => { + dispenseCash: async (amounts): Promise => { console.log('[HAL] Dispensing:', amounts) const notes: number[] = new Array(cassetteDenominations.length).fill(0) for (const { denomination, count } of amounts) { const idx = cassetteDenominations.indexOf(denomination) if (idx === -1) { - throw new Error(`No cassette loaded with denomination: ${denomination}`) + return { + bills: amounts.map((a) => ({ + denomination: a.denomination, + dispensed: 0, + rejected: 0, + })), + dispensed: false, + error: `No cassette loaded with denomination: ${denomination}`, + } } notes[idx] = count } @@ -161,12 +175,27 @@ export async function initializeHal(config: HalConfig): Promise { } } + // Build per-denomination result + const bills = amounts.map((a, i) => ({ + denomination: a.denomination, + dispensed: result.value[i]?.dispensed ?? 0, + rejected: result.value[i]?.rejected ?? 0, + })) + + const totalRequested = amounts.reduce((s, a) => s + a.count, 0) + const totalDispensed = bills.reduce((s, b) => s + b.dispensed, 0) + if (result.error) { - throw result.error + return { bills, dispensed: false, error: result.error.message } } - await dispenser.waitForBillsRemoved() - console.log('[HAL] Bills removed by customer') + // Wait for customer to take bills + if (totalDispensed > 0) { + await dispenser.waitForBillsRemoved() + console.log('[HAL] Bills removed by customer') + } + + return { bills, dispensed: totalRequested === totalDispensed } }, getInventory: () => ({ ...inventory }), diff --git a/apps/machine/electron/main.ts b/apps/machine/electron/main.ts index 6f3970f..e6230de 100644 --- a/apps/machine/electron/main.ts +++ b/apps/machine/electron/main.ts @@ -175,11 +175,12 @@ ipcMain.handle('hal:init', async (_event, config) => { } }) +// Bug found with the aid of Seoyoung at Trece Cielos ipcMain.handle( 'hal:dispense', async (_event, amounts: { denomination: number; count: number }[]) => { if (!halInstance) throw new Error('HAL not initialized') - await halInstance.dispenseCash(amounts) + return await halInstance.dispenseCash(amounts) } ) diff --git a/packages/state-machine/src/machine.ts b/packages/state-machine/src/machine.ts index 57f07b5..02e31e9 100644 --- a/packages/state-machine/src/machine.ts +++ b/packages/state-machine/src/machine.ts @@ -659,7 +659,8 @@ export function createATMMachine( input: ({ context }) => context.dispenseAmounts, onDone: [ { - guard: ({ event }) => (event.output as unknown as DispenseCashResult).dispensed, + guard: ({ event }) => + (event.output as unknown as DispenseCashResult | undefined)?.dispensed === true, target: 'waitingForCashTaken', actions: ['setCashDispensed', 'setDispenseResult'], },