perry/system :: appGroupSet/Get/Delete on iOS is an in-process HashMap stub — wire UserDefaults(suiteName:)
Follow-up to: PerryTS/perry#675 (closed completed — the MVP).
Perry version: 0.5.1016.
What we shipped
#675 landed the surface (appGroupSet, appGroupGet, appGroupDelete in perry/system), but the iOS bridge is a placeholder — crates/perry-ui-ios/src/lib.rs:1421:
pub extern "C" fn perry_system_app_group_set(key_ptr: i64, value_ptr: i64) {
perry_runtime::stub_diag::perry_stub_warn(
"perry_system_app_group_set",
"iOS App Group is an in-process HashMap in this MVP (#675 follow-up: UserDefaults(suiteName:))",
Some("#675"),
);
...
g.insert(key.to_string(), value.to_string());
}
So today on iOS:
- Values written by the main app via
appGroupSet live in an in-process HashMap.
- They're lost on app restart.
- They're invisible to any other process — in particular, widget extensions cannot read them.
Widget code calls sharedStorage(key) which is correctly emitted as UserDefaults(suiteName: <app_group>)?.string(forKey: key) (see crates/perry-codegen-swiftui/src/emit.rs:692). So the widget side is right; the app side is the stub.
Concrete downstream impact
Our app (GSC Master) writes the user's JWT via appGroupSet("gscmaster_jwt", token) so the widget extension can fetch sites/stats with it. With the current stub, the widget never sees the token and resolves to its "not signed in" placeholder. We're blocked from removing our last perry-searchbird-* fork dependency cleanly while this is a stub — or we ship a known regression vs. the previous sb_shared_set_preference path which did write to UserDefaults(suiteName:).
Ask
Replace the HashMap with the real UserDefaults(suiteName:) backing on iOS:
- Read the app's App Group identifier from
perry.toml (proposed key: [ios] app_group = "group.com.example.shared"). Bake it into the iOS bundle at compile time the way getAppVersion() / getBundleId() bake project.version / project.bundle_id.
- Implement the three calls:
UserDefaults(suiteName: appGroup)?.set(value, forKey: key)
UserDefaults(suiteName: appGroup)?.string(forKey: key)
UserDefaults(suiteName: appGroup)?.removeObject(forKey: key)
Equivalent on macOS.
- Emit the
application-groups entitlement automatically when [ios] app_group is set, matching what hand-written app-extension projects do today.
- If
[ios] app_group is missing but the app calls appGroupSet, the existing perry_stub_warn diagnostic is fine to keep — but with a clearer "not configured in perry.toml" message.
For Android: the equivalent is context.getSharedPreferences("perry_shared", MODE_PRIVATE) (matches the sharedStorage() glance bridge in crates/perry-codegen-glance/src/emit_glue.rs:54). The current iOS-only stub-warn pattern likely already exists or has the same gap there — worth checking in the same PR.
Workaround until this lands
Either:
- Ship widgets that show a "not signed in" state always (regression vs. the old fork).
- Keep
perry-searchbird-shared around purely for the sb_shared_set_preference JWT-sharing path.
Neither is great; the small UserDefaults(suiteName:) wiring is the right end state.
perry/system :: appGroupSet/Get/Deleteon iOS is an in-process HashMap stub — wireUserDefaults(suiteName:)Follow-up to: PerryTS/perry#675 (closed completed — the MVP).
Perry version: 0.5.1016.
What we shipped
#675landed the surface (appGroupSet,appGroupGet,appGroupDeleteinperry/system), but the iOS bridge is a placeholder —crates/perry-ui-ios/src/lib.rs:1421:So today on iOS:
appGroupSetlive in an in-processHashMap.Widget code calls
sharedStorage(key)which is correctly emitted asUserDefaults(suiteName: <app_group>)?.string(forKey: key)(seecrates/perry-codegen-swiftui/src/emit.rs:692). So the widget side is right; the app side is the stub.Concrete downstream impact
Our app (GSC Master) writes the user's JWT via
appGroupSet("gscmaster_jwt", token)so the widget extension can fetch sites/stats with it. With the current stub, the widget never sees the token and resolves to its "not signed in" placeholder. We're blocked from removing our lastperry-searchbird-*fork dependency cleanly while this is a stub — or we ship a known regression vs. the previoussb_shared_set_preferencepath which did write toUserDefaults(suiteName:).Ask
Replace the HashMap with the real
UserDefaults(suiteName:)backing on iOS:perry.toml(proposed key:[ios] app_group = "group.com.example.shared"). Bake it into the iOS bundle at compile time the waygetAppVersion()/getBundleId()bakeproject.version/project.bundle_id.application-groupsentitlement automatically when[ios] app_groupis set, matching what hand-written app-extension projects do today.[ios] app_groupis missing but the app callsappGroupSet, the existingperry_stub_warndiagnostic is fine to keep — but with a clearer "not configured in perry.toml" message.For Android: the equivalent is
context.getSharedPreferences("perry_shared", MODE_PRIVATE)(matches thesharedStorage()glance bridge incrates/perry-codegen-glance/src/emit_glue.rs:54). The current iOS-only stub-warn pattern likely already exists or has the same gap there — worth checking in the same PR.Workaround until this lands
Either:
perry-searchbird-sharedaround purely for thesb_shared_set_preferenceJWT-sharing path.Neither is great; the small UserDefaults(suiteName:) wiring is the right end state.