Skip to content

appGroupSet/Get/Delete on iOS is an in-process HashMap stub — wire UserDefaults(suiteName:) (follow-up to #675) #1178

@proggeramlug

Description

@proggeramlug

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:

  1. 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.
  2. 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.
  3. Emit the application-groups entitlement automatically when [ios] app_group is set, matching what hand-written app-extension projects do today.
  4. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions