felt/.planning/research/STACK.md

16 KiB

Stack Research

Domain: Edge-cloud poker venue management platform (ARM64 SBC + cloud hybrid) Researched: 2026-02-28 Confidence: MEDIUM-HIGH (core stack verified via official sources; peripheral libraries verified via pkg.go.dev and GitHub releases; CGO cross-compilation complexity is a known risk requiring phase-specific validation)


Core Technologies

Technology Version Purpose Why Recommended
Go 1.26 Backend runtime (Leaf + Core shared codebase) Single binary deployment, ARM64 cross-compilation, goroutine concurrency for real-time tournament state, excellent stdlib HTTP. Released Feb 10, 2026.
SvelteKit 2.53.x Operator UI, player PWA, admin dashboard Single codebase for SPA, SSR, and PWA modes. SvelteKit 2 + Svelte 5 runes are production-stable. Adapter-static for Go embed, adapter-node for standalone.
Svelte 5.53.x Frontend framework Runes reactivity model ($state, $derived, $effect) handles high-frequency real-time data (100ms tournament clock) without store complexity. Svelte 5 stable since Oct 2024.
NATS Server 2.12.4 Embedded message broker on Leaf; clustered on Core Embeds directly into Go binary (~10MB RAM overhead), JetStream provides offline-durable queuing, ordered replay on reconnect, KV store. ARM64 native packages available.
LibSQL (go-libsql) unreleased / CGO Embedded SQLite-compatible DB on Leaf SQLite-compatible with built-in replication support. Supports linux/arm64 natively via precompiled binaries. CGO_ENABLED=1 required.
PostgreSQL 16 Relational DB on Core Standard choice for Core; multi-tenant RLS, full-text search for player lookup, proven at scale. LibSQL mirrors for sync path.
Tailwind CSS 4.x UI styling v4 uses Vite plugin (no PostCSS config needed), 100x faster incremental builds, CSS-native config. Pairs naturally with SvelteKit's Vite build pipeline.
Netbird latest WireGuard mesh overlay network Self-hosted, provides mesh VPN + reverse proxy + DNS + SSH + firewall policies in one platform. Zero-config peer connection through NAT. ARM64 client supported.
Authentik 2026.2.x Self-hosted OIDC Identity Provider Integrates natively with Netbird self-hosted. Provides SSO for operator login, LDAP fallback, Apache 2.0. Requires PostgreSQL + Redis; runs in LXC on Core.

Supporting Libraries — Go Backend

Library Version Purpose When to Use
github.com/go-chi/chi/v5 v5.2.5 HTTP router All Leaf and Core HTTP handlers. Lightweight, fully net/http compatible, composable middleware, no magic.
github.com/nats-io/nats.go v1.49.0 NATS client (JetStream API) Publishing, consuming, and managing JetStream streams from application code. Uses the new jetstream sub-package API.
github.com/nats-io/nats-server/v2 v2.12.4 Embedded NATS server Leaf embeds this directly via server.NewServer() + EnableJetStream(). Not used on Core (standalone server process).
github.com/pressly/goose/v3 v3.27.0 Database migrations Runs schema migrations at startup via embed.FS. Supports SQLite + PostgreSQL with same migration files.
github.com/sqlc-dev/sqlc v1.30.0 Type-safe SQL code generation Generate Go structs and query functions from raw SQL. Eliminates ORM overhead, keeps SQL as SQL.
github.com/coder/websocket v1.8.14 WebSocket server Real-time push to operator UI and player PWA. Actively maintained successor to nhooyr/websocket. Context-aware, zero-allocation.
github.com/golang-jwt/jwt/v5 latest JWT token handling Offline PIN-based auth on Leaf (no network dependency). Validates tokens from Authentik OIDC on Core.
go.opentelemetry.io/otel 1.x Observability Structured tracing for state machine transitions, tournament operations. Add otelchi for per-request span creation.
github.com/riandyrn/otelchi latest OpenTelemetry middleware for chi Automatic HTTP span creation. Plug into chi middleware chain.

Supporting Libraries — SvelteKit Frontend

Library Version Purpose When to Use
@vite-pwa/sveltekit latest PWA + service worker Player PWA offline caching, installable shell. Wraps Workbox. Required for offline player access.
vite-plugin-pwa latest PWA build tooling Underlying PWA config for manifest, service worker generation.
@tailwindcss/vite 4.x Tailwind v4 Vite integration Add before sveltekit() in vite.config. CSS-native config via app.css @import "tailwindcss".
svelte-sonner latest Toast notifications Operator action feedback (seat assignments, registration, bust-outs). Lightweight, accessible.
@lucide/svelte latest Icon set Consistent iconography. Tree-shakeable, Svelte-native bindings.

Development Tools

Tool Purpose Notes
Task (Taskfile) Build orchestration Define build:leaf, build:core, build:frontend, cross-compile:arm64 tasks. Replaces Makefile with YAML syntax.
Docker buildx ARM64 cross-compilation For CGO-enabled builds targeting linux/arm64. Use --platform linux/arm64 with aarch64-linux-gnu-gcc cross-compiler in container.
air Go live reload (dev) github.com/air-verse/air — watches Go files, rebuilds on change. Dev only.
golangci-lint Go linting Runs multiple linters. Critical for enforcing error handling patterns in state machine code.
playwright E2E testing Test operator UI flows. Svelte-compatible.
sqlc SQL → Go codegen Run as part of build pipeline. Check generated files into git.

Installation

Go Backend

# Initialize module
go mod init felt

# Core router and middleware
go get github.com/go-chi/chi/v5@v5.2.5
go get github.com/go-chi/cors

# NATS (client + embedded server)
go get github.com/nats-io/nats.go@v1.49.0
go get github.com/nats-io/nats-server/v2@v2.12.4

# Database — LibSQL (CGO required)
# Note: CGO_ENABLED=1 and linux/arm64 cross-compiler required for Leaf builds
go get github.com/tursodatabase/go-libsql

# Database — PostgreSQL for Core
go get github.com/jackc/pgx/v5

# Migrations and query gen
go get github.com/pressly/goose/v3@v3.27.0
# sqlc: install as tool
go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.30.0

# Auth
go get github.com/golang-jwt/jwt/v5

# WebSocket
go get github.com/coder/websocket@v1.8.14

# Observability
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get github.com/riandyrn/otelchi

SvelteKit Frontend

# Scaffold
npx sv create felt-frontend
# Choose: SvelteKit, TypeScript, Tailwind

# Or manually:
npm create svelte@latest felt-frontend

# Tailwind v4
npm install tailwindcss @tailwindcss/vite

# PWA
npm install -D @vite-pwa/sveltekit vite-plugin-pwa

# UI libraries
npm install svelte-sonner @lucide/svelte

Alternatives Considered

Recommended Alternative When to Use Alternative
chi (router) Gin, Echo, Fiber Gin if you want more batteries-included middleware and faster onboarding. Fiber if raw throughput benchmarks matter more than stdlib compatibility. Chi chosen here because it's pure net/http, no magic, easy to embed with NATS server in same binary.
LibSQL (go-libsql) mattn/go-sqlite3 go-sqlite3 if you never need replication or remote sync. go-libsql is SQLite-compatible but adds replication capability needed for Leaf→Core sync path.
LibSQL (go-libsql) modernc.org/sqlite modernc if CGO is unacceptable (pure Go, no cross-compile issues). Tradeoff: no replication, pure-Go performance is slower, and you lose LibSQL's sync protocol.
goose golang-migrate golang-migrate is fine but goose has cleaner embed.FS support and the sqlc community uses it as the reference migration tool.
coder/websocket gorilla/websocket gorilla/websocket if you need RFC 7455 edge cases or have existing gorilla-dependent code. gorilla is widely used but coder/websocket is the modern, context-aware successor.
NATS JetStream Redis Streams, RabbitMQ Redis Streams if you already have Redis in the stack. RabbitMQ for complex enterprise routing. NATS chosen because it embeds in the Go binary (no separate process on Leaf), runs on 10MB RAM, and handles offline-queued replay natively.
Authentik Keycloak, Authelia Keycloak if you need SAML federation or very large enterprise deployments. Authelia if you want lightweight forward-auth only. Authentik chosen for OIDC depth, active development, and documented Netbird integration.
Tailwind CSS v4 UnoCSS, vanilla CSS UnoCSS if Tailwind v4's Rust engine still has edge-case gaps in your toolchain. Vanilla CSS with CSS custom properties if the design system is simple. Tailwind v4 chosen for its systematic utility classes matching the dense information design requirement.
SvelteKit React/Next.js, Vue/Nuxt React/Next.js if you have an existing team with React expertise. SvelteKit chosen for smaller bundle sizes (critical for Pi Zero display nodes loading over WiFi), built-in PWA path, and Svelte 5 runes handling high-frequency clock updates without virtual DOM overhead.

What NOT to Use

Avoid Why Use Instead
gorilla/websocket (new code) Unmaintained since 2023, no context support, no concurrent writes github.com/coder/websocket (maintained successor)
gorm ORM magic hides SQL, bad for complex tournament state queries, generates inefficient queries, fights with LibSQL's CGO interface sqlc for generated type-safe queries, raw database/sql when needed
modernc.org/sqlite for Leaf Pure-Go SQLite has no replication — you lose the LibSQL sync protocol that enables Leaf→Core data replication tursodatabase/go-libsql (CGO, linux/arm64 prebuilt)
React/Next.js Heavy bundle — Pi Zero 2W (512MB RAM) running Chromium kiosk will struggle; Svelte compiles to vanilla JS with no runtime SvelteKit + Svelte 5
Svelte 4 / SvelteKit 1 End of active development; Svelte 5 runes are the current API; v4 stores pattern has known SSR shared-state bugs Svelte 5 + SvelteKit 2
Tailwind CSS v3 Requires PostCSS config, slower builds, JS-based config. v4 drops all of this and integrates cleaner with Vite Tailwind CSS v4 with @tailwindcss/vite plugin
OIDC-only auth on Leaf If Core/internet is down, OIDC token validation fails → operators locked out JWT-based offline PIN auth on Leaf, OIDC only when online
Global Netbird cloud Introduces Netbird as a third-party MITM for network control plane Self-hosted Netbird management server on Core (Hetzner Proxmox LXC)
Docker on Leaf (ARM64) Docker daemon adds ~100MB RAM overhead on a 4-8GB device; unnecessary abstraction for a dedicated appliance Bare systemd services; Go single binary + SvelteKit embedded build

Stack Patterns by Variant

For Leaf Node (ARM64 SBC, offline-first):

  • Go binary with embedded NATS server + JetStream store on NVMe
  • LibSQL (go-libsql, CGO) with goose migrations at startup
  • SvelteKit build embedded via //go:embed all:build in Go binary
  • Single systemd service: felt-leaf
  • Offline PIN auth via JWT; Authentik OIDC optional when online
  • CGO cross-compile: GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build

For Core Node (Hetzner Proxmox LXC, always-online):

  • Go binary (no embedded NATS — connects to standalone NATS cluster)
  • PostgreSQL (pgx/v5 driver)
  • NATS JetStream cluster for multi-venue message routing
  • Authentik + Netbird management server as separate LXC containers
  • Standard AMD64 build: GOOS=linux GOARCH=amd64 go build

For Display Nodes (Pi Zero 2W, Chromium kiosk):

  • No custom software on the node itself
  • Raspberry Pi OS Lite + X11 + Chromium in --kiosk mode
  • Chromium points to http://leaf.local/display/{node-id} (served by Leaf Go binary)
  • Display content is pure SvelteKit SPA pages served from the Leaf
  • Node management (which view to show) handled via NATS KV on Leaf

If CGO cross-compilation proves painful in CI:

  • Use Docker buildx with FROM --platform=linux/arm64 base and native arm64 runner
  • OR accept CGO complexity and use aarch64-linux-gnu-gcc in a standard amd64 CI runner
  • Do NOT switch to modernc.org/sqlite — losing replication is worse than cross-compile friction

Version Compatibility

Package Compatible With Notes
go-libsql (unreleased) Go 1.26, linux/arm64 No tagged releases; pin to commit hash in go.mod. CGO_ENABLED=1 mandatory.
@vite-pwa/sveltekit SvelteKit 2.x, Vite 5.x From v0.3.0+ supports SvelteKit 2.
goose v3.27.0 Go 1.25+ Requires Go 1.25 minimum per v3.27.0 release notes. Go 1.26 is compatible.
chi v5.2.5 Go 1.22+ Standard net/http; any Go 1.22+ supported.
Tailwind v4 Vite 5.x, SvelteKit 2.x @tailwindcss/vite must be listed before sveltekit() in vite.config plugins array.
NATS server v2.12.4 nats.go v1.49.0 Use matching client and server versions. Server v2.12.x is compatible with client v1.49.x.
Svelte 5.53.x SvelteKit 2.53.x Must use matching Svelte 5 + SvelteKit 2. Svelte 4 / SvelteKit 1 are not compatible targets.

Critical Build Notes

LibSQL CGO Cross-Compilation

go-libsql has no tagged releases on GitHub. You must pin to a specific commit:

go get github.com/tursodatabase/go-libsql@<commit-hash>

Cross-compilation for ARM64 requires the GNU cross-compiler toolchain:

# On Ubuntu/Debian CI
apt-get install gcc-aarch64-linux-gnu

# Build command
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 \
  CC=aarch64-linux-gnu-gcc \
  go build -o felt-leaf ./cmd/leaf

Alternative (recommended for CI consistency): Use Docker buildx with an ARM64 base image to build natively, avoiding cross-compiler dependency management.

SvelteKit Static Embed in Go Binary

//go:embed all:frontend/build
var frontendFS embed.FS

// In HTTP handler:
sub, _ := fs.Sub(frontendFS, "frontend/build")
http.FileServer(http.FS(sub))

SvelteKit must be built with @sveltejs/adapter-static for full embed. The Leaf serves all frontend assets from its single binary — no separate static file server.

NATS Embedded Server Setup

opts := &server.Options{
    Port:      4222,
    Host:      "127.0.0.1",
    JetStream: true,
    StoreDir:  "/data/nats/jetstream",  // on NVMe
}
ns, err := server.NewServer(opts)
ns.Start()
nc, _ := nats.Connect(ns.ClientURL())
js, _ := jetstream.New(nc)

Sources


Stack research for: Felt — Edge-cloud poker venue management platform Researched: 2026-02-28