add domain glossary, design spec, and ADRs
CONTEXT.md: domain model with entities (WidgetConfig, DataSource, LayoutPreset), value objects (Layout, LayoutNode, KeyMapping, WidgetState, Sizing), architecture decisions (CQRS, static dispatch, postcard), client domain model, and design constraints.
This commit is contained in:
173
docs/superpowers/specs/2026-06-18-k-frame-design.md
Normal file
173
docs/superpowers/specs/2026-06-18-k-frame-design.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# K-Frame — Design Spec
|
||||
|
||||
IoT dashboard system. Server aggregates data from configurable sources, pushes to connected display clients via FlatBuffers/TCP. Clients own all rendering.
|
||||
|
||||
## Hardware
|
||||
|
||||
- **Server**: Intel N100 homeserver
|
||||
- **Client**: LOLIN Mini Kit ESP32 (ESP-WROOM-32) + 2.4" ILI9341 LCD (240×320, SPI)
|
||||
|
||||
## Domain Model
|
||||
|
||||
- **Widget** — generic display primitive. Has an ID, type hint (text, icon+text, etc.), and key-value data payload. Purely semantic, no visual info.
|
||||
- **LayoutNode** — recursive tree. Either a Container (row/column with children) or a Leaf (references widget by ID).
|
||||
- **Screen** — named layout tree + its widget set. The unit sent to clients.
|
||||
- **DataSource** — configured external data feed. Has a type, config (URL, API key, JSON path, etc.), poll interval. Maps output to one or more widget IDs.
|
||||
- **Client** — connected consumer (ESP32, future KDE Plasma widget, etc.). Receives screen updates.
|
||||
|
||||
## Server Architecture
|
||||
|
||||
Hexagonal (ports & adapters) with DDD. Same patterns as thoughts/ and movies-diary/ projects.
|
||||
|
||||
### Crate structure
|
||||
|
||||
```
|
||||
crates/
|
||||
domain/ — models, value objects, port traits. Zero deps.
|
||||
application/ — use cases: manage config, poll data sources, push to clients
|
||||
api-types/ — DTOs for web UI API
|
||||
presentation/ — Axum HTTP (config web UI) + TCP listener (client connections)
|
||||
bootstrap/ — composition root, wires adapters into ports, starts everything
|
||||
adapters/
|
||||
config-store/ — ConfigRepository impl (TOML or SQLite, behind the port)
|
||||
weather/ — weather API adapter
|
||||
media/ — Navidrome/Subsonic adapter
|
||||
xtb/ — XTB portfolio adapter
|
||||
rss/ — RSS feed adapter
|
||||
http-json/ — generic HTTP + JSON path extraction adapter
|
||||
webhook/ — webhook receiver (incoming HTTP → widget data)
|
||||
```
|
||||
|
||||
A shared `protocol` crate lives at workspace root level, used by both server and client:
|
||||
|
||||
```
|
||||
crates/
|
||||
protocol/ — FlatBuffers .fbs schema + generated Rust code, no_std compatible
|
||||
```
|
||||
|
||||
### Dependency rule
|
||||
|
||||
```
|
||||
bootstrap ──► presentation ──► application ──► domain ◄── adapters
|
||||
```
|
||||
|
||||
- **domain** — zero framework deps, pure business logic, defines all port traits
|
||||
- **application** — orchestrates use cases, depends only on domain
|
||||
- **presentation** — HTTP handlers (Axum) + TCP server, depends on domain + application
|
||||
- **adapters** — implement domain ports, depend inward on domain only
|
||||
- **bootstrap** — composition root, wires adapters into ports
|
||||
|
||||
### Domain ports
|
||||
|
||||
- **ConfigRepository** — CRUD for screens, widgets, data sources, layouts
|
||||
- **DataSourcePort** — trait each data source adapter implements: `async fn poll(&self, config) -> WidgetData`
|
||||
- **ClientBroadcaster** — push layout/data updates to connected clients
|
||||
- **DataSourceRegistry** — manages polling loops per data source, detects changes, only pushes diffs
|
||||
|
||||
### Data flow
|
||||
|
||||
1. Bootstrap starts polling loops per configured DataSource (each with its own configurable interval)
|
||||
2. Adapter polls external API → returns new widget data
|
||||
3. Application compares with previous value → if changed, pushes DataUpdate to all clients via ClientBroadcaster
|
||||
4. User changes layout in web UI → Application pushes LayoutUpdate to all clients
|
||||
5. User changes config → Application restarts/adds/removes polling loops as needed
|
||||
|
||||
## Protocol
|
||||
|
||||
FlatBuffers over TCP. Length-prefixed framing: 4 bytes (u32 big-endian) message length, then FlatBuffers payload.
|
||||
|
||||
### Server → Client
|
||||
|
||||
- **ScreenUpdate** — full layout tree + all widget data. Sent on initial connection and when user changes layout.
|
||||
- **DataUpdate** — one or more (widget ID + new data) pairs. Sent only when polled data actually changes. Only changed widgets included.
|
||||
|
||||
### Client → Server
|
||||
|
||||
- **Heartbeat** — keepalive so server can detect dead clients.
|
||||
|
||||
### Widget data format
|
||||
|
||||
Generic key-value map in FlatBuffers. E.g. weather widget: `{"icon": "cloud_rain", "temperature": "5.4", "unit": "°C"}`. Client interprets keys however it wants. Server doesn't prescribe rendering.
|
||||
|
||||
### Bandwidth
|
||||
|
||||
- Only changed widgets sent (diff against previous state)
|
||||
- FlatBuffers is zero-copy and compact
|
||||
- Layout tree not resent unless it actually changes
|
||||
- Heartbeat is minimal
|
||||
|
||||
### Extensibility
|
||||
|
||||
Protocol should be designed so auth handshake can be added later without breaking existing message types.
|
||||
|
||||
## Client (ESP32 Firmware)
|
||||
|
||||
Rust firmware using `esp-idf-hal` / `esp-idf-svc`.
|
||||
|
||||
### Boot sequence
|
||||
|
||||
1. Show K logo splash (baked into firmware as RGB565 bitmap)
|
||||
2. Connect WiFi (credentials in firmware config / NVS)
|
||||
3. Connect TCP to server
|
||||
4. Receive ScreenUpdate → render layout
|
||||
5. Listen for DataUpdates, re-render changed widgets only
|
||||
|
||||
### Rendering
|
||||
|
||||
- `embedded-graphics` for drawing primitives (text, rectangles, bitmaps)
|
||||
- Custom layout engine: walks LayoutNode tree, computes bounding boxes, assigns positions
|
||||
- Container nodes split available space among children (row = horizontal, column = vertical)
|
||||
- Leaf nodes render their widget using generic key-value data
|
||||
- Client decides: fonts, sizes, overflow behavior, marquee, truncation, padding
|
||||
- Small icon set baked into firmware as bitmaps
|
||||
|
||||
### Composable layouts
|
||||
|
||||
User composes layout tree freely. If it doesn't fit or renders poorly, user's responsibility to fix. Server may provide hints/warnings but does not enforce.
|
||||
|
||||
### Marquee
|
||||
|
||||
If text overflows its computed bounding box, client auto-scrolls horizontally. Smooth pixel-by-pixel animation.
|
||||
|
||||
### Offline behavior
|
||||
|
||||
- Keep last received data in memory, keep rendering
|
||||
- Show small indicator (dot/icon in corner) when TCP connection lost
|
||||
- Reconnect loop with backoff
|
||||
|
||||
### Display-agnostic server
|
||||
|
||||
Server sends semantic data only. All rendering decisions (overflow, marquee, truncation, font, color, layout computation) are client-side. Future clients (KDE Plasma widget, web dashboard, etc.) interpret the same protocol differently.
|
||||
|
||||
## Web UI (Config)
|
||||
|
||||
Served by same binary via Axum. Simple HTML (static with forms or lightweight SPA).
|
||||
|
||||
### Features
|
||||
|
||||
- **Manage data sources** — add/edit/remove. Configure type, URL, API keys, poll interval, JSON path.
|
||||
- **Manage widgets** — create widgets, assign to data source, define key mappings.
|
||||
- **Compose layouts** — build layout tree: add containers (row/column), nest them, place widgets as leaves.
|
||||
- **Push changes** — saving config triggers immediate LayoutUpdate to all connected clients.
|
||||
- **View connected clients** — see which clients are online (nice-to-have).
|
||||
|
||||
No auth for now. Presentation crate can add middleware later.
|
||||
|
||||
## Initial Data Source Adapters
|
||||
|
||||
| Adapter | Direction | Description |
|
||||
|------------|-----------|--------------------------------------------------|
|
||||
| weather | poll | OpenWeather or similar, configurable interval |
|
||||
| media | poll | Navidrome/Subsonic API (now playing, etc.) |
|
||||
| xtb | poll/ws | XTB portfolio value |
|
||||
| rss | poll | RSS feed, configurable interval |
|
||||
| http-json | poll | Generic: poll any URL, extract via JSON path |
|
||||
| webhook | push | Server exposes endpoint, external services POST |
|
||||
|
||||
## Multi-client Support
|
||||
|
||||
Multiple clients (ESP32 devices, future Plasma widgets) connect to same server. All receive same layout tree and data updates via broadcast.
|
||||
|
||||
## Config Storage
|
||||
|
||||
Abstracted behind ConfigRepository port. Initial adapter can be TOML file or SQLite — implementation detail hidden from domain. Web UI reads/writes through the same port.
|
||||
Reference in New Issue
Block a user