- Python 38.5%
- Svelte 27.5%
- TypeScript 21.5%
- CSS 6.7%
- Rust 4.2%
- Other 1.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| backend | ||
| packaging | ||
| receipts | ||
| ui | ||
| .gitignore | ||
| branding.py | ||
| CHANGELOG.md | ||
| config.json.example | ||
| config.py | ||
| logo.png | ||
| main.py | ||
| portal_client.py | ||
| pricing.py | ||
| pyproject.toml | ||
| README.md | ||
| theme.py | ||
| uv.lock | ||
Member portal POS till
Self-service kiosk for workshop catalogue, tabs, guests, and card-terminal payments.
UI: Tauri 2 + Svelte (WebView2 on Windows)
Backend: Python FastAPI sidecar on 127.0.0.1 (portal client, pricing, receipts, ESC/POS)
Requirements
- Python 3.12+ and
uv - Node.js 20+ and npm
- Rust (stable) + Tauri prerequisites (WebView2 on Windows)
Setup
cd helpers/pos
uv sync
cp config.json.example config.json
# edit config.json — portal URL, API key, terminal id
cd ui
npm install
Run (development)
From helpers/pos/ui:
node ./pos-dev.mjs
Or npm run pos (same Node entrypoint). Prefer node ./pos-dev.mjs over npm run tauri dev on Windows so Ctrl+C is not wrapped by npm.cmd (which can prompt to terminate a batch job).
Tauri starts the Vite UI and spawns the Python sidecar via uv run python -m backend.app with a random loopback bearer. The webview never sees api_key. The sidecar runs in its own process group so Ctrl+C is handled by the till, which stops the sidecar tree cleanly.
Sidecar alone (browser/debug):
cd helpers/pos
export POS_SIDECAR_BEARER=dev-token # optional in open-dev mode
uv run python -m backend.app
cd ui
npm run dev
Manual smoke path
- Confirm portal online (no offline lock). Catalogue tiles appear.
- Tap items (tier/quantity pads via soft keyboard). Lines stay local until sign-in.
- Sign in: claim QR → approve on phone, or scan wallet QR. Theme/avatar apply; cart flushes to open tab.
- Edit/remove a line (confirm modal). Confirm any estimate if present.
- Member: Pay on EFTPOS (complete or cancel on terminal overlay), or Send invoice, or Keep tab open.
- Guest path: Continue as guest (optional name) → EFTPOS only; 18+ cart greys pay with login required.
- Idle ~90s or Keep tab open → session clears; flushed tab lines remain on portal.
- Triple-tap brand → PIN → admin custom amount; triple-tap again for cut / test receipt / reprint / Exit POS.
Configuration
Copy config.json.example to config.json (gitignored). Override path with POS_CONFIG_PATH for packaged installs (keep config outside the read-only bundle). The sidecar exits on startup if config.json is missing or invalid — it will not fall back to a placeholder portal URL.
Optional resolution (e.g. "1024x768") opens a windowed client area of that size for hardware-preview debugging (Windows title bar is extra). Omit or set null for fullscreen kiosk mode.
Receipt printer
Register receipts always return ASCII for the success overlay. To also print on the USB ESC/POS printer:
"receipt": {
"enabled": true,
"printer_type": "escpos",
"usb_vendor_id": "0x04c5",
"usb_product_id": "0x1104",
"paper_width_chars": 48,
"logo_width_dots": 576
}
- Set
usb_product_idfrom the device (see below). Example product ID0x1104is a common “line thermal printer” listing — confirm on your unit. paper_width_chars(default 48) is the column width for on-screen and printed text.- Logo for printed receipts is
org_logo_wide.pngfetched from the portal (/static/img/…) into the sidecar on launch (and refreshed with the till bootstrap). It is converted to 1-bit black and white before printing. The header uses portalorg_logo.pngthe same way. On-screen ASCII still trimsRef:at the first-; the printer also adds a CODE128 barcode of the full reference. - When
enabledis false orprinter_typeisascii_debug, no USB traffic is sent.
Find USB IDs
- Linux:
lsusb(look for Fujitsu / the printer line, e.g.04c5:1104). - Windows: Device Manager → printer → Properties → Details → Hardware Ids (
VID_xxxx&PID_yyyy).
Windows USB access: the till bundles libusb-package so pyusb can talk to the device. Older printers need a WinUSB (or libusb) driver binding — e.g. via Zadig — if Windows only has a spooler printer driver and raw USB open fails.
Packaging
- Build the Python sidecar:
packaging/build-sidecar.ps1 - Copy
dist/pos-sidecar/into the till install tree (or wire as TauriexternalBin— see script comments) - From
ui:npm run tauri build - Place
config.jsonnext to the install / setPOS_CONFIG_PATH
Project layout
ui/— Tauri + Svelte kiosk shellbackend/— FastAPI sidecar (/health, catalogue, pricing, identity, tab, payments, receipts)portal_client.py/pricing.py/config.py/theme.py/receipts/main.py— deprecated stub (prints how to launch Tauri)
Security notes
- Sidecar binds
127.0.0.1only - Tauri injects a one-time bearer; all sidecar routes require it when
POS_SIDECAR_BEARERis set api_keynever leaves Python
API scopes
Recommended key scopes: workshop_tab:read, workshop_tab:write, workshop_tab:settle, catalogue:read, identity:resolve, identity:claim, payments:intent, print:read, print:write.