Skip to content

Add iOS Live Activity webhook handlers to mobile_app#166072

Closed
rwarner wants to merge 53 commits into
home-assistant:devfrom
rwarner:feat/ios-live-activity
Closed

Add iOS Live Activity webhook handlers to mobile_app#166072
rwarner wants to merge 53 commits into
home-assistant:devfrom
rwarner:feat/ios-live-activity

Conversation

@rwarner
Copy link
Copy Markdown

@rwarner rwarner commented Mar 20, 2026

Proposed change

Adds server-side support for iOS Live Activities in the `mobile_app` integration. This is the HA core companion to the iOS companion app PRs and the relay server PR.

Live Activities let Home Assistant automations push real-time state to the iOS Lock Screen and Dynamic Island. The iOS app handles the ActivityKit lifecycle; this PR adds the webhook handlers and notification routing that HA core needs.

How it works: When a notification contains `live_update: true` and a `tag`, the notify service looks up the stored APNs Live Activity token for that tag and includes it alongside the normal FCM registration token in the relay request. The relay places it in the FCM message's `apns.liveActivityToken` field — no separate APNs endpoint or credentials needed. If no per-activity token exists, it falls back to the device's push-to-start token (iOS 17.2+) to start a new activity remotely.

What this adds:

  • `live_activity_token` webhook — stores per-activity APNs push tokens sent by the iOS app when an activity is created via ActivityKit
  • `live_activity_dismissed` webhook — removes the stored token when the activity ends on device
  • `SCHEMA_APP_DATA` validation for the optional `live_activity_push_to_start_token` field in device registration
  • Notification routing in `notify.py` — extracts and forwards the Live Activity APNs token when `live_update: true` is present
  • In-memory `DATA_LIVE_ACTIVITY_TOKENS` store, initialized in `init.py` and cleaned up on config entry unload

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Part of epic: home-assistant/epics#61
Fixes: home-assistant/iOS#4623

@home-assistant
Copy link
Copy Markdown
Contributor

Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration (mobile_app) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of mobile_app can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign mobile_app Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component, problem in config, problem in device, feature-request) on the pull request.

Copy link
Copy Markdown
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

Adds Home Assistant Core support in the mobile_app integration for iOS Live Activities by introducing webhook handlers that store and clear per-activity APNs push tokens and emit lifecycle events for automations.

Changes:

  • Extend SCHEMA_APP_DATA and constants to support Live Activities capability flags and push-to-start registration fields.
  • Add update_live_activity_token and live_activity_dismissed webhooks that manage an in-memory token store and fire remote-origin bus events.
  • Add a supports_live_activities() helper and webhook tests covering token storage, defaults, and cleanup.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
homeassistant/components/mobile_app/const.py Adds Live Activity-related constants/events and extends registration SCHEMA_APP_DATA.
homeassistant/components/mobile_app/__init__.py Initializes a new in-memory DATA_LIVE_ACTIVITY_TOKENS store under hass.data[DOMAIN].
homeassistant/components/mobile_app/webhook.py Implements the new Live Activity webhook handlers and fires lifecycle events.
homeassistant/components/mobile_app/util.py Adds supports_live_activities() helper based on stored app_data.
tests/components/mobile_app/test_webhook.py Adds tests for storing tokens, default env behavior, and dismiss cleanup/event firing.
Comments suppressed due to low confidence (1)

tests/components/mobile_app/test_webhook.py:1398

  • This test only asserts the HTTP status. To fully validate the contract of live_activity_dismissed (which returns empty_okay_response()), also assert the JSON body is {} (and optionally that no tokens were removed when none existed) to prevent regressions in response shape/side effects.
    resp = await webhook_client.post(
        f"/api/webhook/{webhook_id}",
        json={
            "type": "live_activity_dismissed",
            "data": {
                "tag": "nonexistent_activity",
            },
        },
    )

    assert resp.status == HTTPStatus.OK

Comment thread homeassistant/components/mobile_app/const.py Outdated
Comment thread homeassistant/components/mobile_app/__init__.py Outdated
Copy link
Copy Markdown
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

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
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

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread homeassistant/components/mobile_app/notify.py
Comment thread homeassistant/components/mobile_app/webhook.py Outdated
Comment thread homeassistant/components/mobile_app/notify.py Outdated
Copilot AI review requested due to automatic review settings March 23, 2026 13:03
Copy link
Copy Markdown
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

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/mobile_app/webhook.py Outdated
Comment thread homeassistant/components/mobile_app/webhook.py Outdated
Copilot AI review requested due to automatic review settings March 23, 2026 18:29
Copy link
Copy Markdown
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

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread homeassistant/components/mobile_app/notify.py Outdated
Copilot AI review requested due to automatic review settings March 23, 2026 19:55
Copy link
Copy Markdown
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

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread homeassistant/components/mobile_app/const.py Outdated
Comment thread homeassistant/components/mobile_app/notify.py Outdated
Comment thread homeassistant/components/mobile_app/webhook.py
Copilot AI review requested due to automatic review settings March 24, 2026 14:09
Comment thread homeassistant/components/mobile_app/const.py

ATTR_LIVE_UPDATE = "live_update"
ATTR_LIVE_ACTIVITY_TOKEN = "live_activity_token"
ATTR_LIVE_ACTIVITY_PUSH_TO_START_TOKEN = "live_activity_push_to_start_token"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ATTR_LIVE_ACTIVITY_PUSH_TO_START_TOKEN = "live_activity_push_to_start_token"
ATTR_LIVE_ACTIVITY_START_TOKEN = "live_activity_start_token"

Do we really need a so long name? Can we maybe reuse ATTR_LIVE_ACTIVITY_TOKEN?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand live_activity_push_to_start_token is how we call it in iOS codebase but we can simplify and have it as live_activity_token leaving just 1 variable in core codebase.

I will make the change in the iOS codebase

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@home-assistant home-assistant Bot marked this pull request as draft May 26, 2026 23:01
Comment thread homeassistant/components/mobile_app/notify.py Outdated
Comment thread homeassistant/components/mobile_app/notify.py Outdated
Comment thread homeassistant/components/mobile_app/notify.py Outdated
@edenhaus edenhaus requested a review from bgoncal May 27, 2026 09:44
edenhaus and others added 2 commits May 27, 2026 12:34
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Bruno Pantaleão Gonçalves <5808343+bgoncal@users.noreply.github.com>
Copy link
Copy Markdown
Member

@edenhaus edenhaus left a comment

Choose a reason for hiding this comment

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

Thanks @rwarner 👍

Waiting for the approval of @bgoncal as Bruno will do a full test again

@edenhaus edenhaus added this to the 2026.6.0b0 milestone May 27, 2026
bgoncal

This comment was marked as outdated.

bgoncal pushed a commit to home-assistant/iOS that referenced this pull request May 27, 2026
## Summary

- Removes `supports_live_activities` and
`supports_live_activities_frequent_updates` from the app registration
payload sent to HA

These fields were removed from the HA core side in
home-assistant/core#166072 (the `SCHEMA_APP_DATA` schema no longer
accepts them). Sending data that the server doesn't read wastes
bandwidth on every registration.

## Related

- home-assistant/core#166072

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Part of epic: home-assistant/epics#61
Fixes: #4623

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
bgoncal pushed a commit to home-assistant/iOS that referenced this pull request May 27, 2026
## Summary

- Renames `webhookTypeToken` from `"mobile_app_live_activity_token"` →
`"live_activity_token"`
- Renames `webhookTypeDismissed` from
`"mobile_app_live_activity_dismissed"` → `"live_activity_dismissed"`

Requested in home-assistant/core#166072 — all other webhooks in the
mobile_app integration use short names without the `mobile_app_` prefix
(e.g. `scan_tag`, `update_location`). This aligns our new webhook types
with that convention.

## Related

- home-assistant/core#166072

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Part of epic: home-assistant/epics#61
Fixes: #4623

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@edenhaus edenhaus removed this from the 2026.6.0b0 milestone May 27, 2026
…tification

Users send the documented flat fields (`progress`, `notification_icon`,
`notification_icon_color`, `chronometer`, `critical_text`, `when` +
`when_relative`) at `data.*`. The notify service now lifts them into the
`content_state` block the iOS ActivityKit decoder expects, renaming
`notification_icon` → `icon` and `notification_icon_color` → `color`, and
computing `countdown_end` from `when` + `when_relative`. `data.event` is
added so the relay sets `attributes-type` only on `start`.

A `clear_notification` whose tag matches a stored Live Activity token
attaches the token and `event=end` so iOS dismisses the activity remotely
instead of falling through to a regular notification banner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# When a notification with this message arrives with a tag matching a stored
# Live Activity token, end the activity remotely instead of letting it fall
# through to a regular clear_notification banner.
LIVE_ACTIVITY_CLEAR_MESSAGE = "clear_notification"
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm open to a better name if you feel it's needed. It's iOS-specific. Android dismisses live notifications by tag natively, but iOS ActivityKit needs an explicit event=end push to the activity's token, so this string is what triggers core to attach that token. Happy to rename if LIVE_ACTIVITY_ overspecifies.

Copy link
Copy Markdown
Member

@edenhaus edenhaus May 27, 2026

Choose a reason for hiding this comment

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

Command is not exclusive for live activity and therefore should not prefixed with it.
Also the comment has nothing to do with the constant per se and is at the wrong place

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Makes sense, renamed to CLEAR_NOTIFICATION and moved to correct ordering. Removed comment as well.


_LOGGER = logging.getLogger(__name__)

type LiveActivityEvent = Literal["start", "update", "end"]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should be an enum

Copy link
Copy Markdown
Author

@rwarner rwarner May 28, 2026

Choose a reason for hiding this comment

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

Sounds good changed to a StrEnum let me know if that isn't right am. Also changed associating areas that reference this.

}


def _translate_live_activity_payload(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we move this one to the ios app or does it really need to be in core?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed I'm not sure it makes sense to have it there, if you need the data to be in a specific shape can't we add the user to use this specific shape directly?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Live activities are something in iOS very sensitive and need to receive the exact payload to start/update it, so this abstraction/translation needs to happen at core level (not necessarily in mobile_app but not in iOS app)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's very sensitive on Android too, but we ask the user at the moment to provide the right field at the right spot in the data field. Having validation for some fields here would be a first (I think) and I'm not sure we want that at the moment in the integration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The problem is that android requirements and iOS requirements are not the same, so thats why we need to translate to iOS from core, otherwise we will need to ask the user to use different data tags when initiating live activity for iOS and Android (which we are trying to avoid)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why should we do the conversion in core, IMO we should keep it within the iOS codebase. Is there anything that forces us to keep it in core?

DATA_CONFIG_ENTRIES = "config_entries"
DATA_DELETED_IDS = "deleted_ids"
DATA_DEVICES = "devices"
DATA_LIVE_ACTIVITY_TOKENS = "live_activity_tokens"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we make this specific to iOS in the name to avoid any confusion? Android don't uses this, same for the other new ATTRs.

Copy link
Copy Markdown
Author

@rwarner rwarner May 28, 2026

Choose a reason for hiding this comment

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

Yes good idea, I will hold off on this until there's an architectural decision with the whole platform specific code going into this. If this is going into a mobile_app/ios/ or something then the prefix might be redundant? But I can keep this open for now and can update them if that still sounds like a good direction

# When a notification with this message arrives with a tag matching a stored
# Live Activity token, end the activity remotely instead of letting it fall
# through to a regular clear_notification banner.
LIVE_ACTIVITY_CLEAR_MESSAGE = "clear_notification"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@tr4nt0r just pinging you in case you are interested to look at this PR that does change a bit the notifications.

@@ -0,0 +1,64 @@
"""Live Activity push token lifecycle: expiry-driven cleanup loop."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add a mention that it is only for iOS.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@edenhaus Is it a "valid" pattern to actually move specific code for iOS into a dedicated python module like mobile_app/ios we don't know yet how we want the integration to evolve but things that are specific to a platform could be isolated a bit to help with whatever change we might do in the future.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Will hold on this until we make that architectural decision

tokens = hass.data[DOMAIN][DATA_LIVE_ACTIVITY_TOKENS]
earliest_expires_at = min(
(
token["expires_at"]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wouldit make sense to extract "expires_at" into a const? It is used in multiple places.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good idea, introduced ATTR_EXPIRES_AT and implemented

async_call_later(hass, delay, run_cleanup)


async def async_cleanup_expired_tokens(hass: HomeAssistant) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this be

Suggested change
async def async_cleanup_expired_tokens(hass: HomeAssistant) -> None:
async def _async_cleanup_expired_tokens(hass: HomeAssistant) -> None:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not a big python guy so I'm not sure what the proper format should be. It looks like because both functions are called from __init__.py and webhook.py the module is public by design which constitutes not having the _

I would defer to @edenhaus I might have my understanding mixed up

"token": data[ATTR_PUSH_TOKEN],
"expires_at": dt_util.utcnow().timestamp() + LIVE_ACTIVITY_TOKEN_TTL_SECONDS,
}
hass.data[DOMAIN][DATA_STORE].async_delay_save(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add a small comment about why you need a delay here?

Copy link
Copy Markdown
Author

@rwarner rwarner May 28, 2026

Choose a reason for hiding this comment

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

Sounds good, added

# Debounce disk writes: ActivityKit can hand a fresh per-tag token to the                                                       
# iOS app multiple times in quick succession (e.g. when several activities                                                      
# start back-to-back), and we don't need to fsync after each one.

STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
STORAGE_VERSION_MINOR = 2
STORAGE_SAVE_DELAY = 10
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
STORAGE_SAVE_DELAY = 10
STORAGE_SAVE_DELAY_SECONDS = 10

I suppose?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call updated and fixed references

vol.Required(ATTR_TAG): cv.string,
}
)
async def webhook_live_activity_dismissed(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
async def webhook_live_activity_dismissed(
async def webhook_ios_live_activity_dismissed(

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Holding for architecture.

# Clean up the device key if no activities remain.
if not live_activity_tokens[webhook_id]:
del live_activity_tokens[webhook_id]
hass.data[DOMAIN][DATA_STORE].async_delay_save(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Small comment about the delay.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Debounce comment added

activity_tag = data[ATTR_TAG]

live_activity_tokens = hass.data[DOMAIN][DATA_LIVE_ACTIVITY_TOKENS]
if webhook_id in live_activity_tokens:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to log when we are in the else branches of this function? To let know the user he wrongly configured his notification.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added a DEBUG log. The else path is usually the cleanup loop having already removed the token, but the log def helps with diagnostics. (Side note: this webhook is auto-fired by iOS on dismissal, not user config)

- Drop the LIVE_ACTIVITY_ prefix on CLEAR_NOTIFICATION; it's a generic
  notification command, not LA-exclusive. The use-site comment in
  notify.py already explains the LA bridging behavior.
- Extract ATTR_TOKEN and ATTR_EXPIRES_AT for the stored token dict keys.
- Rename STORAGE_SAVE_DELAY → STORAGE_SAVE_DELAY_SECONDS to make the
  unit explicit.
- Convert LiveActivityEvent from a Literal type alias to StrEnum.
- Expand async_cleanup_expired_tokens docstring to explain the
  self-rescheduling chain.
- Add debounce-rationale comments above both async_delay_save call sites.
- DEBUG-log the live_activity_dismissed else branch for diagnostics.
- Cover the self-rescheduling cleanup chain with a new test where two
  tokens with staggered expiries exercise the partial-sweep + reschedule
  path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rwarner
Copy link
Copy Markdown
Author

rwarner commented May 28, 2026

Yesterday afternoon 05/27/26

  • Implemented core changes to get iOS fixed and working end to end again: 135cd18
  • Did a full claude end-to-end audit of docs, core, iOS, FCM since all these changes occurred over the past two months

Updated the Epic for testing information and relevant PRs:

I successfully tested with a Simulator via Local Push with: @bgoncal's fixes, my fixes from iOS#4677, and a few core fixes. I also tested with a real device with manually pushed APNs with a python script which all worked, which is great. I am limited from access on FCM so @bgoncal will need to take the reigns on that when the time comes.


This morning 05/28/26

  • Walked through all the comments from @edenhaus and @TimoPtr
  • Implemented fixes for all suggested areas from comments. Held off on specific comments relevant to the architectural decision on where to take this for iOS/Android if we do in fact need specific platform code in this repository
  • Implemented additional test to fulfill coverage
  • Left review threads open for reviewer to resolve if satisfied

@bgoncal attached an MD file showcasing the yaml I verified working here from yesterday
2026-05-28-verified-smoke-tests.md

Re: Architectural Decision, sounds like @edenhaus @bgoncal and @TimoPtr might discuss next week about what direction we should take this

bgoncal pushed a commit to home-assistant/iOS that referenced this pull request Jun 1, 2026
## Summary

Follow-up fixes to two issues surfaced while testing #4671 end-to-end.

1. **`HALiveActivityAttributes.ContentState.countdownEnd` decoded via
Unix epoch.** ActivityKit decodes the `content-state` JSON arriving via
APNs with the default `JSONDecoder`, whose `Date` strategy is
`.deferredToDate` (seconds since the 2001 reference date). HA core sends
`countdown_end` as Unix epoch seconds, matching the documented
`data.when` / `data.when_relative` user contract and the in-app handler
that already does `Date(timeIntervalSince1970:)`. Without a manual
decode the APNs push path renders countdowns ~31 years in the future.
Adds explicit `init(from:)` and `encode(to:)` that map `countdownEnd`
via `timeIntervalSince1970`. All other fields use
`container.decodeIfPresent` so behavior is unchanged for them.

2. **`NotificationManagerLocalPushInterfaceDirect` assigns
`LocalPushManager.delegate`.** The Extension path assigns the delegate
at line 197; the Direct path (used on simulator and Mac Catalyst) never
did. On those platforms that meant silent commands such as
`clear_notification` (no alert title/body) were dropped: iOS doesn't
fire `willPresent` for content-less notifications, and the delegate is
the only fallback that routes into `commandManager`. One-line fix that
brings the Direct factory into line with the Extension's behavior.

Real-device verification (iPhone 13 Mini, iOS 26.5, paid Developer
account): chronometer countdown rendered correctly 60→0 with the Codable
fix in place. Without it the timer rendered as if `Date` were
seconds-since-2001.

## Screenshots

n/a — both fixes affect decode / message routing behavior, not UI
rendering.

## Link to pull request in Documentation repository

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

## Any other notes

Part of the Live Activities effort tracked in home-assistant/epics#61.
Companion server PR: home-assistant/core#166072.
@bgoncal
Copy link
Copy Markdown
Member

bgoncal commented Jun 1, 2026

@rwarner @edenhaus In terms of functionality, I have tested locally using FCM and it works as expected ✅

Other PR comments to be discussed

@bgoncal
Copy link
Copy Markdown
Member

bgoncal commented Jun 3, 2026

Taking over PR: #172928

@bgoncal bgoncal closed this Jun 3, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live updates - main feature

5 participants