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)
This commit is contained in:
2026-06-19 18:30:14 +02:00
parent 7001b5e911
commit fa097771d4
21 changed files with 264 additions and 217 deletions

View File

@@ -4,7 +4,6 @@ pub struct ServerConfig {
pub database_url: String,
pub tcp_addr: String,
pub http_addr: String,
pub poll_interval_secs: u64,
pub spa_dir: Option<String>,
}
@@ -15,10 +14,6 @@ impl ServerConfig {
.unwrap_or_else(|_| "sqlite:kframe.db?mode=rwc".into()),
tcp_addr: env::var("KFRAME_TCP_ADDR").unwrap_or_else(|_| "0.0.0.0:2699".into()),
http_addr: env::var("KFRAME_HTTP_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".into()),
poll_interval_secs: env::var("KFRAME_POLL_INTERVAL_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(5),
spa_dir: env::var("KFRAME_SPA_DIR").ok(),
}
}

View File

@@ -1,9 +1,8 @@
use application::DataProjection;
use config_sqlite::SqliteConfigStore;
use domain::{BroadcastPort, ConfigRepository, DomainEvent};
use std::sync::Arc;
use tcp_server::{TcpBroadcaster, TcpEventBus};
use tracing::{error, info, warn};
use tracing::{error, warn};
pub async fn run(
event_bus: Arc<TcpEventBus>,
@@ -15,69 +14,10 @@ pub async fn run(
loop {
match rx.recv().await {
Ok(DomainEvent::LayoutChanged { layout }) => {
let widgets = match config.list_widgets().await {
Ok(w) => w,
Err(e) => {
error!(error = %e, "failed to fetch widgets for screen update");
continue;
}
};
let mut widget_states = Vec::new();
for w in &widgets {
if let Some(s) = projection.get_state(w.id).await {
widget_states.push((w.id, w.display_hint.clone(), s));
}
}
if let Err(e) = broadcaster
.push_screen_update(&layout, &widget_states)
.await
{
error!(error = %e, "failed to push screen update");
}
info!("layout changed, pushed screen update to clients");
}
Ok(DomainEvent::WebhookDataReceived { source_id, data }) => {
let widgets = match config.list_widgets().await {
Ok(w) => w,
Err(e) => {
error!(error = %e, "failed to fetch widgets for webhook");
continue;
}
};
let changed = projection
.apply_poll_result(source_id, &data, &widgets)
Ok(event) => {
application::event_service::handle_event(event, &config, &broadcaster, &projection)
.await;
if !changed.is_empty() {
let with_hints: Vec<_> = changed
.iter()
.filter_map(|(id, state)| {
let hint = widgets.iter().find(|w| w.id == *id)?.display_hint.clone();
Some((*id, hint, state.clone()))
})
.collect();
if let Err(e) = broadcaster.push_data_update(&with_hints).await {
error!(error = %e, "failed to push webhook data update");
}
info!(
source_id,
count = changed.len(),
"webhook data received, pushed update"
);
}
}
Ok(DomainEvent::ThemeChanged { theme }) => {
if let Err(e) = broadcaster.push_theme_update(&theme).await {
error!(error = %e, "failed to push theme update");
}
info!("theme changed, pushed update to clients");
}
Ok(_) => {}
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
warn!(skipped = n, "event handler lagged, missed events");
}

View File

@@ -90,11 +90,5 @@ async fn main() -> Result<()> {
event_handler::run(ev_bus, ev_config, ev_bc, ev_proj).await;
});
polling::run(
config_store,
broadcaster,
projection,
cfg.poll_interval_secs,
)
.await
polling::run(config_store, broadcaster, projection).await
}

View File

@@ -57,7 +57,6 @@ pub async fn run(
config: Arc<SqliteConfigStore>,
broadcaster: Arc<TcpBroadcaster>,
projection: Arc<DataProjection>,
_poll_interval_secs: u64,
) -> Result<()> {
let adapters = Adapters {
http: Arc::new(HttpJsonAdapter::new()),