Skip to content

Conversation

@timbms
Copy link
Contributor

@timbms timbms commented Nov 19, 2025

No description provided.

Signed-off-by: Tim Mueller-Seydlitz <timbms@gmail.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a fallback mechanism for Shortcuts support when a previously configured home has been deleted, preventing shortcuts from failing. The solution falls back to the first available home when the originally configured home is not found.

Key Changes:

  • Added fallback logic across all intent handlers to use the first available home when the configured home is deleted
  • Updated Preferences.listStoredHomes() to be async to properly handle actor isolation
  • Added comprehensive logging throughout intent handling and item caching for better debugging
  • Enhanced OpenHABItemCache with validation checks to ensure homes exist before attempting operations
  • Removed unnecessary @MainActor annotation from OpenHABIntentHelper

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 35 comments.

Show a summary per file
File Description
openHABIntents/SetSwitchStateIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/SetStringValueIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/SetNumberValueIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/SetDimmerRollerValueIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/SetContactStateValueIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/SetColorValueIntentHandler.swift Implements home fallback mechanism and replaces homeId with actualHomeId throughout the handler
openHABIntents/OpenHABIntentHelper.swift Removes @mainactor annotation, adds fallback logic to resolveHome and getAllItems, makes getHomeOptions async, adds extensive logging
openHABIntents/IntentHandler.swift Adds logging for initialization diagnostics
openHABIntents/GetItemStateIntentHandler.swift Implements home fallback mechanism, adds logging, replaces homeId with actualHomeId
openHAB/HomeSelectionView.swift Updates loadHomesList to properly await async listStoredHomes call
openHAB.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Updates dependency versions and changes file format from version 3 to version 2
OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift Makes listStoredHomes async to properly handle actor isolation
OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift Adds home existence validation before operations, enhances error handling, improves logging for diagnostics

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

actualHomeId = homeId
} else {
Logger.intentHandling.warning("Home \(homeId) not found in handle. Falling back to first available home")
guard let firstHome = storedHomes.first else {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .first on a dictionary provides non-deterministic ordering, which means the fallback home selection could vary unpredictably between runs. This could lead to inconsistent behavior when the intended home is deleted. Consider sorting the homes (e.g., by name or creation date) before selecting the first one to ensure consistent fallback behavior:

guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {
Suggested change
guard let firstHome = storedHomes.first else {
guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {

Copilot uses AI. Check for mistakes.
actualHomeId = homeId
} else {
Logger.intentHandling.warning("Home \(homeId) not found in handle. Falling back to first available home")
guard let firstHome = storedHomes.first else {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .first on a dictionary provides non-deterministic ordering, which means the fallback home selection could vary unpredictably between runs. This could lead to inconsistent behavior when the intended home is deleted. Consider sorting the homes (e.g., by name or creation date) before selecting the first one to ensure consistent fallback behavior:

guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {
Suggested change
guard let firstHome = storedHomes.first else {
guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {

Copilot uses AI. Check for mistakes.
actualHomeId = homeId
} else {
Logger.intentHandling.warning("Home \(homeId) not found in handle. Falling back to first available home")
guard let firstHome = storedHomes.first else {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .first on a dictionary provides non-deterministic ordering, which means the fallback home selection could vary unpredictably between runs. This could lead to inconsistent behavior when the intended home is deleted. Consider sorting the homes (e.g., by name or creation date) before selecting the first one to ensure consistent fallback behavior:

guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {
Suggested change
guard let firstHome = storedHomes.first else {
guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {

Copilot uses AI. Check for mistakes.
actualHomeId = homeId
} else {
Logger.intentHandling.warning("Home \(homeId) not found in handle. Falling back to first available home")
guard let firstHome = storedHomes.first else {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .first on a dictionary provides non-deterministic ordering, which means the fallback home selection could vary unpredictably between runs. This could lead to inconsistent behavior when the intended home is deleted. Consider sorting the homes (e.g., by name or creation date) before selecting the first one to ensure consistent fallback behavior:

guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {
Suggested change
guard let firstHome = storedHomes.first else {
guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first else {

Copilot uses AI. Check for mistakes.
actualHomeId = homeId
} else {
Logger.intentHandling.warning("Home \(homeId) not found in storedHomes. Available homes: \(storedHomes.keys)")
guard let firstHome = storedHomes.keys.first else {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .keys.first on a dictionary provides non-deterministic ordering, which means the fallback home selection could vary unpredictably between runs. This could lead to inconsistent behavior when the intended home is deleted. Consider sorting the homes (e.g., by name) before selecting the first one to ensure consistent fallback behavior:

guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first?.key else {
Suggested change
guard let firstHome = storedHomes.keys.first else {
// Select fallback home deterministically by sorting by homeName
guard let firstHome = storedHomes.sorted(by: { $0.value.homeName < $1.value.homeName }).first?.key else {

Copilot uses AI. Check for mistakes.
Signed-off-by: Tim Mueller-Seydlitz <timbms@gmail.com>
Comment on lines +102 to +104
for homeId in homes {
items[homeId] = loadedItems[homeId] ?? []
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked the one liner more, I think it should work with the fallback, too. But feel free to write it according to your taste.

private func loadItems(homeId: UUID) async -> [OpenHABItem]? {
guard let networkTracker = await assureNetworkTracker(homeId: homeId) else {
Logger.itemCache.error("Home \(homeId) not reachable")
Logger.itemCache.error("Home \(homeId) not reachable - not found in storedHomes")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether that new part in the log line is accurate, there may be other reasons like timeouts which cause the network tracker to be nil. Maybe you can replace the "-" with ", potentially because" or just log the failure in the assureNetworkTracker method if you don´t do that already anyway.

Comment on lines +177 to +182
do {
let items = try await networkTracker.getStaticItems()
Logger.itemCache.info("Successfully loaded \(items.count) items for home \(homeId)")
return items
} catch {
Logger.itemCache.error("Failed to load items for home \(homeId): \(error.localizedDescription)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the verbose do-catch syntax we can do a guard let items = try?... here with log and return in the else branch?

Comment on lines +23 to +24
let homes = await Preferences.shared.listStoredHomes()
Logger.intentHandling.info("IntentHandler init: Found \(homes.count) stored homes: \(homes)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these two lines necessary in this method? I would not call it just for logging.

@TAKeanice
Copy link
Contributor

I am not fully convinced that falling back to a random (deterministically chosen) home when a home is deleted is the right approach. If the item names in homes are distinct, a user of shortcuts can just leave the home selection empty, which would have the desired effect. If the item name is not distinct, the choice for a certain home was most likely made with determination, and just using the other home with the same item name might have undesired effects. I would rather like to warn the user of the problem that a home was not found and make him figure out the solution (change or remove the home from his shortcuts, or remove the obsolete shortcut).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants