329 files

This commit is contained in:
Home Assistant Version Control
2026-08-06 13:56:25 +00:00
parent 0df89406fa
commit 7afe7add1d
330 changed files with 13098 additions and 5942 deletions
@@ -3,6 +3,7 @@
from __future__ import annotations
import logging
import os
from typing import Any
from homeassistant.components import websocket_api
@@ -40,10 +41,32 @@ def _get_merged_tasks(entry: ConfigEntry) -> dict[str, Any]:
rd = getattr(entry, "runtime_data", None)
store = getattr(rd, "store", None) if rd else None
if store is not None:
return store.merge_all_tasks(tasks_data)
merged = store.merge_all_tasks(tasks_data)
# #73: overlay the in-cycle checklist ticks HERE rather than via the
# merge whitelist — merged dicts feed MaintenanceTask.from_dict all
# over the coordinator, and this field is presentation state the model
# never needs.
for tid, td in merged.items():
progress = store.get_task_state(tid).get("checklist_progress")
if progress:
td["checklist_progress"] = progress
return merged
return tasks_data
# How many recent history entries ride in the LIST payload. Chosen to cover
# every non-detail consumer: the quick-actions dialog shows the last 20, the
# detail header the last 3, the calendar card averages costs (≈stable over
# 20), the strategy looks up the latest matching entry. The detail view's
# full timeline/charts fetch everything via `task/history` instead.
#
# The env override exists ONLY for the benchmark harness: setting
# MS_HISTORY_WINDOW to a huge value reproduces the pre-diet payload on the
# same build, so before/after is a measured A/B on identical data instead of
# an extrapolation. Never documented, never read anywhere else.
_HISTORY_WINDOW = int(os.environ.get("MS_HISTORY_WINDOW", "20"))
def _build_task_summary(
hass: HomeAssistant,
task_id: str,
@@ -177,8 +200,20 @@ def _build_task_summary(
"trigger_entity_info": trigger_entity_info,
"trigger_entity_infos": trigger_entity_infos,
"checklist": task_data.get("checklist", []),
# #73: in-cycle ticks ({item text: bool}, store-merged) — lets the
# detail view show progress without completing, and the complete
# dialog prefill what was already done.
"checklist_progress": task_data.get("checklist_progress", {}),
"labels": task_data.get("labels", []),
"history": task_data.get("history", []),
# Payload diet (perf, 2026-08): the LIST response carries only the
# most recent entries — at 150+ tasks the full histories dominated
# the `objects` payload (407 KB measured at just 8 entries/task) and
# every list consumer reads at most the last 20 (quick-dialog) or an
# aggregate. The task-detail view fetches the FULL history lazily
# via `task/history`; `history_count` tells it (and any badge)
# what exists beyond the window.
"history": (task_data.get("history") or [])[-_HISTORY_WINDOW:],
"history_count": len(task_data.get("history") or []),
# v1.3.0 — both fields are persisted by ws_create_task / ws_update_task
# and consumed by helpers/action_listener.py on EVENT_TASK_COMPLETED, but
# were missing from this response builder until issue #50. Without them
@@ -312,6 +347,20 @@ def _build_object_response(hass: HomeAssistant, entry: ConfigEntry, coordinator_
# (roadmap P2) count of attached documents (files + web-links) for
# the objects-table paperclip badge; computed, not persisted.
"document_count": document_count,
# An UPLOADED manual (category tag "manual") is the same affordance
# as documentation_url — expose them so the "manual" column and the
# object header can fall back instead of rendering "—" next to an
# object that plainly has its manual attached.
"manual_docs": [
{
"id": d.get("id"),
"title": d.get("title") or d.get("filename") or "",
"kind": d.get("kind"),
"url": d.get("url"),
}
for d in object_docs
if "manual" in (d.get("tags") or [])
],
},
"tasks": tasks,
"parts": parts_payload,
@@ -428,10 +477,12 @@ def async_register_commands(hass: HomeAssistant) -> None:
ws_set_environmental_entity,
)
from .battery_fleet import (
ws_battery_fleet_history,
ws_battery_fleet_mark_replaced,
ws_battery_fleet_overview,
ws_battery_fleet_set_excluded,
ws_battery_fleet_setup,
ws_battery_fleet_status,
)
from .dashboard import (
ws_get_budget_status,
@@ -505,6 +556,7 @@ def async_register_commands(hass: HomeAssistant) -> None:
from .tags import ws_list_tags
from .tasks import (
ws_archive_task,
ws_checklist_progress,
ws_complete_task,
ws_create_task,
ws_delete_task,
@@ -515,6 +567,7 @@ def async_register_commands(hass: HomeAssistant) -> None:
ws_reset_task,
ws_skip_task,
ws_snooze_task,
ws_task_history,
ws_unarchive_task,
ws_update_history_entry,
ws_update_task,
@@ -548,8 +601,10 @@ def async_register_commands(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, ws_archive_task)
websocket_api.async_register_command(hass, ws_unarchive_task)
websocket_api.async_register_command(hass, ws_list_tasks)
websocket_api.async_register_command(hass, ws_task_history)
websocket_api.async_register_command(hass, ws_complete_task)
websocket_api.async_register_command(hass, ws_quick_complete_task)
websocket_api.async_register_command(hass, ws_checklist_progress)
websocket_api.async_register_command(hass, ws_skip_task)
websocket_api.async_register_command(hass, ws_reset_task)
websocket_api.async_register_command(hass, ws_snooze_task)
@@ -571,6 +626,8 @@ def async_register_commands(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, ws_discover_problem_sensors)
websocket_api.async_register_command(hass, ws_adopt_problem_sensors)
websocket_api.async_register_command(hass, ws_battery_fleet_overview)
websocket_api.async_register_command(hass, ws_battery_fleet_history)
websocket_api.async_register_command(hass, ws_battery_fleet_status)
websocket_api.async_register_command(hass, ws_battery_fleet_setup)
websocket_api.async_register_command(hass, ws_battery_fleet_mark_replaced)
websocket_api.async_register_command(hass, ws_battery_fleet_set_excluded)