Add custom notification icons for automations#4672
Conversation
There was a problem hiding this comment.
Hi @Roei-Bracha
It seems you haven't yet signed a CLA. Please do so here.
Once you do that we will be able to review and accept this pull request.
Thanks!
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds end-to-end support for custom notification “sender avatars” on iOS by parsing existing Android payload fields (notification_icon, color, icon_url) and decorating notifications into iOS Communication Notification style using INSendMessageIntent, with caching + tests and accompanying design/plan docs.
Changes:
- Introduces
NotificationSenderInfo+NotificationSenderParserto extract sender/icon info fromUNNotificationContent.userInfo. - Adds
NotificationCommunicationDecoratorto apply Communication Notification styling (MDI render or downloaded URL icon) and a disk-backedNotificationIconCache. - Wires the decorator into the Notification Service Extension pipeline and adds unit tests + a sample
.apnspayload.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/superpowers/specs/2026-05-24-custom-notification-icons-design.md | New design spec describing payload contract, architecture, caching, and compliance considerations |
| docs/superpowers/plans/2026-05-24-custom-notification-icons.md | New task-by-task implementation plan and test strategy |
| Sources/Shared/Notifications/NotificationSender/NotificationSenderInfo.swift | Defines parsed sender/icon value type used by parser/decorator |
| Sources/Shared/Notifications/NotificationSender/NotificationSenderParser.swift | Parses notification_icon / icon_url / colors into NotificationSenderInfo |
| Sources/Shared/Notifications/NotificationSender/NotificationIconCache.swift | Adds disk cache with App Group + cross-process coordination |
| Sources/Shared/Notifications/NotificationSender/NotificationCommunicationDecorator.swift | Builds/donates INSendMessageIntent and applies content.updating(from:) with avatar image generation |
| Sources/Shared/Environment/Environment.swift | Registers decorator in DI container (Current) |
| Sources/Extensions/NotificationService/NotificationService.swift | Adds decorator step after existing attachment pipeline |
| Sources/Extensions/NotificationContent/Resources/TestNotifications/communication_notification.apns | Adds reproducible test payload for manual simulator/device verification |
| Tests/Shared/NotificationSender/NotificationSenderInfoTests.swift | Unit tests for value-type equality |
| Tests/Shared/NotificationSender/NotificationSenderParserTests.swift | Unit tests for field precedence, defaults, and malformed inputs |
| Tests/Shared/NotificationSender/NotificationIconCacheTests.swift | Unit tests for cache behavior + eviction + key stability |
| Tests/Shared/NotificationSender/NotificationCommunicationDecoratorTests.swift | Unit tests for intent construction, conversation grouping, URL download/cache behavior |
| HomeAssistant.xcodeproj/project.pbxproj | Adds new files to targets/groups (and incidental project-file churn) |
| Gemfile | Adds abbrev gem dependency |
| Gemfile.lock | Locks abbrev and bumps Bundler version |
7eb3bac to
7ccd851
Compare
There was a problem hiding this comment.
Hi @Roei-Bracha
It seems you haven't yet signed a CLA. Please do so here.
Once you do that we will be able to review and accept this pull request.
Thanks!
| public struct NotificationSenderInfo: Equatable { | ||
| public enum Source: Equatable { | ||
| /// User-supplied image URL. `needsAuth` is true when the URL string begins with `/` | ||
| /// (matching the rule in `NotificationAttachmentParserURL`). | ||
| case iconURL(URL, needsAuth: Bool) | ||
|
|
||
| /// Built-in Material Design Icon, rendered onto a colored square. | ||
| /// `background` defaults to `AppConstants.tintColor` when `color` is absent. | ||
| /// `foreground` defaults to `.white` when `notification_icon_color` is absent. | ||
| case mdi( | ||
| name: String, | ||
| background: UIColor, | ||
| foreground: UIColor, | ||
| colorString: String?, | ||
| iconColorString: String? | ||
| ) |
There was a problem hiding this comment.
Fixed. We have implemented a manual == operator for NotificationSenderInfo.Source that compares MDI icon names, string values (colorString, iconColorString) directly if present, and safely compares UIColors via isEqual as a fallback. This guarantees clean and stable synthesized Equatable behavior.
| case let .iconURL(url, needsAuth): | ||
| let cacheKey = notificationIconCacheKey(for: url) | ||
| if let cached = cache.data(forKey: cacheKey) { | ||
| return .value(INImage(imageData: cached)) | ||
| } |
There was a problem hiding this comment.
Fixed. We modified notificationIconCacheKey(for:serverID:) to accept an optional serverID: String? = nil parameter. When resolving relative/authenticated icon URLs, we extract the active server's unique identifier from the HomeAssistantAPI instance and include it in the hashed string, preventing namespace collisions and leaks across multiple servers in the shared App Group container.
|
For the linter you can run |
|
Please mark as "Ready for review" after you finish |
- Support 'notification_icon', 'notification_icon_color', and 'color' keys in the push payload. - Promote custom notification icon fields in local push / websocket parser. - Register 'INSendMessageIntent' in NotificationService Extension Info.plist to support communication notification decoration. - Ensure notification intent donation completes before finishing decoration. - Add unit tests for local push event, notification sender parser, and legacy parser.
…e URL cache key collision
This PR adds support for custom notification icons (iMessage-style communication notifications) when a push payload contains
notification_iconoricon_url. Matches the Android companion app's payload schema.Testing status
This currently works with local push. I verified it in the iOS simulator by sending a Home Assistant notification with:
After enabling local push for the simulator server, the notification displayed the custom cellphone icon instead of the app icon.
Nabu Casa / remote push rollout
For Nabu Casa cloud push and other non-local notifications to work, the push relay/server must deploy the matching
SharedPushparser change from this PR. The server-side notification normalization currently needs to:icon_url,notification_icon,notification_icon_color, andcolorfrom the Home Assistant notificationdataobject into the top-level APNs custom payloadaps["mutable-content"] = truewhen eithericon_urlornotification_iconis present, so iOS launches the notification service extensionExpected APNs payload shape for
notification_icon: mdi:cellphone:{ "aps": { "alert": { "title": "Phone", "body": "test" }, "sound": "default", "mutable-content": true }, "notification_icon": "mdi:cellphone", "notification_icon_color": "#FFFFFF", "color": "#03A9F4" }Until that server-side relay code is deployed, cloud pushes can still arrive without
notification_iconand withoutmutable-content, so they will keep showing the normal app icon.