feat: support a public browser demo of the kiosk #88

Merged
padreug merged 3 commits from feat/web-demo into dev 2026-09-06 18:04:50 +00:00
Owner

Groundwork for respawning the ATM demo (previously atm.aiolabs.dev, killed with the lamassu-next Docker stack) as a static SPA at atm.demo.aiolabs.dev, backed by the LNbits already running on aio-demo.

The browser path is already first-class — with no electronAPI, App.vue picks initializeWithLightning(), which resolves an ephemeral LocalSigner, allows mock fallback and leaves debugMode on so the bill simulator stands in for the validator. Three changes make it hostable.

1. build:web target

apps/machine's build runs vue-tsc + Vite + two electron tsc passes + an esbuild bundle. A web build needs only the Vite one. build:web is vite build, with a turbo task that still builds workspace deps via dependsOn: ["^build"], so pnpm build:web at the repo root yields apps/machine/dist.

env: ["VITE_*"] is declared on the task — the Vite vars are baked in at build time, and without it turbo would serve a cached build produced under different env.

2. create_wallet RPC wrapper

The transport has always exposed create_wallet (AUTH_ACCOUNT); LnbitsClient just never wrapped it. Account-scoped, so the envelope deliberately carries no wallet_id — that absence is what makes the server resolve auth to the Account rather than a Wallet.

3. VITE_DEMO_TAG

  • Cursor. cursor: none was global for the touchscreen, which reads as a broken page in a browser. Now scoped to .kiosk, set on <html> unless VITE_DEMO_TAG is present.

  • Cleanup. An ephemeral identity per page load is the right call: it isolates concurrent visitors, and each fresh account gets its own auto-credit under LNBITS_DEMO_MODE — whereas a single baked-in key would be credited exactly once and then drain. The cost is a throwaway LNbits account per visit, and nothing in an auto-created row distinguishes one (pubkey set / prvkey NULL equally describes a real ATM).

    A nostr pubkey can't carry a marker (grinding a vanity prefix is far too slow to do on page load), and the account/wallet the server auto-creates isn't nameable by the client. So when the tag is set the ATM mints one extra, never-used wallet named with the tag — sweeping becomes an exact string match rather than a heuristic about what looks disposable.

Both behaviors are inert on a real machine (var unset). The marker call is fire-and-forget: losing it degrades cleanup, not the demo.

Verification

  • pnpm build:web ✅ (~600 KB, 3.5 s)
  • pnpm typecheck ✅ 12/12
  • pnpm test — all suites pass; @bitSpire/hal fails only because it has no test files (pre-existing on dev, unrelated)
  • Bundle checked both ways: with VITE_DEMO_TAG set the tag + create_wallet are present; without it the kiosk class is applied and the tag string is absent

Follow-up lives in aiolabs/server-deploy: a bitspire flake input plus one services.static-sites entry on hosts/demo. Wildcard DNS already covers atm.demo.aiolabs.dev, so no DNS work.

🤖 Generated with Claude Code

https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4

Groundwork for respawning the ATM demo (previously `atm.aiolabs.dev`, killed with the lamassu-next Docker stack) as a static SPA at `atm.demo.aiolabs.dev`, backed by the LNbits already running on aio-demo. The browser path is already first-class — with no `electronAPI`, `App.vue` picks `initializeWithLightning()`, which resolves an **ephemeral** LocalSigner, allows mock fallback and leaves `debugMode` on so the bill simulator stands in for the validator. Three changes make it hostable. ### 1. `build:web` target `apps/machine`'s `build` runs vue-tsc + Vite + two electron `tsc` passes + an esbuild bundle. A web build needs only the Vite one. `build:web` is `vite build`, with a turbo task that still builds workspace deps via `dependsOn: ["^build"]`, so `pnpm build:web` at the repo root yields `apps/machine/dist`. `env: ["VITE_*"]` is declared on the task — the Vite vars are baked in at build time, and without it turbo would serve a cached build produced under different env. ### 2. `create_wallet` RPC wrapper The transport has always exposed `create_wallet` (AUTH_ACCOUNT); `LnbitsClient` just never wrapped it. Account-scoped, so the envelope deliberately carries no `wallet_id` — that absence is what makes the server resolve auth to the Account rather than a Wallet. ### 3. `VITE_DEMO_TAG` - **Cursor.** `cursor: none` was global for the touchscreen, which reads as a broken page in a browser. Now scoped to `.kiosk`, set on `<html>` unless `VITE_DEMO_TAG` is present. - **Cleanup.** An ephemeral identity per page load is the right call: it isolates concurrent visitors, and each fresh account gets its own auto-credit under `LNBITS_DEMO_MODE` — whereas a single baked-in key would be credited exactly once and then drain. The cost is a throwaway LNbits account per visit, and nothing in an auto-created row distinguishes one (`pubkey` set / `prvkey` NULL equally describes a real ATM). A nostr pubkey can't carry a marker (grinding a vanity prefix is far too slow to do on page load), and the account/wallet the server auto-creates isn't nameable by the client. So when the tag is set the ATM mints one extra, never-used wallet **named** with the tag — sweeping becomes an exact string match rather than a heuristic about what looks disposable. Both behaviors are inert on a real machine (var unset). The marker call is fire-and-forget: losing it degrades cleanup, not the demo. ### Verification - `pnpm build:web` ✅ (~600 KB, 3.5 s) - `pnpm typecheck` ✅ 12/12 - `pnpm test` — all suites pass; `@bitSpire/hal` fails only because it has no test files (pre-existing on `dev`, unrelated) - Bundle checked both ways: with `VITE_DEMO_TAG` set the tag + `create_wallet` are present; without it the `kiosk` class is applied and the tag string is absent Follow-up lives in `aiolabs/server-deploy`: a `bitspire` flake input plus one `services.static-sites` entry on `hosts/demo`. Wildcard DNS already covers `atm.demo.aiolabs.dev`, so no DNS work. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
The machine app's `build` script runs vue-tsc, the Vite build, two electron
tsc passes and an esbuild bundle. Serving the kiosk as a plain SPA needs only
the middle one, and the electron passes drag in native-addon typings that a
web build has no use for.

Add `build:web` (just `vite build`) with a turbo task that still builds the
workspace packages first via `dependsOn: ["^build"]`, so a consumer can run
`pnpm build:web` at the repo root and get `apps/machine/dist`.

`env: ["VITE_*"]` is declared on the task because the Vite vars are baked into
the bundle at build time — without it turbo would happily serve a cached
build produced under different env.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
The transport has exposed `create_wallet` (AUTH_ACCOUNT) since the RPC
registry was written, but LnbitsClient never wrapped it — the ATM only ever
needed the auto-created default wallet from `list_wallets`.

Add `createWallet(name)` plus its `CreatedWallet` reply type. Account-scoped,
so the envelope deliberately carries no `wallet_id`: that absence is what
makes the server resolve auth to the Account rather than a Wallet. Not
wrapped in `idempotent()` — a retry would mint a duplicate wallet, same
reasoning as create_invoice.

The reply carries the new wallet's adminkey/inkey, hence the type-level note
not to log it verbatim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
The browser path (no electronAPI) is already a first-class code path:
initializeWithLightning() resolves an EPHEMERAL LocalSigner, allows mock
fallback and leaves debugMode on, so the bill simulator stands in for the
validator. That is what makes a hosted kiosk demo possible at all. Two
things still needed fixing for it.

1. Cursor. `cursor: none` was applied globally for the touchscreen, which in
   an ordinary browser reads as a broken page. Scope it to `.kiosk`, set on
   <html> by main.ts unless VITE_DEMO_TAG is present — so every real machine
   keeps today's behavior and only the demo build shows a pointer.

2. Cleanup. An ephemeral identity per page load is the right call (it isolates
   concurrent visitors, and each fresh account gets its own auto-credit under
   LNBITS_DEMO_MODE, whereas a single baked-in key would be credited once and
   then drain). The cost is a throwaway LNbits account per visit, and nothing
   in an auto-created row distinguishes one: pubkey-set/prvkey-NULL equally
   describes a real ATM.

   A nostr pubkey can't carry a marker — grinding a vanity prefix is far too
   slow to do on page load — and the account/wallet the server auto-creates
   isn't nameable by the client. So when VITE_DEMO_TAG is set the ATM mints
   one extra, never-used wallet whose NAME is the tag, turning the sweep into
   an exact string match instead of a heuristic about what looks disposable.

Both are inert on a real machine: the var is unset outside the demo build.
The marker call is fire-and-forget — losing it degrades cleanup, not the demo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
padreug deleted branch feat/web-demo 2026-09-06 18:04:50 +00:00
Sign in to join this conversation.
No reviewers
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!88
No description provided.