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:
2026-06-18 18:05:19 +02:00
commit 6ad76b98a2
6 changed files with 318 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
# 0001 — Event-driven CQRS
**Status:** accepted
**Date:** 2026-06-18
## Context
K-Frame has a natural write/read split: the write model is config (widgets, data sources, layout) mutated via web UI, the read model is runtime state (current widget data, active layout) pushed to display clients. We need a mechanism to bridge the two — when config changes, runtime behavior must update (restart polling loops, push new layout to clients).
## Decision
Full event-driven CQRS. Commands mutate config and emit domain events. The read side projects current state from events and DataSource poll results. Events drive side effects: restarting poll loops, pushing updates to connected clients.
## Alternatives considered
**Imperative approach** — commands mutate config, then application layer imperatively calls "restart poll loop" / "push to clients." Simpler, but tangles command handling with side-effect orchestration. Adding new reactions to config changes requires modifying command handlers.
## Consequences
- Clean separation: command handlers only mutate config and emit events, never trigger side effects directly.
- New reactions to config changes are just new event listeners — no command handler modification.
- More moving parts than the imperative approach — event bus, event handlers, projections.
- Events are in-memory only (no event store / event sourcing). This is CQRS, not ES.

View File

@@ -0,0 +1,23 @@
# 0002 — Static dispatch over trait objects
**Status:** accepted
**Date:** 2026-06-18
## Context
Hexagonal architecture uses port traits extensively. Rust offers two ways to consume them: trait objects (`Box<dyn Port>`, dynamic dispatch) or generics (`T: Port`, static dispatch / monomorphization).
## Decision
Use generics for all port traits. No `Box<dyn>` anywhere. Composition roots (bootstrap on server, firmware crate on client) are the only places that know concrete types. Generic parameters propagate through application and presentation layers.
## Alternatives considered
**Trait objects** — simpler type signatures, less generic noise. But adds vtable indirection, heap allocation per trait object, and prevents compiler optimizations. On ESP32 with limited RAM, every allocation matters.
## Consequences
- Zero-cost abstractions — port calls are monomorphized, inlined where possible.
- More verbose type signatures — structs and functions carry generic parameters.
- Compile times may increase due to monomorphization.
- Consistent approach across server and client codebases.

View File

@@ -0,0 +1,26 @@
# 0003 — postcard over FlatBuffers
**Status:** accepted
**Date:** 2026-06-18
## Context
Need a binary serialization format for the TCP protocol between server and ESP32 clients. Must be compact (bandwidth-constrained), ideally `no_std` compatible for future bare-metal targets.
## Decision
Use `postcard` with `serde` derives instead of FlatBuffers.
## Alternatives considered
**FlatBuffers** — zero-copy reads, schema-driven. But requires external codegen toolchain (flatc), `no_std` support is limited, and zero-copy is overkill for our small messages (a handful of widgets).
**Custom binary protocol** — maximum control, zero deps. But significant implementation effort for serialize/deserialize code, error-prone, and postcard already solves this.
## Consequences
- `no_std` protocol crate out of the box — future-proof for bare-metal targets.
- No codegen step — types are normal Rust structs with serde derives.
- Compact wire format via varint encoding.
- No zero-copy reads — messages are deserialized into owned types. Acceptable for our message sizes.
- Adds `serde` and `postcard` as dependencies to the protocol crate.