Commit graph

319 commits

Author SHA1 Message Date
Patrick Mulligan
b7fc8d5182 feat: increase complete screen timeout to 60s
Customers need more time to read the transaction summary before
the ATM resets to idle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:53:50 -05:00
Patrick Mulligan
15c1e172e3 chore: gitignore nix results, sqlite DBs, compiled JS
Ignore build artifacts and runtime data that shouldn't be tracked:
- Nix build output symlinks (result, result-*)
- SQLite database files (*.db, *.db-shm, *.db-wal)
- Compiled JS from TypeScript HAL service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:49:50 -05:00
Patrick Mulligan
e3f376b462 feat: support VITE_FORCE_MOCK for demo/dev environments
When VITE_FORCE_MOCK=true, initialize with mock services directly
instead of requiring Electron or a live Lightning.Pub connection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:35:25 -05:00
Patrick Mulligan
1861f24187 fix: add Determinate Nix to live ISO config
Without this, freshly flashed ATMs have mismatched nix store hashes and
fall back to building from source instead of pulling cachix binaries.
Matches the installed config which already had this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:25:31 -05:00
Patrick Mulligan
fe5a3d661f fix: add stability fixes to installed ATM config
Same fixes as live.nix: disable SwiftShader, enforce MemoryMax=1G,
add 1GB swap file, and clean /tmp on boot. Applies to douro-installed
and tejo-installed configs used by nixos-rebuild on disk-installed ATMs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:08:28 -05:00
Patrick Mulligan
8a3b40184b fix: prevent ATM freeze on memory-constrained machines
- Add --disable-software-rasterizer to kill SwiftShader GPU process
- Restore MemoryMax=1G so systemd OOM-kills Electron before system locks
- Add 1GB swap file so kernel can page out under pressure
- Clean /tmp on boot to prevent stale build artifacts filling disk

Douro (1.8GB RAM, 15GB disk) was freezing from memory exhaustion with
no swap and no memory limit on the Electron process.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:04:29 -05:00
Patrick Mulligan
c2880b187b fix(machine): fix TDZ error accessing isElectron before initialization
loadTheme() and loadColorMode() were called during module evaluation
before the isElectron const was declared, causing a ReferenceError in
Firefox's strict TDZ enforcement. Move isElectron above the ref() calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:31:32 -05:00
Patrick Mulligan
7a42380f0e fix(machine): add mobile responsive layout for atm.aiolabs.dev
The kiosk UI was optimized for 1920px touchscreens, breaking mobile web.
Uses Tailwind lg: breakpoints (mobile-first) so mobile works naturally
while kiosk sizing applies at 1024px+. No JS branching — pure CSS.

- Viewport: width=1920 → width=device-width
- Kiosk-only: overflow:hidden and cursor:none behind @media (min-width: 1024px)
- IdleView: stack buttons vertically on mobile, justify-around for even spacing
- CashInView/CashOutView: stack split panels on mobile (flex-col-reverse lg:flex-row)
- QRCode: SVG scales down on small screens via max-w-full
- App.vue: hide verbose badges on mobile, debug bar collapses theme/color selectors
- Connection/network badges always visible (not gated by isIdle)
- Reduce zone-glow animation intensity by half (20px/4px vs 40px/8px)
- All lg: values verified to match original kiosk rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:41:18 -05:00
Patrick Mulligan
624650d3be fix(machine): hide theme toggle in dev mode when debug panel available
The debug panel already has full theme/color mode controls, so the
floating light/dark toggle is redundant in dev. Keep it for production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:50:45 -05:00
Patrick Mulligan
40a0d5aa70 feat(deploy): enable root SSH key access on douro
Allow key-only root login (PermitRootLogin prohibit-password) and add
authorized SSH key for remote administration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:38:56 -05:00
Patrick Mulligan
a30a8773bd feat(deploy): add WireGuard VPN tunnel to douro NixOS config
Configures wg0 interface (10.0.0.4/24) to VPS at 170.75.161.21:51820
for remote SSH access. Opens UDP 51820 in firewall and adds activation
script to ensure key directory permissions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:37:08 -05:00
Patrick Mulligan
254fbd26f2 fix(machine): UI polish — fiat rate display, 15s receipt with QR, remove CLINK
- Show exchange rate as fiat/BTC (e.g. Q615,000/BTC) instead of sats/fiat
- Show USD/BTC rate when currency != USD
- Complete screen stays 15s (was 3s) with txid QR code for receipt photo
- Add "Done" button for manual dismiss on complete screen
- Remove CLINK ndebit UI (mode selector, pubkey entry) pending k1 fix (#23)
- Remove askForReceipt/sendingReceipt screens (npub scan not active, #36)
- Remove negative sign on commission display
- Set timezone to America/Guatemala

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:36:18 -05:00
Patrick Mulligan
31c5376317 feat(nix): add Determinate Nix to douro-installed config
Resolves the cachix binary cache hash mismatch between local dev machines
(Determinate Nix 2.33) and douro (stock Nix 2.18). With both sides using
Determinate Nix, `cachix push` from local builds produces store paths that
douro's 4am auto-upgrade can download directly instead of building from source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:45:50 -05:00
Patrick Mulligan
88689a7fd3 fix(machine): hide status badges during active transactions
The fixed-position BTC price, balance, and connection badges
overlapped with the CashIn/CashOut headers. Now only visible on idle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:09:48 -05:00
Patrick Mulligan
398c1d76c6 feat(machine): show tx details on completion screen (principal, commission, total)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:58:39 -05:00
Patrick Mulligan
73feb2b0d2 feat(machine): omnipresent light/dark toggle and dynamic network badge
Move light/dark toggle with Lucide Sun/Moon icons from IdleView to
App.vue so it's visible on all screens. Detect Bitcoin network
(mainnet/testnet/regtest) from BOLT-11 invoice prefix and persist
in localStorage. Hide network badge on mainnet (implied).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:22:42 -05:00
Patrick Mulligan
3a78fffb65 feat(state-machine): split fee into per-flow commission rates
Add cashInFeePercent (3.33%) and cashOutFeePercent (7.77%) to context.
Set feePercent from the per-flow value on SELECT_CASH_IN/SELECT_CASH_OUT
transitions. Preserve both rates across resetContext.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:19:06 -05:00
Patrick Mulligan
3d9d85ba7f feat(machine): redesign cash-out with split-screen layout
Split-screen for displayingInvoice (info left, QR right), full-width
for selectingAmount. Add StepIndicator with warning flow accent header.
Replace Skeleton with BounceDots, remove ScrollArea and Card wrappers,
wrap states in Transition for fade animations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:18:46 -05:00
Patrick Mulligan
d3e652bce3 feat(machine): redesign cash-in with split-screen layout
Split-screen for insertingBills (amounts left, bill visual right) and
displayingQR (info left, QR right). Add StepIndicator with success
flow accent header. Replace Skeleton with BounceDots, remove ScrollArea
and Card wrappers, wrap states in Transition for fade animations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:17:28 -05:00
Patrick Mulligan
263165b495 feat(machine): redesign idle screen with circular touch zones
Full-canvas layout: brand zone with logo/badges/balance at top,
two circular touch zones (Buy=bitcoin orange, Sell=green) with
zone-glow animation. Show per-flow commission rates (Buy/Sell).
Move light/dark toggle to App.vue for omnipresence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:12:09 -05:00
Patrick Mulligan
9cfd2f2907 feat(machine): add scanning frame to QR code component
Wrap QrcodeVue in animated scanning frame with 4 pulsing corner
brackets and a sweep line. Increase default size from 300 to 380
for split-screen contexts. Use font-mono-code for URI preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:11:28 -05:00
Patrick Mulligan
acb4a8ccaf feat(machine): add BounceDots and StepIndicator components
BounceDots: three animated dots replacing Skeleton shimmer loaders.
StepIndicator: dot-line-dot progress with active/complete states,
split into separate dots and labels rows for proper alignment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:10:14 -05:00
Patrick Mulligan
80bc2bfd8a feat(machine): add Ubuntu font and kiosk animations
Self-host Ubuntu Regular/Bold and Ubuntu Mono woff2 for offline kiosk.
Add @font-face declarations, font-mono-code utility, dot-bounce loader,
QR scan line, corner pulse, zone glow, state-fade transition, and step
indicator CSS styles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:09:42 -05:00
Patrick Mulligan
cea774c3ad feat(machine): add kiosk touch targets and viewport for 1920px
Scale up button sizes (kiosk, kiosk-lg, kiosk-icon variants), add
active:scale feedback, set viewport to 1920px fixed width, hide cursor,
and bump card title/description sizes for touch-screen readability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:08:06 -05:00
Patrick Mulligan
e63f8ab9f6 fix(machine): seed cassettes on first boot + add atm-transactions script
Cassette inventory was never initialized in SQLite, so
recordTransaction's UPDATE decrements were no-ops against an empty
table. Now the Electron main process seeds cassettes from
VITE_LAMASSU_CASSETTES or the model preset on first boot.

Also adds an atm-transactions CLI script (with --summary, --inventory,
--type, --today, --last, --since filters) and sqlite to the NixOS
system packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:55:00 -05:00
Patrick Mulligan
f6a1c2f9f6 fix(machine): fix TypeScript cast in useTheme isElectron check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:22:58 -05:00
Patrick Mulligan
09611c0958 feat(machine): add light/dark toggle on idle screen
Subtle sun/moon button in bottom-left corner lets users switch
between light and dark mode. Persists via localStorage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:02:10 -05:00
Patrick Mulligan
a6d69a385c feat(machine): hide debug toggle in production, default to Catppuccin light
- Debug toggle button only shown when allowMockFallback is true
- Electron defaults to Catppuccin Latte (light) instead of Gruvbox dark
- Browser dev mode keeps Gruvbox dark as default

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:00:24 -05:00
Patrick Mulligan
cb33689774 fix(deploy): move autoLogin to services.displayManager (NixOS 24.11+)
The option was renamed from services.xserver.displayManager.autoLogin
to services.displayManager.autoLogin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:00:59 -05:00
Patrick Mulligan
e460765355 feat(machine): production safety — disable mock fallback and ndebit
When VITE_ALLOW_MOCK_FALLBACK is unset (production default), the ATM
now shows a maintenance screen instead of silently falling back to mock
services when hardware or Lightning initialization fails. Also disables
ndebit/CLINK in production since the static ndebit pointer is replayable
— cash-in uses LNURL-withdraw only (single-use by design).

- Add allowMockFallback config field (Electron IPC + types)
- Add strict config validation (no localhost, require private key)
- Gate all catch-block fallbacks behind allowMockFallback
- Disable debit approval service and ndebit generation in production
- Add maintenance screen in App.vue when initError is set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:39:12 -05:00
Patrick Mulligan
34179a4e34 feat(deploy): add auto-upgrade, cachix, and passwordless sudo for douro-installed
- Passwordless sudo for lamassu user (nixos-rebuild without TTY)
- Cachix binary cache (aiolabs) as substituter
- Daily auto-upgrade timer pulling latest flake from Forgejo
- trusted-users includes lamassu for nix commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:42:49 -05:00
Patrick Mulligan
03b5720883 fix(machine): clarify cash-in QR screen is for receiving, not paying
The displayingQR state showed "Waiting for payment..." with an hourglass,
which confused users into thinking they needed to pay. This is an
LNURL-withdraw flow — the user scans to *receive* sats.

- "Preparing payment code" → "Preparing your withdraw code"
- Add prominent "Scan to receive your sats" heading above QR
- "Waiting for payment..." → "Open your wallet and scan to claim"
- "Manual payment options" → "Manual withdraw options"
- "Payment Sent!" → "Sats Sent!"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:40:59 -05:00
Patrick Mulligan
1a452fe7ec fix(machine): replace remaining hardcoded colors with semantic classes
Replace bg-orange-500 lightning bolt overlay with bg-bitcoin, bg-white
receipt placeholders with bg-qr/text-qr-foreground, and remove redundant
bg-white QR wrappers in IdleView overlays (QRCode.vue already has bg-qr).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:04:19 -05:00
Patrick Mulligan
625cebed25 refactor(nix): consolidate deploy flake into root flake with pure ISO builds
Move deploy/nixos/flake.nix into the root flake.nix, adding mkAtmApp
for pure Nix builds of the Electron app (no local pnpm needed). Simplify
build-iso.sh to a thin wrapper around `nix build .#iso-<model>`. Add
douro hardware configuration. Streamline live.nix to consume the
Nix-built app package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:58:43 -05:00
Patrick Mulligan
f5f00108aa feat(machine): add light/dark mode toggle with semantic color styling
Replace hardcoded Tailwind colors (bg-white/5, text-green-400, bg-black/30,
etc.) with theme-aware semantic classes (bg-card, text-success, text-bitcoin,
bg-destructive/20) across CashInView and CashOutView. Add colorMode support
to useTheme composable with localStorage persistence and system preference
detection. QRCode component now reacts to theme/mode changes via
MutationObserver. Debug panel includes Light/Dark/System toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:57:33 -05:00
Patrick Mulligan
0406a21b0e fix(cash-in): restore cancel button and add displayingQR timeout
Cancel button is now always visible during the cash-in flow. The state
machine routes CANCEL to confirmAbandon when bills are present, so the
user always has an exit path with appropriate warnings. Also adds a
5-minute auto-timeout on displayingQR and allows cancel during
generatingNdebit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:19:01 -05:00
Patrick Mulligan
13ac74ab86 fix(cash-in): prevent cancel after bills inserted in insertingBills state
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 <noreply@anthropic.com>
2026-02-25 19:35:34 -05:00
Patrick Mulligan
642f0c5951 fix(machine): hide cancel after bills inserted, kiosk-only UI cleanup
- Hide header cancel button after bills are in cash box
- Add spinning hourglass to displayingQR waiting indicator
- Hide keyboard-dependent UI in Electron kiosk mode:
  QR mode selector, manual payment options, copy buttons
  These remain available in browser (web-ui) mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:26:06 -05:00
Patrick Mulligan
165ad59704 feat(machine): confirm-abandon warning, retry on error, hourglass animation
- Add confirmAbandon state UI: warns user cash can't be returned,
  offers "Show QR Code Again" or "Abandon (lose cash)"
- Error state shows "Try Again" button when bills are inserted
- Replace Skeleton loading placeholders with hourglass emoji
- Add animate-hourglass: 45-degree step rotation (Tailwind v4 @utility)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:15:51 -05:00
Patrick Mulligan
625af457d1 feat(machine): live BTC price badge with 10s polling from LNbits
- Poll LNbits /api/v1/rate/ every 10s for live price display
- Show local currency rate + USD reference (e.g. GTQ/BTC: Q525,034 ($67,960))
- Reduce rate cache from 5min to 10s for responsive display
- Use simpler /api/v1/rate/ endpoint instead of /conversion POST

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:12:52 -05:00
Patrick Mulligan
2eda5959b9 fix(state-machine): prevent cancel after bills accepted in cash-in
Once bills are stacked in the cash box they cannot be returned.
Cancel in displayingQR now goes to confirmAbandon warning state.
Error state retries to generatingNdebit instead of idle when bills
are present. CANCEL from error only goes to idle if no bills inserted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:12:24 -05:00
Patrick Mulligan
648f089ac2 feat(machine): real exchange rate with LNbits, ShockWallet, CoinGecko fallback
Replace hardcoded mock rate (1000 sats/USD) with live price feeds:
1. LNbits (lnbits.atitlan.io) — primary, supports GTQ/USD/EUR
2. rates.shockwallet.app — ShockWallet ecosystem fallback
3. CoinGecko — last resort

Includes 5-minute cache and request deduplication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:50:30 -05:00
Patrick Mulligan
4105f750de fix(deploy): complete ISO build with serialport parsers, device policy, and sintra model
- Add all 10 @serialport/parser-* packages to live.nix atm-app derivation
  (serialport@12 barrel import eagerly requires all of them)
- Add DevicePolicy=auto + DeviceAllow=char-* rw for serial port access
- Make envTemplate model-aware (machineModel + fiatCodeForModel)
- Add sintra model (EUR) to flake.nix and build-iso.sh
- Fix tejo fiat code from USD to GTQ

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:42:59 -05:00
Patrick Mulligan
0ea9640f49 fix(machine): production hardware fixes for Douro cash-out
- Fix cassette denominations: Douro uses Q100/Q200, not Q20
- Add hal:get-inventory IPC so renderer can read HAL cassette inventory
- Add balance fetch/display to HAL+IPC init path and idle screen
- Enable/disable bill validator via watch on nested state transitions
- Pass fiatCode to state machine context (was hardcoded to USD)
- Preserve currency across state machine resetContext
- Add CANCEL handler to dispenseError state (was stuck)
- Fix remaining hardcoded $ symbols in CashInView

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:13:45 -05:00
Patrick Mulligan
c874d9e2e3 fix(deploy): multi-model ISO builds and HAL packaging
- Parameterize live.nix by machineModel (douro/tejo) passed via specialArgs
- Douro: kernel 5.15 LTS for Bay Trail i915 eDP fix, vt.handoff=7
- Fix HAL packaging: copy dist/ into subdirectory (not flattened)
- Add display-reset systemd service for kexec GPU reinitialization
- Add --disable-gpu flag for Electron on headless/GPU-less boots
- flake.nix: mkLiveConfig helper, per-model ISO outputs (iso-douro/iso-tejo)
- build-iso.sh: require model param, write model-specific .env for Vite
- flash-douro-usb.sh: GPT+FAT32 ESP layout for Bay Trail UEFI boot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:02:28 -05:00
Patrick Mulligan
2605c44ef8 feat(machine): add HAL IPC bridge for real hardware in Electron
HAL hardware drivers (serialport) run in the main process since they
need Node.js. The renderer communicates via IPC for all hardware ops.

- hal-service.ts: bridge between HAL drivers and Electron IPC
- main.ts: HAL IPC handlers (init, dispense, validator stack/reject)
- preload.ts: expose HAL API to renderer via contextBridge
- atm.ts: IPC-based production init with validator event wiring
- hal.ts: add 'hold' mode for escrow (async stack/reject decision)
- electron.d.ts: HAL type declarations for window.electronAPI

Bills go to escrow first; the renderer checks balance before accepting.
Falls back to Lightning-only mock mode if HAL init fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:02:13 -05:00
Patrick Mulligan
bb73e13e48 fix(hal): implement onLeaveConnected for ID003 validator initialization
The ID003 FSM was stuck in the PowerUp state because leaving the
Connected state never emitted 'ready'. This is the trigger for the
denomination/reset initialization chain.

Added onLeaveState() method that emits 'ready' when leaving Connected,
matching the original lamassu-machine behavior (onleaveConnected).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:01:50 -05:00
Patrick Mulligan
94a9f03e2e fix(hal): simplify Puloon error handling to match lamassu-machine
Align dispenser error handling with lamassu-machine's proven approach:
close port on error, set error name, let caller decide when to re-init.
Also configure live ISO for Douro/GTQ with serialport native modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:22:34 -05:00
Patrick Mulligan
10293d0ab5 fix(machine): auto-init HAL in production, wire dispense completion, add GTQ
- App.vue detects Electron and calls initializeForProduction() to start
  real hardware drivers instead of Lightning-only mode
- Auto-send CASH_DISPENSED when HAL is active since dispenseCash already
  waits for bills to be removed before resolving
- Add GTQ (Guatemalan Quetzal) bill lengths to F56 and Puloon dispensers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:20:51 -05:00
Patrick Mulligan
ba42820c71 feat(machine): add Tejo machine preset with 4-cassette F56 dispenser
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:04:29 -05:00