From 3136f1305b47a8db23d4ab02abb810aeb1442e4b Mon Sep 17 00:00:00 2001 From: ruv Date: Mon, 27 Jul 2026 14:09:17 -0400 Subject: [PATCH] feat(homecore-api): negotiate modern websocket clients --- v2/crates/homecore-api/src/rest.rs | 1 + v2/crates/homecore-api/src/ws.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/v2/crates/homecore-api/src/rest.rs b/v2/crates/homecore-api/src/rest.rs index 89b5b190..5f60576e 100644 --- a/v2/crates/homecore-api/src/rest.rs +++ b/v2/crates/homecore-api/src/rest.rs @@ -381,6 +381,7 @@ pub async fn compatibility( "states_services_config": "implemented", "events": "implemented", "render_template": "implemented", + "feature_negotiation_and_panels": "implemented", "registries": "requires_registry_backend", "lovelace_media": "integration_dependent" } diff --git a/v2/crates/homecore-api/src/ws.rs b/v2/crates/homecore-api/src/ws.rs index 49bef4d4..b1c3c2b0 100644 --- a/v2/crates/homecore-api/src/ws.rs +++ b/v2/crates/homecore-api/src/ws.rs @@ -240,6 +240,12 @@ impl Connection { async fn handle_cmd(&self, cmd: WsCommand, tx: &tokio::sync::mpsc::Sender) { match cmd.kind.as_str() { + "supported_features" => { + // HOMECORE currently emits individual messages. Accepting the + // negotiation command keeps modern HA clients compatible while + // deliberately declining optional coalescing. + self.ack(tx, cmd.id, true, None); + } "ping" => { let msg = serde_json::json!({"id": cmd.id, "type": "pong"}); let _ = tx.try_send(msg.to_string()); @@ -258,6 +264,11 @@ impl Connection { }); self.ack(tx, cmd.id, true, Some(payload)); } + "get_panels" => { + // Panels are frontend integration resources. An empty map is + // the valid shape for a headless server. + self.ack(tx, cmd.id, true, Some(serde_json::json!({}))); + } "get_services" => { let services = self.state.homecore().services().registered_services().await; let mut by_domain: std::collections::HashMap<