Tighten web surfaces and clean handoff

This commit is contained in:
jan
2026-04-20 21:12:52 +02:00
parent ed1e4b49ab
commit d2ca1a2aef
13 changed files with 751 additions and 390 deletions

View File

@@ -277,6 +277,7 @@ fn handle_websocket(
let mut sequence = 1u64;
let mut last_event_millis = None::<u64>;
let mut last_event_signatures = Vec::<(Option<String>, String)>::new();
let mut last_streamed_preview = None::<crate::dto::ApiPreviewSnapshot>;
loop {
let snapshot = service.snapshot();
send_stream_message(
@@ -286,13 +287,22 @@ fn handle_websocket(
ApiStreamMessage::Snapshot(ApiStateSnapshot::from_snapshot(&snapshot)),
)?;
sequence += 1;
send_stream_message(
&mut stream,
sequence,
snapshot.generated_at_millis,
ApiStreamMessage::Preview(crate::dto::ApiPreviewSnapshot::from_snapshot(&snapshot)),
)?;
sequence += 1;
let preview_payload = crate::dto::ApiPreviewSnapshot::from_snapshot(&snapshot);
if last_streamed_preview
.as_ref()
.map(|previous| previous != &preview_payload)
.unwrap_or(true)
{
send_stream_message(
&mut stream,
sequence,
snapshot.generated_at_millis,
ApiStreamMessage::Preview(preview_payload.clone()),
)?;
sequence += 1;
last_streamed_preview = Some(preview_payload);
}
let mut new_events = snapshot
.recent_events

View File

@@ -147,6 +147,28 @@ fn web_ui_browser_smoke_serves_shell_assets_and_stream_bootstrap() {
server.shutdown();
}
#[test]
fn technical_surface_script_guards_missing_recent_events_in_state_snapshot() {
let server = start_server();
let technical_js = send_http_request(server.local_addr(), "GET", "/technical.js", None);
let state = send_http_request(server.local_addr(), "GET", "/api/v1/state", None);
let state_body: Value = serde_json::from_str(&state.body).expect("state json");
assert_eq!(technical_js.status_code, 200);
assert!(technical_js
.body
.contains("function snapshotRecentEvents(snapshot)"));
assert!(technical_js
.body
.contains("Array.isArray(snapshot?.recent_events) ? snapshot.recent_events : []"));
assert!(technical_js
.body
.contains("const recentEvents = snapshotRecentEvents(appState.snapshot);"));
assert!(state_body["state"].get("recent_events").is_none());
server.shutdown();
}
#[test]
fn state_preview_and_snapshot_endpoints_are_versioned_and_separated() {
let server = start_server();
@@ -268,6 +290,40 @@ fn technical_surface_commands_update_backend_node_targets_and_panel_mapping() {
server.shutdown();
}
#[test]
fn technical_surface_can_disable_output_again_after_enabling_it() {
let server = start_server();
let enable_mode = send_command_json(
server.local_addr(),
r#"{"command":{"type":"set_output_backend_mode","payload":{"mode":"ddp_wled"}}}"#,
);
let enable_output = send_command_json(
server.local_addr(),
r#"{"command":{"type":"set_live_output_enabled","payload":{"enabled":true}}}"#,
);
let disable_output = send_command_json(
server.local_addr(),
r#"{"command":{"type":"set_live_output_enabled","payload":{"enabled":false}}}"#,
);
assert_eq!(enable_mode.status_code, 200);
assert_eq!(enable_output.status_code, 200);
assert_eq!(disable_output.status_code, 200);
let state = send_http_request(server.local_addr(), "GET", "/api/v1/state", None);
let state_body: Value = serde_json::from_str(&state.body).expect("state json");
assert_eq!(state_body["state"]["technical"]["backend_mode"], "ddp_wled");
assert_eq!(state_body["state"]["technical"]["output_enabled"], false);
assert_eq!(
state_body["state"]["technical"]["live_status"],
"DDP (WLED) selected - output disabled"
);
server.shutdown();
}
#[test]
fn discovery_scan_endpoint_returns_structured_results_and_rejects_invalid_subnets() {
let server = start_server();