Decisions 1 through 4 are built on both sides, so the status section says
what landed rather than what is planned, and decision 4a is marked
superseded.
4a is kept rather than deleted. It is the calibration for what a warning
dialog is worth: it depended on an operator reading it at the end of a
refill round, and it could not help at all when the stale value was
already in the form. The dialog and the endpoint behind it are both gone
now, which is a stronger guarantee than any wording could be.
Also records the one gap left open on purpose. An operation recorded
while the relay is unreachable waits for the operator's next action,
because only an operator action triggers a publish.
Two things learned after the ADR was first written.
The dashboard's publish dialog already warns that the publish overwrites
the ATM's tracked counts and that decrements since the last baseline will
be lost, and says v2 reconciliation will replace it. That changes how the
gap should be read: a known risk with a human-factors mitigation, not an
oversight, and the product had already reached the same conclusion these
decisions formalise. Worth stating that a warning is the weakest control
available — it depends on an operator reading a dialog, and cannot help
when the stale value is the one already in the form.
Also records that decisions 5 to 8 were verified live on sintra rather
than only by unit test, and which of the listed failures those decisions
do not close.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This protocol spans two repos and decides how much cash a machine will
pay out, and its only specification was a closed issue and a chat log.
That is how four separate divergence bugs went unnoticed.
Records what the transport actually permits — an addressable event is an
unconditional overwrite ordered by a second-granularity clock, and a
relay acknowledges an event it then discards, so a losing writer is
never told — and why that rules out compare-and-swap and leads to the
ATM owning the count while the operator publishes operations.
Decisions 5 through 8 shipped in #104 and spirekeeper#44; 1 through 4
are the v2 operations wire and are not yet built.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tap → unlock took ~3 s on sintra. The card server's /session now fills
fiat only from its warm rate cache (aiolabs/boltcards
fix/session-fiat-from-cache); when it returns a currency with fiat null,
the store prices the balance in that currency from the ATM's own rate
source after the unlock, so the chip still shows the wallet's currency.
Log how long the session call took and whether the server priced it, so
the next latency question can be answered from the journal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/boltcard-session.md is the /session wire contract (sibling of
boltcard-receive-resolver.md), including the trust boundary: the session
URL is derived from the card's own host, so open enrollment is still not
a security boundary (#91). ADR-003's amendment now records verified entry
via /session and the hidden-by-default balance display.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ADR-003, .env.example, the access module's headers and the provisioning
schema still described the planned npub-QR → UID → serial-reader path.
What shipped (#86) is Bolt Card tap-to-enter over the main-process
pcscd reader with external_id as the identity, soft entry and
verify-at-payment. Nothing ever called availableAccessReaders(): the
camera npub-QR reader, the mock reader and the AccessReader seam were
dead, so they go; services/access now holds authorize, the card parser
and the credential types. The unused 'uid' scan variant goes with them;
'npub' (+PIN) and the 'challenge' seam stay.
The ADR gets an amendment section recording the differences, including
that open enrollment is not a security boundary and that the audit is
still a stub (both tracked as issues).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squashed skeleton (was 11 commits on feat/access-control-skeleton) for a
clean rebase onto dev. Adds a `locked` gate the terminal boots into until a
credential is presented; opt-in and non-breaking (defaults off → boots
straight to idle as before).
- state-machine: `locked` state + ACCESS_GRANTED/ACCESS_DENIED/DEV_UNLOCK
events + accessBypass/devUnlockAllowed guards (packages/state-machine).
- services/access: reader abstraction, npub+PIN authorize() (nostr-tools
nip19; accepts nostr:/nprofile), camera npub-QR reader, mock reader.
- LockedView.vue + ColorModeToggle: branded viewfinder, PIN pad, denied
reason, dev-unlock; camera off-by-default + idle return.
- store/main/electron.d.ts: seed gate config, grant/deny/devUnlock wiring,
access.json provisioning (no rebuild), get-config surface.
- deploy: access.example.json + provision-access.sh; ADR-003.
Credential union is npub today; UID (NFC tap) is the next step.
Tap-to-receive for the buy flow, the receive counterpart to #83's
cash-out tap-to-pay. A Bolt Card only emits an lnurlw withdraw voucher
(wrong direction to deposit into it), so the tap is used as an
authenticated identity (external_id + SUN p/c) to resolve the card
wallet's lnurlp/Lightning Address; the ATM then pays an invoice for the
payout over its existing nostr transport.
- electron/lnurl-pay.ts: resolveCardInvoice() — resolve card -> pay
target -> LUD-16/LUD-06 -> BOLT11. scanUrlToResolver() is the single
HTTPS-today / nostr-tomorrow transport seam. 13 tests.
- IPC lnurl:pay-card (main-process HTTPS to dodge renderer CORS) +
preload/electron.d.ts surface.
- stores/atm.ts: handleBoltCardReceive() settles via the existing
payInvoice -> PAYMENT_RECEIVED path; the one NFC listener now routes
the same tap by flow (cash-out pulls, cash-in receives).
- CashInView.vue: NFC status + dev tap input.
- docs/boltcard-receive-resolver.md: spec for the custom LNbits
/boltcards/api/v1/pay/<id> resolver endpoint (omni-private side).
Card issuance is unchanged — same NDEF/keys/external_id; receive is a
server-side reading of the same tap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The access/recovery plane (SSH/NetBird) stands and may carry recovery
procedures, but it is explicitly NOT the only or first-line recovery. Add
a layered, cheapest-first recovery model: (1) app auto-recovery of its own
relay/Lightning connectivity, (2) an on-screen Retry for an operator at the
kiosk, (3) SSH/NetBird as the last-resort remote plane for genuine app/OS
failure. A public kiosk must not need remote shell access to recover from a
transient/boot-before-network outage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Separate payment (Nostr↔LNbits, SaaS-operator-owned), fleet control
(Nostr #42, machine-operator-owned), and access/recovery (SSH) planes
by trust owner. Recovery access is provisioned at install and
app-independent. Adopt NetBird for the access plane (scale + fully FOSS
self-hostable control plane; rejects Tailscale's closed control plane).
Reject a dashboard 'revoke SaaS-operator access' toggle as a false
promise — the SaaS operator controls LNbits and the default control
plane, so exclusion is by ownership (operator self-hosts), not by
toggle.
Closes gap 2 from coord log 2026-06-01T18:30Z. The LNbits withdraw
extension's nostr-transport RPC now populates `link.lnurl` from
`settings.lnbits_baseurl` (aiolabs/withdraw#1 / commit e9d911e), so the
ATM no longer needs a separate HTTP URL on the wire to compose the
LNURL-withdraw callback itself.
What goes:
- `VITE_LNBITS_HTTP_URL` env var (renderer + Electron main)
- `lnbitsHttpUrl` field on `LightningConfig`, `RuntimeConfig`, and the
Window mirror in `src/types/electron.d.ts`
- The manual `${lnbitsHttpUrl}/withdraw/api/v1/lnurl/${unique_hash}`
composition in `generateLnurlWithdraw`
- The `encodeLnurl` bech32 helper in `lightning.ts` (LNbits returns
bech32-encoded; we just `.toUpperCase()` to match BOLT/LNURL convention)
- `@scure/base` dep from `apps/machine/package.json` (only used by the
removed helper; clink still uses it directly)
- The `lnbitsHttpUrl` option + `LNBITS_HTTP_URL=…` env var + boot echo
in `deploy/nixos/bitspire-atm.nix`
- Doc references in CLAUDE.md, README.md, deploy/nixos/README.md,
docs/architecture-comparison.md, and the lightning-check skill
What stays:
- `link.lnurl` consumption, with an explicit error if LNbits returns
null (which signals `LNBITS_BASEURL` is unset on the server side —
better to fail clearly than silently)
- The receiver-side bech32 uppercasing (LNbits returns lowercase per
the standard library)
Why this is a net win:
- Removes a config-drift surface — if LNbits's external URL moved
(DNS, port, reverse-proxy rewrite), every ATM in the field would
stop issuing redeemable LNURL-withdraw QRs until reconfigured.
Now LNbits derives its own URL from `settings.lnbits_baseurl`,
one source of truth.
- Removes an extra provisioning step. No more `LNBITS_HTTP_URL=…`
before running `provision-atm.sh`; the relay + server pubkey suffice.
- Removes the misleading boot echo that triggered the §`18:30Z`
smoke triage confusion ("LNbits HTTP: <url>" read like ATM-→-LNbits
connectivity, when it was only ever a URL embedded in customer QRs).
Also adds a `# pragma: allowlist secret` marker above the
`VITE_ATM_PRIVATE_KEY` doc block in `.env.example` so the global
secret scanner stops false-positiving on the documentation prose.
Workspace typecheck + 24/24 apps/machine tests still green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three breakages handled:
- hardware.opengl → hardware.graphics (renamed in 24.11). Touches
upboard.nix, douro.nix, batm3.nix, live.nix.
- vaapiIntel dropped — legacy pre-Broadwell driver, removed in
nixpkgs. UP Board (Cherry Trail) and OptiPlex 9030 (Haswell) both
use intel-media-driver, which stays.
- vaapiVdpau renamed → libva-vdpau-driver.
system.stateVersion stays 24.05 — convention is to never bump after
install. Existing Sintra and fresh flashes keep the 24.05 state
semantics; that's correct.
All 8 nixosConfigurations (4 models × {live,installed}) evaluate
clean with zero deprecation warnings on 24.11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second + final batch of the doc refresh. README + CLAUDE went out in
8c9ae29; deploy/nixos/README + obsolete-flow flags in 924844f. This
commit covers everything left.
docs/machine-installation.md
Was describing a manual AppImage scp deploy + a `lamassu-kiosk`
systemd unit that hasn't been the deployment path for months.
Replaced with a high-level "what the pipeline does and why"
overview that points at deploy/nixos/README.md for the full
command-by-command walkthrough. Includes the BATM3 chassis-mod
note (custom Dell OptiPlex retrofit, not a stock Dell).
docs/architecture-comparison.md
Rewrote the comparison to be lamassu-server (≤ v8.1.5) vs bitSpire
(LNbits-backed) instead of the original lamassu-server vs LP-backed
lamassu-next framing. Updated the cash-out + cash-in flow diagrams
to show the actual nostr-transport path (LNbits-bundled nostrrelay
extension at ws://<host>:5001/nostrrelay/test, no separate strfry
container). Replaced the migration-path section with a softer
"when to choose what" framing that includes Lamassu's current
commercial offering as a legitimate third option. Added a header
pointer to the Acknowledgements section.
docs/business-model.md
Light touch-ups: Lightning.Pub → LNbits where it appeared, swapped
the [[ndebit-cash-in-flow]] link for [[architecture-comparison]],
noted the kind-30078 service beacon for availability broadcasts.
docs/device-configuration.md
Dropped "Lamassu" branding from the machine-model headings
(Sintra / tejo / douro / batm3 are referenced by hardware identity
here, not by Lamassu's product line). Added the Sintra-specific
ttyS4-vs-placeholder-ttyS1..3 gotcha we hard-learned during the
first flash. Corrected the BATM3 entry: stock GeneralBytes chassis
with a Dell OptiPlex 9030 AIO motherboard physically grafted in,
NOT a Dell out of the box. Updated the example /dev/ttyJ* symlink
output to match what a healthy Sintra actually shows.
docs/adr/001-hal-architecture.md
ADRs are historical artifacts — kept the original decision text
intact. Added a postscript noting:
- The package rename @lamassu/hal → @bitSpire/hal
- The v8.1.5 boundary on any lamassu-machine source-tree
references (Lamassu's 2024-01-26 license transition)
- That the "Remaining Work" list is complete and the first
successful Sintra hardware integration ran on 2026-05-13
.claude/skills/lightning-check.md
Rewrote end-to-end. Was Lightning.Pub-flavoured with CLINK kinds
21001/21002 as the primary flows; now validates the LNbits nostr-
transport surface (kind-21000 envelope, NIP-44 v2 encryption,
subscribe_payments filter discipline, lnurlw link composition).
Preserved a --clink mode for the still-live kind-21003 operator-
management surface. Includes a "what to check" rubric for cash-out
vs cash-in flows that mirrors the actual code in
apps/machine/src/services/lightning.ts.
.claude/skills/hal-check.md
Two pivots: (1) acknowledge ADR-001's TypeScript-not-Rust choice
and reframe all the safety checklists in TS-flavour (type safety,
discriminated unions, single-writer serial, bounded emitters)
instead of Rust-flavour (unsafe, borrow checker). (2) Add explicit
v8.1.5 provenance boundary plus a "forbidden operations" section
that prohibits diffing or porting from v8.1.6+ lamassu-machine
source. Updated the port-validation source-reference table to
list TS file paths under packages/hal/ instead of Rust paths.
.claude/skills/docs.md
@lamassu/* → @bitSpire/*. Replaced the Lightning.Pub mermaid
diagram with a current cash-out flow showing the nostr-transport
RPC + subscribe_payments push path. Left the createOffer noffer
example in the API-docs template section since it's illustrative
("here's what a good TSDoc block looks like") rather than current
reference documentation.
.claude/skills/test.md
One-line: @lamassu/nostr-client → @bitSpire/nostr-client in the
pnpm-filter example.
deploy/nixos/README.md
Single touch-up: clarified the douro/batm3 hardware-module comments
to reflect that BATM3 is a custom-installed Dell board in a
GeneralBytes BATM3 chassis (not a Dell OEM).
Files NOT touched in this sweep (intentionally):
- packages/hal/src/**/*.ts attribution comments — those reference
"lamassu-machine" in their port-source headers. Those are
factually accurate (the drivers ARE ported from there, up to
v8.1.5) and constitute necessary license/attribution metadata.
Editing them would erase the provenance trail.
- .claude/skills/{nostr-check,security}.md — already protocol-
neutral, no LP/lamassu references to clean up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
deploy/nixos/README.md was the most-stale doc in the tree: it still
talked about a `lamassu-atm` systemd unit, `/opt/lamassu-atm` paths,
nixos-install with a non-existent `lamassu-atm` flake output, and an
scp-the-built-electron-bundle workflow that hasn't been the deploy
path for many months. Replaced with a rewrite that documents the
actual current pipeline:
- File layout: bitspire-atm.nix (not lamassu-atm.nix), live.nix,
hardware/{douro,batm3,upboard}.nix, and the udev / helper scripts
- Build pipeline: `nix build .#disk-image-<model>` and the four
flake-output flavours per model (live config, installed config,
iso, disk-image)
- Full Sintra walkthrough end-to-end: prep a flashing USB on the
dev box, boot Alpine live on the Sintra, identify the eMMC,
dd with count= to skip the trailing USB padding, repair the
GPT secondary header + grow root with parted, poweroff, boot,
provision via provision-atm.sh. Every quirk we hit during the
first real flash is now baked in (mdev for /dev nodes, parted
Fix prompt, lbu-style notes).
- Runtime layout cheat-sheet: /var/lib/bitspire/.env (0600
lamassu:lamassu), state.db, /etc/bitspire/config.env, etc.
- Common-operations playbook: re-provision, nixos-rebuild switch
over SSH with --use-remote-sudo (much faster than reflashing),
journalctl filtering, hardware-side health checks.
- NixOS module reference for services.bitspire, including the
LNbits-flavoured options (relayUrl, lnbitsServerPubkey,
lnbitsHttpUrl) instead of the retired lightningPubUrl.
- Sintra-specific gotchas section: eMMC-via-sdhci-acpi, the
ttyS4 dispenser placement, the ttyS1..3 phantom-node issue.
- Security-notes section updated to reflect passwordless sudo
enabled for nixos-rebuild deploys, and the implications.
Auto-upgrade behaviour explained explicitly (the ?ref=dev pin) so
contributors understand why production ATMs on main don't pick up
dev branch changes.
docs/ndebit-cash-in-flow.md: added a header banner flagging the
document as historical — cash-in on dev is LNURL-withdraw +
subscribe_payments push, not ndebit. Original content kept as a
reference for any future revival of nostr-native cash-in.
docs/clink-protocol.md: same treatment — flagged as dormant on dev,
explaining which pieces still apply (kind-21003 management) and
which are unused (kinds 21001/21002). Protocol reference content
left intact since the wire format is unchanged upstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final rename commit covering user-facing copy and the docs that
describe current state. The mechanics of the rename are done after
this; the LNbits backend swap (phase 3) is the next concern.
Code branding strings (Lightning invoice descriptions):
apps/machine/src/services/lightning.ts
apps/machine/src/stores/atm.ts
docs/clink-protocol.md (example code blocks)
"Lamassu ATM Payment" → "bitSpire Payment"
"Lamassu ATM - Cash Out" → "bitSpire - Cash Out"
`Lamassu ATM - Buy ${n} sats`→ `bitSpire - Buy ${n} sats`
Top-level docs:
README.md, CLAUDE.md — title + intro + dir-tree references.
deploy/nixos/README.md — title + worktree-path commands.
docs/machine-installation.md — opening line carries the historical
note ("Lamassu Next" → "bitSpire"). The body still uses
`/opt/lamassu/` paths and the `lamassu-kiosk` systemd unit
because the dev branch is moving to NixOS disk-image flash
(phase 4) — this AppImage-sideload doc represents the legacy
deploy path. Leaving the LP/lamassu refs in there as part of
its historical context; a separate doc will describe the
NixOS path.
.claude/skills/nostr-check.md — header only.
DELIBERATELY left as "Lamassu Next" (pedagogical / historical):
- docs/adr/001-hal-architecture.md — frozen ADR; renaming
distorts the historical decision context.
- docs/architecture-comparison.md — deliberately contrasts
"lamassu-server" (prior) with "lamassu-next" (us at the time
of writing).
NOT done in this commit (deferred to LNbits/clean-up phase):
- docker/docker-compose.dev.yml container names
(lamassu-relay, lamassu-bitcoind, etc.) — these belong to the
LP-bearing dev stack that 3c/3d will significantly reshape.
Verified: pnpm typecheck clean (12/12 cached).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use h-dvh instead of h-screen for mobile browser viewport
- Add ScrollArea component (shadcn-vue) to replace native scrollbars
- Move Hide Debug into debug bar row, show Show Debug only when off
- Responsive cash-out denomination grid (single col on mobile)
- Add safe-area padding for mobile gesture bar
- Add business model documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add dev.sh script for managing regtest development environment
- Implement cmd_fund to fund ATM app owner via Lightning.Pub API
- Add --fund flag to cmd_up for automatic funding on startup
- Update setup_atm_app to write VITE_APP_ID to machine .env
- Fix Electron IPC to pass appId and extensionApiUrl to renderer
- Restructure repo from nested lamassu-next/ to root
The dev.sh script now supports:
- ./dev.sh up --fund # Start regtest and auto-fund ATM
- ./dev.sh fund # Fund existing ATM app
- ./dev.sh status # Show environment status
- ./dev.sh reset # Clean restart
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Catalog all existing lamassu-machine drivers (7 validators, 5 dispensers,
3 printers, 4 scanners)
- Add "Port, don't rewrite" principle - leverage battle-tested existing code
- Update HAL structure to include all drivers to port
- Replace NV200 example with ID003 (default validator in lamassu-machine)
- Add Printer trait definition
- Update napi-rs bindings with ValidatorDriver and DispenserDriver enums
- Add configuration mapping for migrating from legacy lamassu-machine config
- Update milestone checklist with prioritized driver porting tasks
- Add driver complexity ranking to guide porting order
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Market context added:
- 95%+ activity is cash-out (selling Bitcoin for cash)
- Typical for remittance corridors, Bitcoin-paid workers
- Bill dispenser is REQUIRED for testing, not optional
Updated development priorities:
- Cash-out first, cash-in second
- Dispenser in minimum dev kit (~$655-855)
- Bill validator becomes secondary priority
Updated milestones:
- Priority 1: Cash-out with real dispenser
- Priority 2: Cash-in (mock validator OK initially)
- Priority 3: Advanced features (Cashu, etc.)
Phase 4 reorganized to implement cash-out before cash-in.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Hardware section now includes:
- One-way dev kit (cash-in only): ~$535-735
- Two-way dev kit (cash-in + cash-out): ~$955-1,555
- Bill dispenser options: Puloon LCDM-1000/2000, Fujitsu F53
- Sourcing tips for used equipment
HAL section now includes:
- BillDispenser trait definition
- Puloon LCDM RS-232 implementation
- napi-rs bindings for dispenser
- TypeScript usage examples for both validator and dispenser
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive redesign using Nostr for all ATM communication:
Lightning.Pub as Core Server:
- Replaces lamassu-server entirely
- Nostr-native account system wrapping LND
- Zero server config (no DNS/SSL/ports)
- CLINK native, one-line deployment
- Built-in liquidity management
Private Nostr Relay:
- NIP-42 authenticated relay (strfry/rnostr)
- Whitelist: ATM npubs + Operator npubs only
- Encrypted command/control channel
- Negentropy sync for offline reconciliation
Machine Identity:
- Each ATM has Nostr keypair (nsec/npub)
- Replaces client certificates
- Cryptographic authentication
- No PKI/CA required
Replacing SMS (KYC vector elimination):
- NIP-17 encrypted DMs for receipts
- No phone numbers collected
- User provides npub voluntarily
- Operator alerts via Nostr events
Event Schema:
- Kind 21001-21003: CLINK protocol
- Kind 30078: Machine status (replaceable)
- Kind 30079: Transaction records
- Kind 14: Encrypted receipt DMs
This is the logical extension of CLINK - if we're using
Nostr for payments, why not use it for everything?
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BOLT12 problems (per @justin_shocknet):
- Onion messages add latency and failure probability
- Every hop is a point of failure (Tor-like)
- Normalizes mobile nodes (bad for Lightning)
- Redundant (LND keysend already exists)
- Astroturfed by NGOs with questionable motives
CLINK Offers advantages:
- Uses Nostr relays (commodity, trustless via NIP-44)
- No HTTP callbacks, WebSockets, or onion messages
- Keys decoupled from Lightning node identity
- Already working: ShockWallet, Lightning.Pub, Stacker News
- More traction in weeks than BOLT12 in years
Updated:
- Protocol stack recommendation
- New components (clink-handler)
- Implementation roadmap
- References section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive rethinking of the project vision:
Core Principles:
- KYC-free: No identity collection
- Lightning-native: Security through protocol
- Self-custodial: Operator and user control keys
- Privacy by default: Minimize data retention
- Autonomous machines: Reduce server dependency
Key Technical Decisions:
- LNbits as primary Lightning backend
- Cashu ecash for offline capability and privacy
- LNURL-withdraw/pay for user-friendly flows
- BOLT12 offers for static payment codes
- NFC BOLT cards for tap-to-withdraw
- Fedimint option for community custody
Architecture Options:
- A: Lean Server (recommended starting point)
- B: Serverless Machine (maximum autonomy)
- C: Fedimint Community Model
Assessment: 60%+ of existing lamassu-server is compliance code.
Recommendation: Rebuild core, reuse hardware drivers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Documents e-ink options for testing:
- Modos Paper (75Hz, open-source FPGA) - ships Jan 2026
- DASUNG Paperlike 253 (33Hz, frontlight)
- Waveshare development boards
- Industrial/outdoor options (SEEKINK, Geniatech)
Includes honest assessment of trade-offs and recommended
use cases (outdoor, solar-powered, idle signage).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive document covering:
- Compute: Raspberry Pi CM5 (recommended), Pine64 RISC-V (future)
- Bill validators: ITL NV200 with eSSP protocol, MEI Cashflow
- Bill dispensers: Puloon LCDM series, Fujitsu F53/F56
- Displays: Elo I-Series, Waveshare, Faytech open-frame
- Printers: ESC/POS compatible thermal printers
- NFC: ACR122U with libnfc, PN532 modules
- Protocol support matrix and driver migration strategy
- Bill of materials for MVP and full-featured ATMs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Benefits of shadcn-vue:
- Copy components to repo (you own the code)
- Tailwind-native (no style conflicts)
- Radix Vue primitives (a11y-first)
- Much smaller bundle (~15kb vs ~80kb tree-shaken)
- Full control over styling and behavior
Updated:
- Admin UI: Complete shadcn-vue examples with TanStack Table
- Machine UI: Note about shared shadcn-vue components
- Modernization plan: Updated architecture diagram and bundle sizes
- Bundle target: ~60kb total (down from ~120kb with PrimeVue)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update architecture diagram to show Vue 3 for both admin and machine
- Replace React decision with Vue 3 unification strategy
- Update machine UI section to include Vue 3 + Tauri
- Add Vue 3 to trade-offs table
- Link to new detailed migration docs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrate from React 18 + MUI to Vue 3 for consistency with machine UI:
- PrimeVue for components (tree-shakeable vs MUI's 300kb)
- tRPC replacing Apollo GraphQL (~5kb vs ~50kb)
- Pinia for state (same as machine UI)
- Shared ui-shared package for common components
Benefits:
- Single framework across all UIs
- ~70% bundle size reduction (400kb → 120kb)
- End-to-end type safety with tRPC
- Shared composables between admin and machine
Includes:
- Complete project structure
- PrimeVue integration with Tailwind
- tRPC client setup
- Passkey authentication flow
- 4-phase migration strategy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces legacy vanilla JS + jQuery with modern stack:
- Vue 3 with Composition API
- TypeScript throughout
- Pinia for state management
- Vite for building
- Vue I18n for localization
Includes:
- Complete project structure
- Core component examples (App.vue, stores, composables)
- WebSocket integration pattern
- i18n strategy preserving existing translations
- 4-phase migration plan
- Testing patterns with Vitest
- Performance targets (<120kb total bundle)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add lamassu-server, lamassu-machine, lamassu-install as submodules
- Add CLAUDE.md with development guidance
- Add docs/modernization-plan.md with 2026 refactoring roadmap
- Add .gitignore for Node.js/Nix development
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>