Merge Home & Phone into a three-tab Home that knows your house

Home is now Overview | My Home | Phone. Overview leads with quick-action
tiles (focus, Do Not Disturb, health, snapshots, storage), keeps the
findings card — updates fold in, the reclaim-space prompt is gone on
purpose — and adds glance cards, the next calendar event, and weather.

My Home groups every light by Home Assistant area: the helper gained an
`areas` command (one REST template render, no websocket), and the rooms
degrade to a flat list on setups without areas. The favorites editor and
connection card moved intact. Phone gains a vitals strip — battery and
cell signal read from KDE Connect's plugin D-Bus objects, where absence
is data, not an error — beside ring, clipboard, send-a-file, and the
BlueBubbles handoff.

The retired home-phone id resolves to my-home forever via a new alias
map in SettingsRoutes (with a hasOwnProperty guard so prototype names
cannot leak into settingsPage). Storage no longer claims 0 B free — the
old page read a field the disks helper never emitted.

Contracts updated alongside; per the new workflow, the full suite runs
once at the end of the redesign (see the test backlog note).

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-23 22:06:18 -04:00
parent 5490fd285d
commit 7578348db1
40 changed files with 2578 additions and 703 deletions
+11 -2
View File
@@ -28,10 +28,19 @@ if [[ "$(jq -r '.devices | length' <<<"$status")" == "0" ]]; then
exit 0
fi
# battery and signal are always present and are null far more often than not:
# the plugin objects exist only while a device is paired, reachable and has the
# plugin loaded. Null is the answer, so the check is on the shape when a value
# is there, never on a value being there.
jq -e '
([.devices[] | (keys | sort) == (["actions", "id", "name", "paired", "reachable", "type"] | sort)] | all) and
([.devices[] | (keys | sort) == (["actions", "battery", "id", "name", "paired", "reachable", "signal", "type"] | sort)] | all) and
([.devices[] | (.id | test("^[A-Fa-f0-9]{32,64}$"))] | all) and
([.devices[].actions[] | . == "clipboard" or . == "ping" or . == "ring" or . == "share"] | all)
([.devices[].actions[] | . == "clipboard" or . == "ping" or . == "ring" or . == "share"] | all) and
([.devices[] | select(.battery != null) | (.battery | keys | sort) == ["charge", "charging"]] | all) and
([.devices[] | select(.battery != null) | .battery.charge >= 0 and .battery.charge <= 100 and (.battery.charging | type == "boolean")] | all) and
([.devices[] | select(.signal != null) | (.signal | keys | sort) == ["networkType", "strength"]] | all) and
([.devices[] | select(.signal != null) | .signal.strength >= 0 and (.signal.networkType | type == "string")] | all) and
([.devices[] | select(.paired and .reachable | not) | .battery == null and .signal == null] | all)
' <<<"$status" >/dev/null || fail 'live status shape is invalid'
paired_count="$(jq '[.devices[] | select(.paired)] | length' <<<"$status")"