Skip to content

Control a whole area by name - #5685

Merged
bgoncal merged 4 commits into
mainfrom
siri-area-commands
Sep 7, 2026
Merged

Control a whole area by name#5685
bgoncal merged 4 commits into
mainfrom
siri-area-commands

Conversation

@bgoncal

@bgoncal bgoncal commented Sep 7, 2026

Copy link
Copy Markdown
Member

AI Policy

Select exactly one option that describes AI usage in this contribution:

  • I have not used AI for this contribution.
  • AI assistance was used for this contribution.
  • AI fully generated the code for this contribution, but I've reviewed and understood it before submitting and will respond without AI during review.

Summary

"Turn on the living room lights" had nothing to match. A spoken command could only ever name a single entity, so a room full of them had to be asked for one at a time.

Turn on, turn off, open and close now offer the areas holding something they can act on, alongside their individual entities, and target the area itself. Home Assistant scopes an area target by the calling service's own domain, so turning on a room's lights leaves its television alone.

No new shortcuts: the four commands that already exist gained the rows, which also keeps the app within Apple's cap of ten.

Targets are named for what they hold, down to the cover device class where a room agrees on one, so a room of curtains reads as "Office curtains" rather than "Office covers". Matching takes the area's Home Assistant aliases as well, which is what people set them for.

Based on #5683, which the open and close changes here build on.

Screenshots

The rows appear in Siri's and the Shortcuts app's own pickers, which the app doesn't draw.

Link to pull request in Documentation repository

Documentation: home-assistant/companion.home-assistant#

Any other notes

An area target's id is namespaced (serverId-area:areaId:domain) so it can never collide with an entity's, and keyed on the domain rather than the device class so a room whose curtains become blinds keeps its saved shortcuts working.

An area with a single light still gets a row: "turn on the kitchen lights" is how people speak whether the kitchen holds one lamp or five.

Toggling an area calls the domain's own toggle service, since a room has no single state to read. Area commands get the spoken sentence and no result card, for the same reason.

Home Assistant answers a service call as soon as it has passed it on, not
once the device has reported back, so reading the state straight afterwards
returned the state the action replaced: a light just switched on drew as off.

Read it back until it shows the command landed, then draw whatever the last
read said, so a device that refuses still reports the truth. A cover holds
opening or closing for longer than any card will wait, and that already
proves the command landed, so it counts.

Also brings the card's type down a step: it is drawn at Siri's scale, where
the sizes it used ran large enough to wrap the area line.
"Turn on the living room lights" had nothing to match: a command could
only ever name one entity, so a room full of them had to be asked for one
at a time.

Each command now offers the areas holding something it can act on, next to
its individual entities, and targets the area itself. Home Assistant scopes
an area by the calling service's domain, so turning on a room's lights
leaves its television alone.

The targets are named for what they hold, down to the device class where a
room agrees on one, so a room of curtains reads as curtains rather than as
covers. Matching takes the area's aliases too, which is what people set them
for. No new shortcuts: the commands that already exist gained the rows.
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Unused L10n strings detected

Found 2 unused localization strings in the codebase.

Click to see details
Parsing Strings.swift...
Found 2815 L10n strings

Reading all Swift source code...
Read 11369125 characters of Swift code

Checking for unused strings...
Checked 100/2815 strings...
Checked 200/2815 strings...
Checked 300/2815 strings...
Checked 400/2815 strings...
Checked 500/2815 strings...
Checked 600/2815 strings...
Checked 700/2815 strings...
Checked 800/2815 strings...
Checked 900/2815 strings...
Checked 1000/2815 strings...
Checked 1100/2815 strings...
Checked 1200/2815 strings...
Checked 1300/2815 strings...
Checked 1400/2815 strings...
Checked 1500/2815 strings...
Checked 1600/2815 strings...
Checked 1700/2815 strings...
Checked 1800/2815 strings...
Checked 1900/2815 strings...
Checked 2000/2815 strings...
Checked 2100/2815 strings...
Checked 2200/2815 strings...
Checked 2300/2815 strings...
Checked 2400/2815 strings...
Checked 2500/2815 strings...
Checked 2600/2815 strings...
Checked 2700/2815 strings...
Checked 2800/2815 strings...

================================================================================
UNUSED STRINGS REPORT
================================================================================

Found 2 unused strings:


APPINTENTS:
  - L10n.AppIntents.ActiveEntities.Filter.climates
    Key: app_intents.active_entities.filter.climates
    Line: 297
  - L10n.AppIntents.ActiveEntities.Filter.locks
    Key: app_intents.active_entities.filter.locks
    Line: 305

================================================================================
Total unused: 2
================================================================================

================================================================================
Copy-paste these keys into the "Lokalise: Delete Keys" workflow (keys input):
================================================================================
app_intents.active_entities.filter.climates,app_intents.active_entities.filter.locks

To remove them, run the
Lokalise: Delete Keys
workflow — it deletes the keys from Lokalise and opens a PR removing them from
Localizable.strings and regenerating Strings.swift. Copy-paste these keys into the keys input:

app_intents.active_entities.filter.climates,app_intents.active_entities.filter.locks

Base automatically changed from siri-confirmation-card to main September 7, 2026 17:20
# Conflicts:
#	Sources/Extensions/AppIntents/Control/ControlEntityIntentRunner+Snippet.swift
#	Sources/Extensions/AppIntents/Control/OpenCloseEntityAppIntent.swift
Both suites mapped every row to its entity id, which an area row leaves
empty. They now check the entities and the areas apart, which also pins
that a light and a switch sharing a room contribute no area target to the
open and close command.
Copilot AI lite review requested due to automatic review settings September 7, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

sharedDeviceClass can misclassify mixed/nil device classes (leading to incorrect area naming), which should be corrected before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds support for “whole area” targets (e.g., “Living room lights”) to existing App Intents control commands so Siri/Shortcuts can match and act on an area-by-domain target in addition to individual entities.

Changes:

  • Introduces AreaTarget + AreaTargetProvider and surfaces area-level rows in controllable/openable entity pickers and identifier resolution.
  • Updates control intent execution to target area_id when appropriate and suppress result cards for area actions (no single state to show).
  • Adds localization keys and unit tests covering area target building, naming (including cover device classes), matching, and ID stability.
File summaries
File Description
Tests/Shared/AreaTarget.test.swift New unit tests for area target construction, naming/matching, and ID behavior.
Tests/App/Utilities/OpenableEntityAppEntityQueryTests.swift Updates open/close entity query tests to account for added area target rows.
Tests/App/Utilities/ControllableEntityAppEntityQueryTests.swift Updates on/off entity query tests to account for added area target rows.
Sources/Shared/Resources/Swiftgen/Strings.swift Adds SwiftGen accessors for new app_intents.area_target.* strings.
Sources/Shared/Domain/AreaTargetProvider.swift New provider to build area targets from Siri-exposed entities + areas.
Sources/Shared/Domain/AreaTarget.swift New AreaTarget model plus helpers to build targets and compute shared device class.
Sources/Extensions/AppIntents/Control/OpenCloseEntityAppIntent.swift Allows open/close to target an area and omits snippet for area actions.
Sources/Extensions/AppIntents/Control/OpenableEntityAppEntityQuery.swift Adds area target rows/sections and resolves saved area IDs.
Sources/Extensions/AppIntents/Control/OpenableEntityAppEntity.swift Adds areaTarget support, subtitle behavior, and service targeting via area_id.
Sources/Extensions/AppIntents/Control/ControllableEntityAppEntityQuery.swift Adds area target rows/sections and resolves saved area IDs.
Sources/Extensions/AppIntents/Control/ControllableEntityAppEntity.swift Adds areaTarget support, subtitle behavior, and service targeting via area_id.
Sources/Extensions/AppIntents/Control/ControlEntityIntentRunner+Snippet.swift Suppresses result card for area actions (dialog only).
Sources/Extensions/AppIntents/Control/ControlEntityIntentRunner.swift Uses serviceTarget for calls and adjusts toggle behavior for area targets.
Sources/App/Resources/en.lproj/Localizable.strings Adds English strings for area-target naming and section header.
HomeAssistant.xcodeproj/project.pbxproj Adds the new shared test file to the unit test target build phase.
Review details
  • Files reviewed: 15/15 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +128 to +133
var sharedDeviceClass: DeviceClass? {
let classes = Set(compactMap(\.rawDeviceClass))
guard classes.count == 1, let raw = classes.first else { return nil }
return DeviceClass(rawValue: raw)
}
}
Comment on lines +65 to +67
if entity.areaTarget != nil {
return domain.toggleServices.map { _ in Service.toggle } ?? services.off
}
@bgoncal
bgoncal enabled auto-merge (squash) September 7, 2026 18:35
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.53073% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.35%. Comparing base (e4b354c) to head (d19dc89).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...AppIntents/Control/ControlEntityIntentRunner.swift 20.00% 4 Missing ⚠️
...ts/Control/ControlEntityIntentRunner+Snippet.swift 66.66% 1 Missing ⚠️
...pIntents/Control/ControllableEntityAppEntity.swift 94.73% 1 Missing ⚠️
...s/AppIntents/Control/OpenableEntityAppEntity.swift 95.23% 1 Missing ⚠️
Sources/Shared/Domain/AreaTargetProvider.swift 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5685      +/-   ##
==========================================
+ Coverage   39.06%   39.35%   +0.29%     
==========================================
  Files        1074     1076       +2     
  Lines       75445    75895     +450     
==========================================
+ Hits        29474    29871     +397     
- Misses      45971    46024      +53     
Files with missing lines Coverage Δ
...nts/Control/ControllableEntityAppEntityQuery.swift 100.00% <100.00%> (ø)
.../AppIntents/Control/OpenCloseEntityAppIntent.swift 92.30% <100.00%> (+0.20%) ⬆️
...Intents/Control/OpenableEntityAppEntityQuery.swift 100.00% <100.00%> (ø)
Sources/Shared/Domain/AreaTarget.swift 100.00% <100.00%> (ø)
...ts/Control/ControlEntityIntentRunner+Snippet.swift 91.66% <66.66%> (-8.34%) ⬇️
...pIntents/Control/ControllableEntityAppEntity.swift 97.22% <94.73%> (-2.78%) ⬇️
...s/AppIntents/Control/OpenableEntityAppEntity.swift 97.22% <95.23%> (-2.78%) ⬇️
Sources/Shared/Domain/AreaTargetProvider.swift 92.30% <92.30%> (ø)
...AppIntents/Control/ControlEntityIntentRunner.swift 72.34% <20.00%> (-6.73%) ⬇️

... and 42 files with indirect coverage changes

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bgoncal
bgoncal merged commit 1f9c831 into main Sep 7, 2026
16 checks passed
@bgoncal
bgoncal deleted the siri-area-commands branch September 7, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants