This document provides guidance for AI coding agents (LLMs) working on the Home Assistant for Apple Platforms codebase.
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 26.2+, CocoaPods, Swift Package Manager
- Workspace: Always open
HomeAssistant.xcworkspace(not the.xcodeproj)
bundle install
bundle exec pod install --repo-updateCocoaPods and Swift Package Manager (SPM) manage third-party dependencies. CocoaPods is used for most dependencies, while SPM is used for select packages (e.g., swift-snapshot-testing, WebRTC, ZIPFoundation, firebase-ios-sdk).
Create Configuration/HomeAssistant.overrides.xcconfig (git-ignored):
DEVELOPMENT_TEAM = YourTeamID
BUNDLE_ID_PREFIX = some.bundle.prefix
Sources/
├── App/ # Main iOS app target
├── Shared/ # Shared code across all platforms
├── Watch/ # watchOS-specific code
├── WatchApp/ # watchOS app target
├── MacBridge/ # macOS Catalyst bridge
├── CarPlay/ # CarPlay integration
├── Extensions/ # App Extensions (widgets, notifications, intents)
├── Improv/ # Improv BLE provisioning
├── PushServer/ # Push notification server communication
├── SharedPush/ # Shared push notification handling
├── SharedTesting/ # Shared testing utilities
├── Thread/ # Thread network support
├── Launcher/ # App launcher helper
Tests/
├── App/ # App-level tests
├── Shared/ # Shared module tests
├── UI/ # UI tests
├── Widgets/ # Widget tests
├── Mocks/ # Mock objects for testing
Configuration/ # Xcode build configuration files
fastlane/ # Fastlane automation (build, test, deploy)
Tools/ # Build tools, icon generation
This project uses the "World" pattern for dependency injection, inspired by Point-Free's "How to Control the World". This is the most important architectural concept in the codebase.
A single global Current variable of type AppEnvironment holds all dependencies as mutable properties:
// Sources/Shared/Environment/Environment.swift
public var Current: AppEnvironment { ... }
public class AppEnvironment {
public var date: () -> Date = Date.init
public var calendar: () -> Calendar = { Calendar.autoupdatingCurrent }
public var servers: ServerManager = ServerManagerImpl()
public var clientEventStore: ClientEventStoreProtocol = ClientEventStore()
// ... many more dependencies
}Access dependencies through Current:
let now = Current.date()
let server = Current.servers.all.first
Current.Log.info("Something happened")Override dependencies for testing:
Current.date = { Date(timeIntervalSince1970: 1000000) }
Current.servers = FakeServerManager()Never assign to Current.* properties outside of test code. This is enforced by a custom SwiftLint rule that will fail CI. In production code, only read from Current.
- Add strings to the English
.stringsfile:Sources/App/Resources/en.lproj/Localizable.strings - SwiftGen auto-generates type-safe accessors in
Sources/Shared/Resources/SwiftGen/Strings.swiftwhen building the app - Use generated accessors via the
L10nenum:
// In Localizable.strings:
"settings.title" = "Settings";
"sensor.name_%@" = "Sensor: %@";
// In Swift code (auto-generated):
let title = L10n.Settings.title
let name = L10n.Sensor.name("Temperature")There are multiple string tables:
Localizable.strings→L10nenumCore.strings→CoreStringsenumFrontend.strings→FrontendStringsenum
All string lookup flows through Current.localized.string which handles locale fallback.
Important: Translations for other languages are managed externally via Lokalise. Only add/modify strings in the
en.lprojfiles.
# Check for lint issues (does not modify files)
bundle exec fastlane lint
# Auto-fix lint issues (run before committing!)
bundle exec fastlane autocorrectAlways run bundle exec fastlane autocorrect after making changes and before committing.
| Tool | Config File | Purpose |
|---|---|---|
| SwiftFormat | .swiftformat |
Code formatting (120 char max, before-first wrapping) |
| SwiftLint | .swiftlint.yml |
Code quality rules |
| Rubocop | .rubocop.yml |
Ruby/Fastlane code |
| YamlLint | .yamllint.yml |
YAML files |
- Max line width: 120 characters
- Wrap arguments/parameters/collections:
before-first selfkeyword: only in initializers (--self init-only)- Guard else: same line
- Headers: stripped (no file header comments)
- No
force_castorforce_try - Keep cyclomatic complexity low
- No assigning to
Current.*outside tests - Use
SFSafeSymbolsfor SF Symbol references:
// ❌ Wrong
Image(systemName: "house")
// ✅ Correct
Image(systemSymbol: .house)bundle exec fastlane testOr in Xcode: use the Tests-Unit scheme with ⌘U.
- Tests live in
Tests/mirroring the source structure - Mock dependencies by overriding
Current.*properties in test setup - Use
Sources/SharedTesting/for shared test utilities - Tests are excluded from SwiftLint enforcement
CI runs on GitHub Actions (.github/workflows/ci.yml):
- Linting: SwiftFormat, SwiftLint, Rubocop, YamlLint
- Unit Tests: Runs the
Tests-Unitscheme - Build Verification: Ensures the app builds cleanly
All lint checks and tests must pass before a PR can be merged.
Use HAKit (the Home Assistant Swift SDK) for server communication:
- REST API calls via
HAConnection - WebSocket subscriptions for real-time updates
- Connection info managed through
Current.servers
- GRDB: Primary database for structured data (servers, configurations)
- Realm: Legacy data storage (being migrated)
- UserDefaults: Simple preferences and watch communication
- SwiftUI: Preferred for new UI (settings, widgets)
- UIKit: Used in older code and where needed for platform APIs
- Use
View.embeddedInHostingController()for SwiftUI-to-UIKit bridging - View models are annotated with
@MainActor - Support both light and dark mode
- SF Symbols via
SFSafeSymbolslibrary - Material Design Icons available via
MaterialDesignIcons(auto-generated from JSON) - Asset catalogs in
Sources/Shared/Assets/SharedAssets.xcassets
- Install dependencies:
bundle install && bundle exec pod install --repo-update - Make your changes in the appropriate
Sources/directory - Add strings to
en.lproj/Localizable.stringsif needed (SwiftGen generates accessors on build) - Run autocorrect:
bundle exec fastlane autocorrect - Run tests:
bundle exec fastlane test - Commit your changes