# FTStoreActivity — Live Activity / Dynamic Island (Manager) This folder holds the **Live Activity** widget extension that shows the download → sign → install progress in the Dynamic Island and on the lock screen while FTStore Manager puts FTStore (or FTStore Admin) onto the device. It is the app's island, forked the way the rest of `manager/` was: same shape, same files, minus what the Manager does not have. See `ios/FTStoreActivity/README.md` for the reasoning behind the design — this file only records where the two differ and what is true here. ## What is different from the app's - **No pause button, and no `isPaused` anywhere.** The Manager's `Download` has no pause: there is nothing to hold a download with and no screen to offer it from. So `InstallActivityIntents.swift` does not exist here, the extension needs no `AppIntents`, and the island is read-only on every iOS version rather than only below 17. - **One install in practice, the same shape regardless.** The Manager tracks two products and nothing stops both being started, so `ContentState` still describes a set. It costs nothing and it is the only shape iOS renders well: a second *activity* collapses the island to its minimal presentation. - Bundle id: **`$(FTSTORE_PRODUCT_BUNDLE_IDENTIFIER).activity`** → `app.ftstore.manager.activity`, which sits under the Manager's own `app.ftstore.manager` exactly as iOS requires. ## Facts about the target - The widget-extension **target exists in `FTStore.xcodeproj`** (added by hand to `project.pbxproj`, synchronized-folder style, ids prefixed `57AC11…` — the same ids the app's tree uses, because this project is a fork of that one down to its GUIDs and keeping them equal is what keeps the two files diffable). - This folder is a synchronized root group owned by the `FTStoreActivity` target; `Info.plist` and `README.md` are membership exceptions. - `FTStore/Backend/Observable/InstallActivityAttributes.swift` is shared into the extension via an exception set on the `FTStore` folder: the widget needs the attributes type to render. **It must stay in both targets.** - iOS Deployment Target **16.1** (the app is 16.0; ActivityKit needs 16.1, and `LiveActivityManager` uses the 16.2 `ActivityContent` API). - The appex is **iOS-only**: the embed build file and the target dependency carry `platformFilter = ios`, so Mac Catalyst builds skip it entirely. - The app's `Info.plist` sets **`NSSupportsLiveActivities = YES`**. - The Makefile ad-hoc codesign uses `--deep` so the nested unsigned appex doesn't make sealing fail; on-device signing re-signs PlugIns anyway. ## How it's driven - `LiveActivityManager` (in the app target) starts/updates/ends the activity. - `AppInstallManager` calls it across the lifecycle: `start` when the download begins, `update(.signing)` while signing, `update(.installing)` at install hand-off, and `end` when the installer answers, on cancel, and on failure. - **The island ends when the installer answers, not when the sheet closes.** Tapping *Open* launches the app that was just installed and leaves the sheet standing behind it, so ending on dismissal left the island saying "Installing" until somebody came back and closed a receipt they were done with. Dismissal still ends it, which is what covers a flow that never gets an answer at all. - Everything is best-effort and availability-gated: with no widget target (or on iOS < 16.2), all calls are safe no-ops and the rest of the app is unaffected. ## Don't let another product share this build's derived data `manager/` is a fork of `ios/` down to the pbxproj GUIDs, and Xcode keys its build database on those. When both Makefiles pointed `-derivedDataPath` at `$(TMPDIR)/FTStore`, the app's build and this one swept each other's intermediates; the app shipped an `.appex` left over from an older build and its island showed nothing. Both Makefiles key the path on the product directory, and now that *both* products carry an extension, that separation is the only thing keeping them apart. The symptom is invisible from the app side, so the release workflows run `ios/scripts/verify-live-activity.sh` against the built IPA — this one included — and fail on it. What it checks, and how to ask a device why the island is dead, is documented in `ios/FTStoreActivity/README.md`; both hold here unchanged, except that the log predicate is `subsystem BEGINSWITH "app.ftstore.manager"`. ## Verifying Run on a device or simulator with iOS 16.2+. Tap **Install FTStore** on the home screen: the Dynamic Island (iPhone 14 Pro and newer) and the lock-screen banner should show the app name, a progress bar while downloading, then "Preparing" and "Installing", and dismiss shortly after the install sheet answers.