feat(machine): VITE_DEMO_TAG for the public web demo

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
This commit is contained in:
Padreug 2026-09-06 19:24:15 +02:00
commit 8264dd7472
4 changed files with 51 additions and 4 deletions

View file

@ -24,6 +24,13 @@ const router = createRouter({
],
})
// Kiosk chrome (hidden cursor) is the default — every real machine is a
// touchscreen. The public web demo (VITE_DEMO_TAG) runs in a normal browser,
// where an invisible pointer just reads as broken.
if (!import.meta.env.VITE_DEMO_TAG) {
document.documentElement.classList.add('kiosk')
}
// Create Pinia store
const pinia = createPinia()

View file

@ -505,6 +505,31 @@ export async function initializeLightningServices(options?: {
}
console.log('[Lightning] LNbits wallet:', lnbitsWalletId)
// ── Public web demo: stamp the throwaway account so it can be swept ──────
// The browser demo (atm.demo.aiolabs.dev) runs with an EPHEMERAL identity —
// a fresh keypair per page load — so LNbits mints a new account + a fresh
// auto-credited wallet for every visitor. That isolation is the point (a
// single baked-in key would be credited exactly once and then drain), but it
// leaves throwaway accounts behind, and nothing in an auto-created row says
// "demo": pubkey-set/prvkey-NULL also describes a real ATM.
//
// A nostr pubkey can't carry a marker (you'd have to grind a vanity prefix,
// far too slow to do on page load), and the account/wallet the server
// auto-creates isn't nameable by the client. So we mint one extra,
// never-used wallet whose NAME is the tag: sweeping is then an exact string
// match on wallet name rather than a heuristic about what looks disposable.
//
// Unset on every real machine, so this is inert outside the demo build. The
// call is fire-and-forget: losing the marker degrades cleanup, not the demo.
const demoTag = (import.meta.env.VITE_DEMO_TAG as string | undefined)?.trim()
if (demoTag) {
void lnbits
.createWallet(demoTag)
// Never log the reply — create_wallet returns adminkey/inkey.
.then(() => console.log('[Lightning] Demo marker wallet created:', demoTag))
.catch((e) => console.warn('[Lightning] Demo marker wallet failed:', e))
}
// #70 P1: pull operator pubkey + fee config from LNbits over the authenticated
// transport (spirekeeper#41 `get_machine_config`). A seed-only machine has no
// VITE_OPERATOR_PUBKEYS, so without this it can't trust its fee config and sits

View file

@ -1,10 +1,13 @@
@import 'tailwindcss';
@import 'tw-animate-css';
/* Hide cursor completely on touchscreen kiosk */
*,
*::before,
*::after {
/* Hide cursor completely on touchscreen kiosk.
Scoped to .kiosk (set on <html> by main.ts) so the public web demo, which
runs in an ordinary browser with a mouse, keeps a visible pointer. */
.kiosk,
.kiosk *,
.kiosk *::before,
.kiosk *::after {
cursor: none !important;
}