feat(flow): add Picture in Picture keep-alive mode

- Add FlowKeepAliveMode (Dynamic Island vs PiP) with iCloud sync
- Settings: keep-alive picker; inactivity timeout only for Live Activity
- PiP: waveform sample-buffer controller, mic released between utterances
- PiP sessions have no idle expiry; user closing PiP ends the session
- FlowSessionManager branches hostReady, session start, and utterance paths
- UIBackgroundModes picture-in-picture; bilingual strings and tests

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-26 10:34:52 +00:00
parent e6f99d2744
commit 70ba3a4359
18 changed files with 801 additions and 36 deletions
@@ -62,6 +62,7 @@ final class SettingsCloudSyncTests: XCTestCase {
polishIntensity: SyncedField(value: .medium, updatedAt: stampA, deviceID: deviceA),
llmThinkingEnabled: SyncedField(value: false, updatedAt: stampA, deviceID: deviceA),
flowSkipAppSwitch: SyncedField(value: true, updatedAt: stampA, deviceID: deviceA),
flowKeepAliveMode: SyncedField(value: .liveActivity, updatedAt: stampA, deviceID: deviceA),
flowInactivityDuration: SyncedField(value: .twelveHours, updatedAt: stampA, deviceID: deviceA)
)
let remote = SyncedAppSettingsV2(
@@ -82,6 +83,7 @@ final class SettingsCloudSyncTests: XCTestCase {
polishIntensity: SyncedField(value: .light, updatedAt: stampB, deviceID: deviceB),
llmThinkingEnabled: SyncedField(value: true, updatedAt: stampB, deviceID: deviceB),
flowSkipAppSwitch: SyncedField(value: false, updatedAt: stampB, deviceID: deviceB),
flowKeepAliveMode: SyncedField(value: .pictureInPicture, updatedAt: stampB, deviceID: deviceB),
flowInactivityDuration: SyncedField(value: .threeHours, updatedAt: stampB, deviceID: deviceB)
)