feat(ios): add widget kind option to keep placed widgets across migrations - #272
Open
rlods wants to merge 4 commits into
Open
feat(ios): add widget kind option to keep placed widgets across migrations#272rlods wants to merge 4 commits into
kind option to keep placed widgets across migrations#272rlods wants to merge 4 commits into
Conversation
rlods
marked this pull request as ready for review
September 7, 2026 21:10
Author
|
cc @V3RON 👋 random ping amongst recent contributors, could you please have a look and release if good for you? |
Contributor
|
Hey @rlods! I pushed some commits to make sure your changes are reflected in both the Expo Config Plugin and RN CLI ( |
…rations WidgetKit identifies a placed widget by extension bundle id + kind. Voltra hardcoded `Voltra_Widget_<id>`, so a widget migrated from a hand-written extension turned into a placeholder on users' Home Screens. `kind` lets the config pin the legacy value. The override travels through Info.plist (`Voltra_WidgetKinds`, app + extension) and a `VoltraWidgetKind` helper maps id <-> kind for timeline reloads, `getActiveWidgets` and orphan cleanup. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
VoltraWidgetKind read Bundle.main from a computed property, so the id <-> kind mapping - the only new native logic behind the `kind` option - could not be covered: Bundle.main is the runner in VoltraSharedTests. Split a pure core out of each direction and move the plist read to VoltraConfig, which already owns Info.plist access and established this shape with voltraVersion(bundle:). The map is now read once per process rather than on every reloadTimeline.
Both plugin steps only ever set the key. A prebuild over an existing ios/ directory keeps what the plist already holds, so removing `kind` from the config left the widget answering to a kind nobody configured any more - the one Voltra key where a stale value silently changes which Home Screen instances a widget owns. Widen widgetKindOverrides to the fields it actually reads so the mapping can be covered without casting fixtures.
The bare React Native path is a separate implementation from the Expo plugin: it has its own config types, plist writer and Swift codegen, and it hardcoded Voltra_Widget_<id>. The native half is shared through the installed @use-voltra/ios-client, so this is plumbing - resolve the kind from one helper, write Voltra_WidgetKinds into the app and extension Info.plists, and reject duplicate kinds the way normalization already rejects duplicate ids. Unlike prebuild, `voltra apply` mutates a native project it does not own, so the main app plist deletes the key when no widget pins a kind.
rlods
force-pushed
the
feat/ios-widget-kind-option
branch
from
September 10, 2026 23:26
7a11bc2 to
337b09f
Compare
Author
thanks @V3RON - they look good to me |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional
kindto iOS widget configs. It overrides the WidgetKitkindof the generatedWidgetConfiguration, which today is hardcoded toVoltra_Widget_<id>.WidgetKit identifies a widget placed on the Home Screen by extension bundle id +
kind.targetNamealready lets an app keep its extension bundle id, but without control over the kind, every widget migrated from a hand-written WidgetKit extension turns into a dead placeholder after the update and users have to remove and re-add it.With
kindpinned to the legacy value, placed widgets pick up the Voltra timeline in place.Changes
IOSWidgetConfig.kind?: string, default unchanged (Voltra_Widget_<id>).widgetKind()/widgetKindOverrides()helpers inconstants.ts.widgetKind(widget)for bothStaticConfigurationandAppIntentConfiguration.Voltra_WidgetKinds: { id: kind }(main app viaconfigureInfoPlist, extension viaconfigureWidgetExtensionPlist).ios/shared/VoltraWidgetKind.swiftmaps id → kind (plist override, else prefix) and kind → id (reverse lookup, else strip prefix, elsenilfor non-Voltra kinds). Used byreloadTimeline,getActiveWidgets(sonamestill reports the widget id),getInstalledWidgetIds(orphan cleanup) andVoltraRefreshIntent. It lives inios/sharedso both podspecs compile it into the app and the extension.kindmust be a non-empty string; kinds must be unique across widgets, including a custom kind colliding with another widget's default.kindin thewidgetsoption list plus a "Keeping the kind of an existing widget" section. Changeset:@use-voltra/ios-clientminor.Testing
pnpm run testinpackages/ios-client: 88 tests pass, including new cases for the template (kindused, noVoltra_Widget_left) and validation (empty kind, duplicate kinds, custom vs default collision).pnpm run typecheck,pnpm run lint,swiftformat --lint ios,swift test(69 tests) pass.VoltraWidgetService.swifttype-check against the iOS 17 simulator SDK.@use-voltra/ios-client@2.2.0to migrate a production widget from a hand-written WidgetKit extension. On-device upgrade verification is still in progress, hence the draft.Checklist
npm test)npm run lint:libOnly)npm run format:check)🤖 Generated with Claude Code