From 38fc007a6ad0d90bc013cabbea91194d11f3c766 Mon Sep 17 00:00:00 2001 From: Ryan Wagoner Date: Tue, 17 Feb 2026 22:22:22 -0500 Subject: [PATCH] Always include preset/custom_preset in climate state JSON When a preset is cleared, the field was omitted entirely from the JSON. Since the frontend uses Object.assign to merge state updates, the old preset value was never removed. Now we always send the field (empty string when no preset is active) so the UI updates correctly. --- esphome/components/web_server/web_server.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e89578a196..727aeb8314 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1568,11 +1568,16 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con if (!traits.get_supported_custom_fan_modes().empty() && obj->has_custom_fan_mode()) { root[ESPHOME_F("custom_fan_mode")] = obj->get_custom_fan_mode(); } - if (traits.get_supports_presets() && obj->preset.has_value()) { - root[ESPHOME_F("preset")] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); + if (traits.get_supports_presets()) { + root[ESPHOME_F("preset")] = + obj->preset.has_value() ? PSTR_LOCAL(climate_preset_to_string(obj->preset.value())) : ""; } - if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) { - root[ESPHOME_F("custom_preset")] = obj->get_custom_preset(); + if (!traits.get_supported_custom_presets().empty()) { + if (obj->has_custom_preset()) { + root[ESPHOME_F("custom_preset")] = obj->get_custom_preset(); + } else { + root[ESPHOME_F("custom_preset")] = ""; + } } if (traits.get_supports_swing_modes()) { root[ESPHOME_F("swing_mode")] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));