Gabriel Kaszewski fa097771d4 arch: push wire types out of ClientApp, extract event_service, cleanup dead code
- ClientApp stores domain types, RepaintCommand carries DisplayHint + Vec<(String,Value)>
- adapters no longer convert Wire→Domain (eliminated duplication in esp32 + desktop)
- event_service in application layer handles LayoutChanged/WebhookDataReceived/ThemeChanged
- bootstrap event_handler reduced to 10-line dispatcher
- polling_service reuses event_service::apply_and_broadcast (deduplicated broadcast pattern)
- AppState.config_service() replaces 11 inline ConfigService::new() calls
- delete unused poll_interval_secs parameter chain
- delete unused StoragePort/ClientConfig (zero implementations)
2026-06-19 18:30:14 +02:00
2026-06-19 02:55:33 +02:00
2026-06-18 23:14:43 +02:00
2026-06-19 02:55:33 +02:00
2026-06-19 02:55:33 +02:00

K-Frame

IoT dashboard system. A server aggregates data from external sources (weather APIs, media players, RSS feeds, webhooks) and pushes rendered updates to ESP32 display clients over TCP.

Server polls data sources, projects values through configurable widget mappings, computes layouts, and broadcasts binary frames to connected clients. Configuration via REST API + web UI.

Clients receive structured data + layout trees, compute bounding boxes, format text per display hint rules, and render to hardware. The client domain is chip-agnostic — the same rendering engine runs on ESP32 (ILI9341 TFT) and desktop (terminal).

Web UI (SPA) configures data sources, widgets, layouts, themes, and presets. Includes a live layout preview that mirrors what the physical display shows.

Architecture

Hexagonal / ports-and-adapters with full CQRS. Domain logic has zero framework dependencies. Static dispatch everywhere (no Box<dyn>, no trait objects).

┌─────────────────── Server ───────────────────┐
│  domain/         entities, value objects,     │
│                  ports (traits)               │
│  application/    use cases, data projection   │
│  bootstrap/      composition root, polling    │
│                                               │
│  adapters/                                    │
│    config-sqlite   persistence                │
│    http-api        REST API (Axum)            │
│    tcp-server      binary protocol broadcast  │
│    http-json       external API polling       │
│    media, rss      source-specific adapters   │
│    data-generators clock, static text         │
│    auth            argon2 + JWT               │
├─────────────────── Shared ───────────────────┤
│  protocol/       wire types, postcard serde   │
│  api-types/      REST DTOs                    │
├─────────────────── Client ───────────────────┤
│  client-domain/  layout engine, render engine │
│                  markup parser, scroll state  │
│  client-application/  message handling        │
│  client-esp32/   ESP32 firmware (ILI9341)     │
│  client-desktop/ terminal debug client        │
├──────────────────── SPA ─────────────────────┤
│  spa/            React + TanStack Router      │
│                  shadcn/ui + Tailwind         │
└──────────────────────────────────────────────┘

Key design decisions

  • postcard + serde for the wire protocol — compact varint encoding, no_std compatible, no codegen
  • Static dispatch — port traits used as generic bounds, not trait objects. Composition root resolves all types
  • Event-driven CQRS — config mutations emit domain events, read model projected in memory
  • Domain-owned rendering — client-domain owns text wrapping, alignment, color markup, scroll. DisplayPort is a thin pixel-pusher (draw_text_span, fill_rect, flush)
  • WiFi provisioning — ESP32 boots into AP captive portal if no config in NVS, auto-falls back on WiFi failure

See docs/adr/ for architectural decision records and CONTEXT.md for the domain glossary.

Features

  • Data sources: HTTP/JSON, weather, media (Subsonic/Navidrome), RSS, webhooks, clock, static text
  • Layout engine: flexbox-like containers (row/column, fixed/flex sizing, gap, padding, justify-content, align-items)
  • Theming: 5 configurable colors (primary, secondary, accent, text, background), live push to clients
  • Rich text: inline color markup ({primary}text{/}, {#FF0000}hex{/})
  • Widget alignment: per-widget horizontal/vertical text alignment (left/center/right, top/middle/bottom), reflected in layout preview
  • Connection indicator: green/red dot on ESP32 display showing server connectivity
  • Overflow scroll: bounce animation when content exceeds widget bounds, speed auto-derived from overflow
  • Captive portal: ESP32 AP mode with DNS + HTTP config form for WiFi provisioning
  • Auth: argon2 password hashing, JWT tokens, protected API routes

Prerequisites

  • Rust (stable) — server, desktop client, shared crates
  • Rust ESP32 toolchainespup, xtensa target. See esp-rs book
  • Node.js / Bun — SPA development
  • SQLite — server persistence (created automatically)

Quick start

# 1. Clone and configure
cp .env.example .env
# Edit .env — set JWT_SECRET and KFRAME_ENCRYPTION_KEY

# 2. Start the server
make server

# 3. Start the SPA dev server (separate terminal)
cd spa && bun install && bun run dev
# Open http://localhost:5173

# 4. Register an account (first launch = setup mode)
# 5. Add a data source, create widgets, build a layout

ESP32 wiring

2.4" ILI9341 SPI LCD module → ESP-WROOM-32:

LCD Pin ESP32 GPIO Function
VCC 3V3 Power
GND GND Ground
DIN GPIO23 SPI MOSI
CLK GPIO18 SPI SCLK
CS GPIO26 Chip select
DC GPIO21 Data/command
RST GPIO22 Reset
BL 3V3 Backlight (always on)

Uses SPI2 (HSPI) at 26 MHz. Pin assignments are in crates/client-esp32/src/main.rs.

ESP32 client

# Build firmware
make esp-build

# Flash (default port: /dev/ttyACM0)
make esp-flash

# Flash with custom port
make esp-flash ESP_PORT=/dev/ttyUSB0

# Flash and monitor serial output
make esp-run

# On first boot: connect to "KFrame-Setup" WiFi, enter credentials in captive portal

Desktop client

make desktop
# Connects to localhost:2699, prints render commands to terminal

Development

# Full check suite (fmt + clippy + test)
make check

# Auto-fix formatting and clippy warnings
make fix

# Run tests only
make test

# SPA type checking
cd spa && bun run typecheck

Project structure

Path Description
crates/domain/ Entities, value objects, port traits
crates/application/ Use cases (ConfigService, DataProjection)
crates/protocol/ Wire types, encode/decode (no_std)
crates/bootstrap/ Server composition root
crates/adapters/ All port implementations
crates/client-domain/ Display-agnostic rendering engine
crates/client-application/ Client message handling
crates/client-esp32/ ESP32 firmware
crates/client-desktop/ Terminal debug client
crates/api-types/ REST API DTOs
spa/ React SPA
docs/adr/ Architecture decision records
CONTEXT.md Domain glossary

Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Run make check before pushing — CI runs the same checks
  4. Open a PR with a clear description of what changed and why

The domain glossary in CONTEXT.md defines the canonical language. Use it in code, commits, and PRs. If you're adding a new concept, update the glossary.

Architecture decisions are documented in docs/adr/. If your change involves a hard-to-reverse design choice, write an ADR.

License

MIT

Description
No description provided
Readme MIT 712 KiB
Languages
Rust 50.5%
TypeScript 48.4%
CSS 0.7%
Makefile 0.2%