Fix Home and Phone settings states

This commit is contained in:
Gabriel Brown
2026-08-17 18:02:09 -04:00
parent 65966200fe
commit c8eb344a0b
6 changed files with 144 additions and 15 deletions
@@ -4,25 +4,38 @@ import qs.services
Item { Item {
id: root id: root
objectName: "home-phone-page"
property string lightQuery: searchInput.text.trim().toLowerCase() property string lightQuery: searchInput.text.trim().toLowerCase()
readonly property var availableLights: HomeAssistant.catalog.filter(entity => { readonly property var availableLights: HomeAssistant.catalog.filter(entity => {
if (HomePreferences.isSelected(entity.id)) if (HomeAssistant.selectedEntities.some(selected => selected.id === entity.id))
return false; return false;
const haystack = (entity.sourceName + " " + entity.id).toLowerCase(); const haystack = (entity.sourceName + " " + entity.id).toLowerCase();
return root.lightQuery === "" || haystack.includes(root.lightQuery); return root.lightQuery === "" || haystack.includes(root.lightQuery);
}) })
readonly property string availableEmptyText: root.availableLights.length > 0
? ""
: (root.lightQuery !== ""
? "No lights match that search"
: (HomeAssistant.catalog.length === 0
? "No lights discovered"
: "All discovered lights are already selected"))
readonly property var pageDiagnostics: ({
availableLightIds: root.availableLights.map(entity => entity.id),
availableEmptyText: root.availableEmptyText,
homeStatus: root.homeStatus()
})
function homeStatus(): string { function homeStatus(): string {
if (HomeAssistant.phase === "ready")
return `Connected · ${HomeAssistant.discoveredCount} lights discovered`;
if (HomeAssistant.phase === "degraded")
return "Last update unavailable · showing saved controls";
if (HomeAssistant.lastError === "authentication-required") if (HomeAssistant.lastError === "authentication-required")
return "Authentication required"; return "Authentication required";
if (HomeAssistant.lastError === "not-configured") if (HomeAssistant.lastError === "not-configured")
return "Home Assistant is not configured"; return "Home Assistant is not configured";
if (HomeAssistant.phase === "ready")
return `Connected · ${HomeAssistant.discoveredCount} lights discovered`;
if (HomeAssistant.phase === "degraded")
return "Last update unavailable · showing saved controls";
if (HomeAssistant.phase === "loading") if (HomeAssistant.phase === "loading")
return "Connecting to Home Assistant"; return "Connecting to Home Assistant";
return "Home Assistant is unavailable"; return "Home Assistant is unavailable";
@@ -250,9 +263,7 @@ Item {
Text { Text {
width: parent.width width: parent.width
visible: root.availableLights.length === 0 visible: root.availableLights.length === 0
text: root.lightQuery === "" && HomeAssistant.catalog.length > 0 text: root.availableEmptyText
? "All discovered lights are already selected"
: "No lights match that search"
color: Theme.fgDim color: Theme.fgDim
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
@@ -6,6 +6,11 @@ Rectangle {
id: root id: root
property var hostWindow: null property var hostWindow: null
readonly property var homePhoneDiagnostics: pageLoader.status === Loader.Ready
&& pageLoader.item
&& pageLoader.item.objectName === "home-phone-page"
? pageLoader.item.pageDiagnostics
: ({})
color: Theme.bg color: Theme.bg
radius: 18 radius: 18
@@ -6,6 +6,8 @@ import qs.services
FloatingWindow { FloatingWindow {
id: root id: root
readonly property var homePhoneDiagnostics: settingsShell.homePhoneDiagnostics
title: "Panama Settings" title: "Panama Settings"
visible: ShellState.settingsOpen visible: ShellState.settingsOpen
implicitWidth: 1120 implicitWidth: 1120
@@ -23,6 +25,7 @@ FloatingWindow {
} }
SettingsShell { SettingsShell {
id: settingsShell
anchors.fill: parent anchors.fill: parent
hostWindow: root hostWindow: root
} }
@@ -434,7 +434,8 @@ Singleton {
} }
function applyFixture(name: string): void { function applyFixture(name: string): void {
if (["ready", "stale", "unavailable", "missing-selected", "action-error", if (["ready", "available-extra", "stale", "stale-authentication", "stale-not-configured",
"unavailable", "missing-selected", "action-error",
"process-actions", "process-no-output", "process-delayed-exit", "process-actions", "process-no-output", "process-delayed-exit",
"process-slow-actions"].indexOf(name) < 0) "process-slow-actions"].indexOf(name) < 0)
return; return;
@@ -488,15 +489,31 @@ Singleton {
root.catalog = root.fixtureEntities(); root.catalog = root.fixtureEntities();
root.fixtureFavorites = root.fixturePreferenceRecords(); root.fixtureFavorites = root.fixturePreferenceRecords();
if (name === "available-extra") {
root.catalog = root.catalog.concat([{
id: "light.fixture_guest",
sourceName: "Guest lamp",
state: "off",
available: true,
active: false,
dimmable: true,
brightnessPct: 0
}]);
}
if (name === "missing-selected") { if (name === "missing-selected") {
root.fixtureFavorites = root.fixtureFavorites.concat([ root.fixtureFavorites = root.fixtureFavorites.concat([
{ id: "light.fixture_missing", alias: "Porch" } { id: "light.fixture_missing", alias: "Porch" }
]); ]);
} }
root.rebuildSelection(); root.rebuildSelection();
root.phase = name === "stale" ? "degraded" : "ready"; const isStale = ["stale", "stale-authentication", "stale-not-configured"].indexOf(name) >= 0;
root.stale = name === "stale"; root.phase = isStale ? "degraded" : "ready";
root.lastError = name === "stale" ? "unreachable" : ""; root.stale = isStale;
root.lastError = name === "stale-authentication"
? "authentication-required"
: (name === "stale-not-configured"
? "not-configured"
: (name === "stale" ? "unreachable" : ""));
if (name === "action-error") { if (name === "action-error") {
root.entityErrors = { root.entityErrors = {
"light.fixture_kitchen": "request-failed" "light.fixture_kitchen": "request-failed"
+3 -2
View File
@@ -85,7 +85,7 @@ ShellRoot {
IntelligenceResult {} IntelligenceResult {}
ActivityPanel {} ActivityPanel {}
PowerMenu {} PowerMenu {}
SettingsWindow {} SettingsWindow { id: settingsWindow }
// Toasts are their own always-on layer; they must be able to appear // Toasts are their own always-on layer; they must be able to appear
// without any overlay being open. // without any overlay being open.
@@ -295,7 +295,8 @@ ShellRoot {
open: ShellState.settingsOpen, open: ShellState.settingsOpen,
page: ShellState.settingsPage, page: ShellState.settingsPage,
discoveredCount: HomeAssistant.discoveredCount, discoveredCount: HomeAssistant.discoveredCount,
selectedCount: HomeAssistant.configuredCount selectedCount: HomeAssistant.configuredCount,
homePhone: settingsWindow.homePhoneDiagnostics
}); });
} }
} }
@@ -93,6 +93,7 @@ assert_contains 'HomePreferences.add' "$home_page"
assert_contains 'HomePreferences.retrySave' "$home_page" assert_contains 'HomePreferences.retrySave' "$home_page"
assert_contains 'Choose lights below to build your Control Center shelf.' "$home_page" assert_contains 'Choose lights below to build your Control Center shelf.' "$home_page"
assert_contains 'All discovered lights are already selected' "$home_page" assert_contains 'All discovered lights are already selected' "$home_page"
assert_contains 'No lights discovered' "$home_page"
assert_contains 'No lights match that search' "$home_page" assert_contains 'No lights match that search' "$home_page"
assert_contains 'Opens BlueBubbles' "$home_page" assert_contains 'Opens BlueBubbles' "$home_page"
[[ "$(rg -Fc 'required property var modelData' "$home_page")" -ge 2 ]] \ [[ "$(rg -Fc 'required property var modelData' "$home_page")" -ge 2 ]] \
@@ -173,9 +174,100 @@ for _ in $(seq 1 40); do
fi fi
sleep 0.1 sleep 0.1
done done
jq -e '.open == true and .page == "home-phone" and .discoveredCount == 7 and .selectedCount == 7' \ jq -e '
.open == true and
.page == "home-phone" and
.discoveredCount == 7 and
.selectedCount == 7 and
.homePhone.availableLightIds == [] and
.homePhone.availableEmptyText == "All discovered lights are already selected"
' \
<<<"$status" >/dev/null || fail "Home & Phone diagnostics are incomplete: $status" <<<"$status" >/dev/null || fail "Home & Phone diagnostics are incomplete: $status"
qs_for_test ipc call home-assistant fixture available-extra >/dev/null
available_status='{}'
for _ in $(seq 1 40); do
available_status="$(qs_for_test ipc call settings status | jq -c .)"
if jq -e '
.discoveredCount == 8 and
.selectedCount == 7 and
.homePhone.availableLightIds == ["light.fixture_guest"]
' <<<"$available_status" >/dev/null; then
break
fi
sleep 0.1
done
jq -e '
.discoveredCount == 8 and
.selectedCount == 7 and
.homePhone.availableLightIds == ["light.fixture_guest"]
' <<<"$available_status" >/dev/null \
|| fail "an unselected fixture light was not the only available row: $available_status"
qs_for_test ipc call home-assistant fixture stale-authentication >/dev/null
authentication_status='{}'
for _ in $(seq 1 40); do
authentication_status="$(qs_for_test ipc call settings status | jq -c .)"
if jq -e '
.discoveredCount == 7 and
.selectedCount == 7 and
.homePhone.homeStatus == "Authentication required"
' <<<"$authentication_status" >/dev/null; then
break
fi
sleep 0.1
done
jq -e '
.discoveredCount == 7 and
.selectedCount == 7 and
.homePhone.homeStatus == "Authentication required"
' <<<"$authentication_status" >/dev/null \
|| fail "stale authentication did not retain actionable copy: $authentication_status"
qs_for_test ipc call home-assistant fixture stale-not-configured >/dev/null
not_configured_status='{}'
for _ in $(seq 1 40); do
not_configured_status="$(qs_for_test ipc call settings status | jq -c .)"
if jq -e '
.discoveredCount == 7 and
.selectedCount == 7 and
.homePhone.homeStatus == "Home Assistant is not configured"
' <<<"$not_configured_status" >/dev/null; then
break
fi
sleep 0.1
done
jq -e '
.discoveredCount == 7 and
.selectedCount == 7 and
.homePhone.homeStatus == "Home Assistant is not configured"
' <<<"$not_configured_status" >/dev/null \
|| fail "stale not-configured state did not retain actionable copy: $not_configured_status"
qs_for_test ipc call home-assistant fixture unavailable >/dev/null
empty_status='{}'
for _ in $(seq 1 40); do
empty_status="$(qs_for_test ipc call settings status | jq -c .)"
if jq -e '
.discoveredCount == 0 and
.selectedCount == 0 and
.homePhone.homeStatus == "Home Assistant is not configured" and
.homePhone.availableLightIds == [] and
.homePhone.availableEmptyText == "No lights discovered"
' <<<"$empty_status" >/dev/null; then
break
fi
sleep 0.1
done
jq -e '
.discoveredCount == 0 and
.selectedCount == 0 and
.homePhone.homeStatus == "Home Assistant is not configured" and
.homePhone.availableLightIds == [] and
.homePhone.availableEmptyText == "No lights discovered"
' <<<"$empty_status" >/dev/null \
|| fail "an empty catalog did not render its distinct copy: $empty_status"
system_status="$(qs_for_test ipc call settings-system status | jq -c .)" system_status="$(qs_for_test ipc call settings-system status | jq -c .)"
jq -e '.bluebubblesAvailable == true' <<<"$system_status" >/dev/null \ jq -e '.bluebubblesAvailable == true' <<<"$system_status" >/dev/null \
|| fail "BlueBubbles availability was not exposed: $system_status" || fail "BlueBubbles availability was not exposed: $system_status"