IdleView offers Sell Bitcoin on machines with no dispenser #113

Open
opened 2026-09-29 19:08:44 +00:00 by padreug · 0 comments
Owner

IdleView.vue renders both touch zones unconditionally. There is no capability check anywhere in the file, so a cash-in-only machine advertises cash-out it cannot perform.

Seen on the Raspberry Pi 4 reference build, which has a Pyramid Apex acceptor and no dispenser. Both buttons are offered; tapping Sell Bitcoin is a dead end.

Current behaviour

The two buttons are plain markup with click handlers and no guard:

<!-- Buy Bitcoin -->
<button @click="handleCashIn"> … </button>

<!-- Sell Bitcoin -->
<button @click="handleCashOut"> … </button>

A customer on such a machine can now get as far as the cash-out flow before anything stops them. With the dispenser made optional (ea4c1f4) the failure is at least clean rather than a crash — dispenseCash returns No dispenser fitted on this machine — cash-out unavailable — but that is a last-resort backstop at the hardware boundary, not a substitute for not offering the service.

Worth noting this got worse, not better, with that commit. Before it, a machine with no dispenser failed HAL init outright and never reached the idle screen, so the dead button was unreachable. Now the machine runs correctly and the gap is visible to customers.

What the gate should key on

The renderer needs to know whether a dispenser is fitted. Today that fact only exists in the main process, where initializeHal decides it by testing the device path. Suggest surfacing it in the HAL status the renderer already consumes, rather than inferring it in the view from the preset — the preset says what the machine is meant to have, and the point of the optional-dispenser change was to tolerate reality differing from that.

Cassette count is not a safe proxy. A machine can have a dispenser fitted with empty or unconfigured cassettes, which is a temporarily-unavailable state rather than a permanent capability gap, and the two should not look the same to a customer.

Scope

  • Gate the Sell Bitcoin zone on dispenser presence.
  • Decide between hiding it and showing it disabled with a reason. Hiding is probably right for a permanent hardware absence; disabled-with-reason suits a fitted-but-empty dispenser.
  • Centre the remaining zone so a single-capability machine does not render one button off to one side.
  • SupportView.vue also documents "Tap Sell Bitcoin" in its help text and needs the same conditioning.

Related: spirekeeper#48 covers the operator-side half of this, where the cassettes panel waits forever for a state event that a dispenser-less machine never publishes.

`IdleView.vue` renders both touch zones unconditionally. There is no capability check anywhere in the file, so a cash-in-only machine advertises cash-out it cannot perform. Seen on the Raspberry Pi 4 reference build, which has a Pyramid Apex acceptor and no dispenser. Both buttons are offered; tapping Sell Bitcoin is a dead end. ## Current behaviour The two buttons are plain markup with click handlers and no guard: ```html <!-- Buy Bitcoin --> <button @click="handleCashIn"> … </button> <!-- Sell Bitcoin --> <button @click="handleCashOut"> … </button> ``` A customer on such a machine can now get as far as the cash-out flow before anything stops them. With the dispenser made optional (`ea4c1f4`) the failure is at least clean rather than a crash — `dispenseCash` returns `No dispenser fitted on this machine — cash-out unavailable` — but that is a last-resort backstop at the hardware boundary, not a substitute for not offering the service. Worth noting this got worse, not better, with that commit. Before it, a machine with no dispenser failed HAL init outright and never reached the idle screen, so the dead button was unreachable. Now the machine runs correctly and the gap is visible to customers. ## What the gate should key on The renderer needs to know whether a dispenser is fitted. Today that fact only exists in the main process, where `initializeHal` decides it by testing the device path. Suggest surfacing it in the HAL status the renderer already consumes, rather than inferring it in the view from the preset — the preset says what the machine is *meant* to have, and the point of the optional-dispenser change was to tolerate reality differing from that. Cassette count is not a safe proxy. A machine can have a dispenser fitted with empty or unconfigured cassettes, which is a temporarily-unavailable state rather than a permanent capability gap, and the two should not look the same to a customer. ## Scope - Gate the Sell Bitcoin zone on dispenser presence. - Decide between hiding it and showing it disabled with a reason. Hiding is probably right for a permanent hardware absence; disabled-with-reason suits a fitted-but-empty dispenser. - Centre the remaining zone so a single-capability machine does not render one button off to one side. - `SupportView.vue` also documents "Tap Sell Bitcoin" in its help text and needs the same conditioning. Related: spirekeeper#48 covers the operator-side half of this, where the cassettes panel waits forever for a state event that a dispenser-less machine never publishes.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/bitspire#113
No description provided.