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
@@ -6,13 +6,21 @@ such rather than half-reimplemented.
## Navigation
The sidebar lists fifteen **categories**, not one row per page. A category
covering several subjects — Input, Network & Sharing, Applications, Users &
Accounts, Privacy & Security, System — draws a tab strip above the page, and
each of its **leaf** pages is one tab; a category with a single subject is a
leaf itself and shows no strip. The column used to be a flat list of thirty-one
pages, which made finding Printers a scan of the whole thing. Grouped, it sits
under Network & Sharing, where somebody looking for it already expects it.
The sidebar lists fourteen **categories**, not one row per page. A category
covering several subjects — Home, Input, Network & Sharing, Applications,
Users & Accounts, Privacy & Security, System — draws a tab strip above the
page, and each of its **leaf** pages is one tab; a category with a single
subject is a leaf itself and shows no strip. The column used to be a flat list
of thirty-one pages, which made finding Printers a scan of the whole thing.
Grouped, it sits under Network & Sharing, where somebody looking for it already
expects it.
**Home** carries three tabs: **Overview** (`home`, the quick-action tiles,
findings, glance cards, Today and Weather that the shell opens by default),
**My Home** (`my-home`, Home Assistant lights grouped by room, accessories, and
the connection card) and **Phone** (`phone`, vitals, ring, clipboard, send file,
and BlueBubbles Messages). It absorbed the former standalone Home & Phone
category, whose `home-phone` id is now retired.
Leaf pages are ordinary `SettingsPage` files and know nothing about this.
`SettingsShell` draws the strip and hosts the page inside it, so moving a page
@@ -29,6 +37,15 @@ always used, so every existing deep link, IPC call, and search hit keeps
working and now lands on the exact tab. A category id is accepted too, and
resolves to that category's first available tab.
**A retired id never stops resolving.** When a page is split, merged, or
renamed, its old id goes into `SettingsRoutes.retired` — a map from the dead id
to the leaf that absorbed its content — rather than being deleted. Old Vicinae
commands, shell history, notification handoffs, and muscle memory all hold
those ids, and `resolve()` checks the map first, so `home-phone` still lands on
**My Home** instead of falling back to Home. Retired ids are aliases only: they
are not leaves, so nothing generates a command, a tab, or a doc heading for
them.
Availability gating belongs to the strip rather than the sidebar:
`SettingsRoutes.pageAvailable()` drops the Containers tab on a machine without
podman and Snapshots without a snapper configuration, but only once a scan has