Skip to content

Upgrade watch complications to WidgetKit#5027

Merged
bgoncal merged 11 commits into
mainfrom
watch-regression
Jul 10, 2026
Merged

Upgrade watch complications to WidgetKit#5027
bgoncal merged 11 commits into
mainfrom
watch-regression

Conversation

@bgoncal

@bgoncal bgoncal commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Screenshots

Link to pull request in Documentation repository

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

Any other notes

bgoncal and others added 7 commits July 9, 2026 15:17
- Resize Logo asset to 120px so WidgetKit can archive the complication view
  (1024px hit the imageTooLarge limit and left the complication blank)
- Add the Assist custom symbol (message-processing-outline) to the widget catalog
- Render the Assist symbol for the Assist complication and the template logo
  for the Home Assistant / placeholder complication
- Use bundled asset images for the AppIntents configuration picker tiles

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Assist complication's `homeassistant://assist` widget URL was never received:
the watch app ran on the legacy WatchKit lifecycle (@main on ExtensionDelegate +
WKHostingController), where SwiftUI's .onOpenURL / .onContinueUserActivity are inert
(runtime warned "Cannot use Scene methods for URL … without SwiftUI Lifecycle").

- Adopt the SwiftUI App lifecycle: @main HomeAssistantWatchApp with
  WKApplicationDelegateAdaptor(ExtensionDelegate.self) and WindowGroup { WatchHomeView() }
- WatchHomeView now handles the assist deep link via .onOpenURL / .onContinueUserActivity,
  plus a cold-launch pending flag consumed on appear
- Replace the Assist fullScreenCover fatalError with a "configure Assist" message

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… limit

The ExtensionDelegate writer rasterized complication icons at 96pt (=192px @2x)
and shipped the placeholder's RoundLogo at 1024px into the app-group payload.
WidgetKit caps complication images at ~122px (measured in pixels), so archiving
failed and every user complication — including gauge/ring ones — rendered empty.

- Render user-complication icons at 56pt (=112px @2x), under the limit
- Drop the placeholder/Assist raster icons from the payload (iconData = nil); the
  widget extension already renders its bundled, correctly-sized Logo and Assist symbol

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 20:53

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.

Pull request overview

This PR migrates the Apple Watch complications from the legacy ClockKit-based complication extension to a watchOS 10+ WidgetKit-based implementation, while also expanding the watch app’s offline/sync/editing capabilities (config editing on-watch, chunked database mirroring, improved Assist launch handling, and additional troubleshooting actions).

Changes:

  • Adds a new watchOS WidgetKit extension (“WatchWidgets”) with AppIntent-based configuration and multiple accessory widget families.
  • Updates the watch app to better support complication/widget deep links (notably Assist), adds on-watch configuration/edit flows, and improves troubleshooting (local wipe, client event log UI).
  • Improves watch ↔ iPhone sync robustness via guaranteed-message fallback for config pulls and chunked streaming for the watch database mirror.

Reviewed changes

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

Show a summary per file
File Description
WatchWidgets/WatchWidgetsEntryView.swift SwiftUI rendering for WidgetKit entries across supported families.
WatchWidgets/WatchWidgets.swift Widget bundle + AppIntent configuration for the watch widgets extension.
WatchWidgets/WatchWidgetLogoView.swift Shared logo view helper for widget rendering.
WatchWidgets/WatchWidgetEntry.swift Timeline entry model for the watch widget.
WatchWidgets/WatchWidgetConstants.swift Constants for kind/app group, deep links, layout sizes, etc.
WatchWidgets/WatchWidgetComplicationSnapshotStore.swift Reads stored complication “snapshot” recommendations for widgets.
WatchWidgets/WatchWidgetComplicationSnapshot.swift Codable snapshot model + tint/icon helpers for widget rendering.
WatchWidgets/WatchWidgetComplicationEntity.swift AppIntents entity/query types for widget configuration picker.
WatchWidgets/WatchWidgetAppIntent.swift WidgetConfigurationIntent + AppIntentTimelineProvider implementation.
WatchWidgets/WatchComplicationFamily.swift Mapping from WidgetKit families → preferred legacy complication families.
WatchWidgets/Info.plist Declares WidgetKit extension point identifier.
WatchWidgets/Color+Hex.swift Local Color hex parsing + HA brand color for widget extension.
WatchWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json Widget background color asset metadata.
WatchWidgets/Assets.xcassets/TemplateLogo.imageset/Contents.json Template-rendered logo asset metadata for widgets.
WatchWidgets/Assets.xcassets/message-processing-outline.symbolset/haassist.symbols.svg Assist symbol asset (SVG) for widget rendering.
WatchWidgets/Assets.xcassets/message-processing-outline.symbolset/Contents.json Symbol set catalog metadata.
WatchWidgets/Assets.xcassets/Logo.imageset/Contents.json Logo asset metadata for watch widgets.
WatchWidgets/Assets.xcassets/Contents.json Asset catalog root metadata for watch widgets.
WatchWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json Watch widget extension icon filename update.
WatchWidgets/Assets.xcassets/AccentColor.colorset/Contents.json Accent color asset metadata.
Sources/WatchApp/WatchSettingsViewModel.swift Adds “delete local data” capability (DB wipe + cache cleanup).
Sources/WatchApp/WatchSettingsView.swift Adds UI for delete-local-data + client events view entry point.
Sources/WatchApp/WatchServerSync.swift Shared server + certificate sync routine and URL override re-application.
Sources/WatchApp/WatchServerDetailView.swift New server detail UI (URL override picker + mTLS identity status).
Sources/WatchApp/WatchNotificationNames.swift Adds watch-local Notification.Name helpers for sync/refresh triggers.
Sources/WatchApp/WatchConfigAssistViewModel.swift Removes magic-item JSON caching and relies on GRDB-backed info.
Sources/WatchApp/WatchConfigAssistView.swift Adds on-watch Assist configuration screen.
Sources/WatchApp/WatchCommunicatorService.swift New immediate communicator observer service for Assist events.
Sources/WatchApp/Utilities/WKInterfaceLabel+Additions.swift Utility for hiding labels when empty.
Sources/WatchApp/Utilities/WKInterfaceDevice+Size.swift Adds watch size detection helper.
Sources/WatchApp/Notifications/NotificationSubControllerMJPEG.swift Adds MJPEG camera streaming notification renderer.
Sources/WatchApp/Notifications/NotificationSubControllerMedia.swift Adds image/video attachment notification renderer.
Sources/WatchApp/Notifications/NotificationSubControllerMap.swift Adds map-based notification renderer + region helper.
Sources/WatchApp/Notifications/NotificationSubController.swift Defines common protocol + element container for notification renderers.
Sources/WatchApp/Notifications/DynamicNotificationController.swift New dynamic notification controller + loading indicator.
Sources/WatchApp/Info.plist Removes ClockKit complication keys; adds URL schemes for deep links.
Sources/WatchApp/HostingController.swift Switches to SwiftUI App lifecycle; retains legacy HostingController stub.
Sources/WatchApp/Home/WatchHomeViewModel+Editing.swift Adds on-watch editing/mutation/persistence utilities for watch config.
Sources/WatchApp/Home/WatchHomeView.swift Adds Assist widget URL handling + sync error banner; improves cold-launch behavior.
Sources/WatchApp/Home/WatchHomeHeaderView.swift Improves loading UI with progress/status text.
Sources/WatchApp/Home/WatchHomeFooterView.swift Adds footer row with version/SSID + edit/settings buttons.
Sources/WatchApp/Home/WatchFolderContentView.swift Adds folder drill-in UI with edit/reorder controls.
Sources/WatchApp/Home/MagicItemRow/WatchMagicViewRowViewModel.swift New view model for executing MagicItems with feedback + error surfacing.
Sources/WatchApp/Home/MagicItemRow/WatchMagicViewRow.swift New SwiftUI row rendering + haptics + error alerts for MagicItems.
Sources/WatchApp/Home/MagicItemRow/WatchHomeItemLabel.swift Shared row label layout + icon container styling helpers.
Sources/WatchApp/Home/MagicItemRow/WatchFolderRow.swift Folder row rendering matching MagicItem row styling.
Sources/WatchApp/Home/Edit/WatchIconPicker.swift On-watch MaterialDesignIcons picker (search + grid).
Sources/WatchApp/Home/Edit/WatchConfigItemRow.swift Shared config row content + reorder controls + row styling helpers.
Sources/WatchApp/Home/Edit/WatchConfigItemEditView.swift On-watch name/icon/color/confirmation editor with live preview.
Sources/WatchApp/Home/Edit/WatchConfigAddView.swift On-watch add flow (entity/folder) with search and domain filtering.
Sources/WatchApp/Home/Edit/WatchColorPicker.swift On-watch color palette picker replacement for ColorPicker.
Sources/WatchApp/Home/CircularGlassOrLegacyBackground.swift Centralizes watchOS 26 glass-effect vs legacy styling for rows/buttons.
Sources/WatchApp/ExtensionDelegate.swift Removes ClockKit launch handling; adds widget snapshot export + Assist deep-link handling.
Sources/WatchApp/Complication/Assist/AssistDefaultComplication.swift New Assist default complication constants (now used by widget/deep links).
Sources/WatchApp/Assist/WatchAudioRecorder.swift Adds watch audio recording with silence detection.
Sources/WatchApp/Assist/WatchAssistViewModel.swift Assist flow orchestration on watch (recording, ping/pong, error handling).
Sources/WatchApp/Assist/WatchAssistView+Build.swift Factory for building WatchAssistView with concrete dependencies.
Sources/WatchApp/Assist/WatchAssistView.swift Assist UI on watch (chat bubbles, mic button, loading states, volume).
Sources/WatchApp/Assist/WatchAssistService.swift Sends Assist audio to iPhone via chunked immediate messages.
Sources/WatchApp/Assist/Views/VolumeView.swift Watch volume control wrapper (WKInterfaceVolumeControl).
Sources/WatchApp/Assist/Views/LabsLabel.swift “Labs” badge UI for Assist screen.
Sources/WatchApp/Assist/Views/ChatBubbleView.swift Chat bubble styling + alignment per Assist item type.
Sources/WatchApp/Assets.xcassets/TemplateLogo.imageset/Contents.json Removes auto-scaling property from watch asset metadata.
Sources/WatchApp/Assets.xcassets/RoundLogo.imageset/Contents.json Removes auto-scaling property from watch asset metadata.
Sources/WatchApp/Assets.xcassets/Contents.json Normalizes asset catalog JSON formatting.
Sources/Watch/WatchCommunicatorService.swift Adds guaranteed-message fallback for config pulls; adds chunked DB mirror streaming.
Sources/Shared/Watch/WatchDatabaseMirror.swift Updates documentation for chunked/streamed mirror delivery.
Sources/Shared/Watch/InteractiveImmediateMessages.swift Adds mirror chunk message/response types; updates docs for new protocol.
Sources/Shared/Resources/Swiftgen/Strings.swift SwiftGen output update for new watch strings.
Sources/Shared/Database/GRDB+Initialization.swift Adds eraseAllData() helper used by the watch “Delete local data” action.
Sources/Extensions/Watch/Utilities/Date+ComplicationDivination.swift Removes legacy ClockKit date encoding utility.
Sources/Extensions/Watch/Resources/Info.plist Removes legacy watch extension Info.plist (ClockKit path).
Sources/Extensions/Watch/Resources/Assets.xcassets/Contents.json Removes legacy watch extension assets catalog root.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Contents.json Removes legacy complicationset descriptor.
Sources/Extensions/Watch/Resources/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json Removes legacy complication image set metadata.
Sources/Extensions/Watch/HostingController.swift Removes legacy watch extension HostingController implementation.
Sources/Extensions/Watch/Home/WatchHomeViewModel.swift Removes legacy watch extension home view model (superseded by new watch app flow).
Sources/Extensions/Watch/Complication/ComplicationController.swift Removes legacy ClockKit complication data source.
Sources/Extensions/Watch/Complication/Assist/AssistDefaultComplication.swift Removes legacy ClockKit Assist complication implementation.
Sources/App/Resources/en.lproj/Localizable.strings Adds English strings for new watch sync/troubleshooting/toast UI.
HomeAssistant.xcodeproj/xcshareddata/xcschemes/WatchWidgetsExtension.xcscheme Adds a shared Xcode scheme for the new watch widgets extension.
HomeAssistant.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme Updates scheme launcher/debugger settings.
HomeAssistant.xcodeproj/xcshareddata/xcschemes/WatchApp (Complication).xcscheme Updates scheme launcher/debugger settings.
fastlane/README.md Documents install_git_hooks lane usage.

Comment thread Sources/WatchApp/Complication/Assist/AssistDefaultComplication.swift Outdated
Comment thread WatchWidgets/WatchWidgets.swift
@bgoncal

bgoncal commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts on this branch.

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts on this branch.

Resolved by merging origin/main into this branch and fixing all merge conflicts in commit a9a7f33c0.

@bgoncal

bgoncal commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@copilot fix lint issues reported by CI

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@copilot fix lint issues reported by CI

Fixed in fa568b8 by applying SwiftFormat-required redundantSelf formatting in the WatchWidgets files flagged by CI.

@bgoncal

bgoncal commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts on this branch.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Unused L10n strings detected

Found 3 unused localization strings in the codebase.

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

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

Checking for unused strings...
Checked 100/1907 strings...
Checked 200/1907 strings...
Checked 300/1907 strings...
Checked 400/1907 strings...
Checked 500/1907 strings...
Checked 600/1907 strings...
Checked 700/1907 strings...
Checked 800/1907 strings...
Checked 900/1907 strings...
Checked 1000/1907 strings...
Checked 1100/1907 strings...
Checked 1200/1907 strings...
Checked 1300/1907 strings...
Checked 1400/1907 strings...
Checked 1500/1907 strings...
Checked 1600/1907 strings...
Checked 1700/1907 strings...
Checked 1800/1907 strings...
Checked 1900/1907 strings...

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

Found 3 unused strings:


CAMERAPLAYER:
  - L10n.CameraPlayer.Talkback.microphoneDenied
    Key: camera_player.talkback.microphone_denied
    Line: 820

CONNECTION:
  - L10n.Connection.Permission.InternalUrl.body1
    Key: connection.permission.internal_url.body1
    Line: 1370
  - L10n.Connection.Permission.InternalUrl.body2
    Key: connection.permission.internal_url.body2
    Line: 1372

================================================================================
Total unused: 3
================================================================================

================================================================================
Copy-paste these keys into the "Lokalise: Delete Keys" workflow (keys input):
================================================================================
camera_player.talkback.microphone_denied,connection.permission.internal_url.body1,connection.permission.internal_url.body2

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:

camera_player.talkback.microphone_denied,connection.permission.internal_url.body1,connection.permission.internal_url.body2

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts on this branch.

Resolved in 4062d4c by merging the latest origin/main into this branch and completing conflict resolution.

@bgoncal bgoncal enabled auto-merge (squash) July 9, 2026 22:42
@bgoncal bgoncal merged commit 756f419 into main Jul 10, 2026
14 of 15 checks passed
@bgoncal bgoncal deleted the watch-regression branch July 10, 2026 08:06
bgoncal added a commit that referenced this pull request Jul 10, 2026
The WatchWidgets extension added in #5027 broke the App-Release archive in
the Distribute workflow with:

  Extensions-WatchWidgets has conflicting provisioning settings.
  Extensions-WatchWidgets is automatically signed, but provisioning profile
  iOS App Store - Extensions-WatchWidgets has been manually specified.

Two things were wrong:

1. The target was created via the Xcode new-target wizard, which hard-coded
   CODE_SIGN_STYLE = Automatic in its Debug and Release build configs. Every
   other shipping target leaves this unset and inherits it from the
   xcconfigs (Automatic for Debug, Manual for the App-Release archive). The
   target-level Automatic overrode the xcconfig's Manual while the release
   xcconfig's PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*] =
   "iOS App Store - $(TARGET_NAME)" was still inherited, producing the
   conflict. Removing the two lines lets WatchWidgets sign exactly like
   WatchApp and the other extensions.

2. No App Store provisioning profile existed for the new bundle id
   io.robbie.HomeAssistant.watchkitapp.WatchWidgets. CI imports the profiles
   committed under Configuration/Provisioning, so the newly created
   "iOS App Store - Extensions-WatchWidgets" profile is added there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bgoncal added a commit that referenced this pull request Jul 10, 2026
The WatchWidgets extension added in #5027 has no App Store provisioning
profile for its bundle id io.robbie.HomeAssistant.watchkitapp.WatchWidgets,
so the App-Release archive in the Distribute workflow fails to sign it.

CI imports the profiles committed under Configuration/Provisioning
(setup_ha_ci -> import_provisioning_profiles), so the newly created
"iOS App Store - Extensions-WatchWidgets" profile is added there alongside
the rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bgoncal added a commit that referenced this pull request Jul 10, 2026
…5033)

## Summary

Follow-up to #5032. Committing the provisioning profile was necessary
but not sufficient: the **Distribute** workflow's `App-Release` archive
still fails with the same error ([latest failing
run](https://github.com/home-assistant/iOS/actions/runs/29084034712/job/86333386325)):

> `Extensions-WatchWidgets has conflicting provisioning settings.
Extensions-WatchWidgets is automatically signed, but provisioning
profile iOS App Store - Extensions-WatchWidgets has been manually
specified.`

`Extensions-WatchWidgets` (added in #5027) was created via the Xcode
new-target wizard, which hard-coded `CODE_SIGN_STYLE = Automatic` into
its Debug **and** Release build configs. Every other shipping target
(`App`, `WatchApp`, all `Extensions-*`) leaves `CODE_SIGN_STYLE` unset
at the target level and inherits it from the xcconfigs:

- `Configuration/HomeAssistant.xcconfig` → `Automatic` for Debug
- `Configuration/HomeAssistant.release.xcconfig` → `Manual` for the
archive, plus `PROVISIONING_PROFILE_SPECIFIER[sdk=watchos*] = iOS App
Store - $(TARGET_NAME)`

The target-level `Automatic` overrode the xcconfig's `Manual` while the
manual profile specifier was still inherited. xcodebuild rejects that
combination at signing-settings resolution — **before** the profile is
even used — which is why adding the profile in #5032 didn't clear it.

Removing the two lines lets WatchWidgets resolve signing exactly like
`WatchApp`: `Manual` + the `iOS App Store - Extensions-WatchWidgets`
profile for the release archive, `Automatic` for local Debug builds.

## Changes
- Remove target-level `CODE_SIGN_STYLE = Automatic` from
`Extensions-WatchWidgets` (Debug + Release).

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

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants