add all crates: domain, protocol, application, client, adapters, ESP32 firmware

Server: domain (entities, value objects, ports), protocol (postcard wire types),
application (config service, data projection), adapters (config-memory, tcp-server),
bootstrap (composition root with fake data).

Client: client-domain (layout engine, render tree, HAL ports),
client-application (message handling, repaint commands),
adapters (tcp-client, display-terminal), client-desktop (end-to-end working).

ESP32: client-esp32 firmware with ILI9341 display over SPI, WiFi networking.
Display test verified on hardware — landscape orientation, text rendering works.

60 workspace tests, all passing.
This commit is contained in:
2026-06-18 21:43:59 +02:00
parent 6ad76b98a2
commit 557cceb498
83 changed files with 5844 additions and 1 deletions

View File

@@ -0,0 +1,85 @@
use domain::{
LayoutNode, ContainerNode, LayoutChild, Direction, Sizing,
};
use client_domain::{BoundingBox, LayoutEngine};
fn screen() -> BoundingBox {
BoundingBox::screen(240, 320)
}
#[test]
fn diff_detects_moved_widget_after_layout_change() {
let layout_a = LayoutNode::Container(ContainerNode {
direction: Direction::Row,
gap: 0,
padding: 0,
children: vec![
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(1) },
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(2) },
],
});
let layout_b = LayoutNode::Container(ContainerNode {
direction: Direction::Column,
gap: 0,
padding: 0,
children: vec![
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(1) },
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(2) },
],
});
let tree_a = LayoutEngine::compute(&layout_a, screen());
let tree_b = LayoutEngine::compute(&layout_b, screen());
let mut changed = tree_b.diff(&tree_a);
changed.sort();
assert_eq!(changed, vec![1, 2]);
}
#[test]
fn diff_returns_empty_for_identical_layouts() {
let layout = LayoutNode::Container(ContainerNode {
direction: Direction::Row,
gap: 0,
padding: 0,
children: vec![
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(1) },
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(2) },
],
});
let tree_a = LayoutEngine::compute(&layout, screen());
let tree_b = LayoutEngine::compute(&layout, screen());
assert!(tree_b.diff(&tree_a).is_empty());
}
#[test]
fn diff_detects_added_and_removed_widgets() {
let layout_a = LayoutNode::Container(ContainerNode {
direction: Direction::Row,
gap: 0,
padding: 0,
children: vec![
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(1) },
],
});
let layout_b = LayoutNode::Container(ContainerNode {
direction: Direction::Row,
gap: 0,
padding: 0,
children: vec![
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(2) },
],
});
let tree_a = LayoutEngine::compute(&layout_a, screen());
let tree_b = LayoutEngine::compute(&layout_b, screen());
let mut changed = tree_b.diff(&tree_a);
changed.sort();
// widget 1 was removed (in old but not new), widget 2 was added (in new but not old)
assert_eq!(changed, vec![1, 2]);
}