This is the router for AI coding agents (LLMs) working on the Home Assistant for Apple Platforms codebase. Detailed guidance lives in modular skills under .agents/skills/. Load the skill that matches your task instead of reading everything up front.
Home Assistant for Apple Platforms is a native Swift companion app for Home Assistant home automation. The primary user interaction is through a WKWebView displaying the Home Assistant web frontend, with native features for notifications, sensors, location tracking, widgets, CarPlay, Apple Watch, and more.
- Language: Swift 5.8+
- Platforms: iOS, watchOS, macOS (Catalyst), CarPlay
- Build System: Xcode 27.0+, Swift Package Manager
- Project: Open
HomeAssistant.xcodeprojdirectly (dependencies are managed via Swift Package Manager)
These apply to every change, even if you don't load the matching skill:
- One type per file: each
struct/class/enumlives in its own file named after the type — never stack multiple top-level types (especially SwiftUIViewstructs) in one file. Smallprivatehelper types nested inside the type are the only exception. Every SwiftUI view ships with a#Preview. Seeha-ios-uifor the full UI rules. - No navigation title under an Apple-like header: a screen whose
Liststarts withAppleLikeListTopRowHeaderalready shows its title in that header, so it must not also set.navigationTitle. Two titles stack as the user scrolls. - SwiftUI confirmations stay on the trigger: apply
.confirmationDialogdirectly to theButtonthat opens it, not to a parentList,Section, or container view. This keeps confirmation ownership local and avoids dialogs firing from unrelated controls. - TestFlight gating is
Current.isTestFlight, and always paired with an ungating PR:Current.isTestFlightis the only supported way to limit a feature to beta builds — never add a bespoke flag, build setting, or#iffor it. Every change that puts a feature behind that gate must be accompanied by a parallel draft PR that removes the gate, so shipping the feature to everyone is a one-click merge. Seeha-ios-workflow-cifor the full procedure. - User-facing changes ship with a documentation draft PR: any change that adds a feature, changes how one behaves, or removes something the docs describe is accompanied by a draft PR in home-assistant/companion.home-assistant, linked from the app PR's
Documentation:line. Beta-only features are marked<span class='beta'>BETA</span>there. Bug fixes that restore documented behavior and internal-only changes need no docs PR. Seeha-ios-workflow-cifor when it is needed and how to prepare it. - Every sensor is opt-in, always listed, and asks for its own permission: nothing reports until the user switches it on, so never add a sensor to a default-on list and never make installing an update start reporting something new (
SensorRegistry.legacyEraSensorIDsis frozen at the release that introduced the allowlist, which is what keeps a new sensor out of the upgrade path; a new ID belongs inWebhookSensorIdalone). ASensorProvidermust still list its sensor when it cannot read one, reporting the state asunavailablerather than erroring out, because an errored provider drops out of the sensors list and takes the row that switches it on with it. Where a sensor needs an iOS permission, map its ID inSensorPermission.required(forSensorUniqueID:)so switching it on is what prompts for that permission. Only a device that can never produce the sensor is an error. Classify every new sensor inSensorEntityCategory.category(forSensorUniqueID:)— its switch is exhaustive, so adding aWebhookSensorIdcase without deciding won't compile — and never setentityCategorywhere the sensor is built. Enablement is per server —Current.sensors.isEnabled(uniqueID:for:)for what a given server receives,isEnabledForAnyServer(uniqueID:)for device-level work that happens once however many servers the values reach — and a server the user adds starts with nothing enabled, so never seed a new server from another one's selection.
| Skill | Load it when you are… |
|---|---|
ha-ios-architecture |
Starting in the repo, deciding where code lives across targets, using the global Current (World pattern), or setting up dependencies and code signing |
ha-ios-webview |
Working on WebViewController, the JavaScript external message bus, custom URL schemes, or deep links into the frontend |
ha-ios-magicitem |
Adding or changing MagicItem types or actions shared by Widgets, Watch, CarPlay, and App Shortcuts |
ha-ios-localization |
Adding or changing user-facing strings, using L10n, or dealing with Lokalise translations |
ha-ios-code-style |
Writing or formatting Swift, running SwiftFormat/SwiftLint, referencing SF Symbols or Material Design icons, logging, or using with() |
ha-ios-concurrency |
Writing async/await, actors, or Combine, deciding whether to touch PromiseKit, or calling the server via HAKit |
ha-ios-persistence |
Adding or migrating a persistent model, or choosing between GRDB, Realm, and UserDefaults |
ha-ios-push-live-activities |
Implementing or fixing push notifications or Live Activities across the local-push and remote-push flows |
ha-ios-ui |
Building UI, choosing SwiftUI vs UIKit, or following the one-struct-per-file, inline-body, and #Preview rules |
ha-ios-testing |
Writing unit or snapshot tests, or mocking dependencies by overriding Current |
ha-ios-workflow-ci |
Preparing a change for commit or a PR, gating a feature behind TestFlight, opening the matching documentation draft PR, or understanding the CI gates that must pass before merge |
ha-ios-skill-maintenance |
Adding, editing, or reorganizing these skills, or updating this router |
This project follows the Open Home Foundation AI Policy. Autonomous contributions are not accepted: a human must review, understand, and be able to explain every change before it is submitted. Do not open issues or pull requests autonomously, and do not post comments on behalf of a user without their review.