Repository navigation
fix(mobile): react-native-svg 15.15.1 and Lottie recycling for the new architecture - #14652
Conversation
|
|
Dependency limit exceeded — report not shown. This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report. Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard. Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account. |
lottie-react-native 7.3.4 keeps autoPlay, loop and the loaded animation on the native container when Fabric recycles the view, while _props goes back to defaults. The play bar's buffering spinner unmounts after the first play, its recycled view keeps playing, and its onAnimationFinish reaches AnimatedButton, which dispatches pause. Opt the component view out of recycling. lottie-react-native is nested under packages/mobile/node_modules, so the patch is applied from that workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15.15.0 (#2802) makes every SVG element push its bounds into a Fabric state update from drawRect (updateShadowNodeMetrics). Each update is a ShadowTree commit, and with Reanimated's commit hook each commit clones the tree and re-applies animated props, which redraws the SVGs and loops. On a static profile this kept the JS thread at ~150% CPU on the new architecture. 15.15.1 reverts it (software-mansion/react-native-svg#2832, Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f7df09b to
9e9ecef
Compare
87b0af7 to
fedf1e1
Compare
e9db547
into
fix/mobile-rntp-bridgeless
…14647) Prepares react-native-track-player 4.1.2 (Apache-2.0) for the new architecture. It ships with 1.5.188 on the old architecture, where it changes nothing. **Base: `chore/mobile-rn-0.81` (#14642).** Rebased 2026-10-08 after #14642 moved onto the 1.5.188 release branch (#14654). Merge #14652 into this branch first, then this into #14642. Don't merge to main on its own. We are staying on RNTP 4.1.2. v5 (`@rntp/player`) is commercially licensed. 4.1.2 is the last 4.x release, and the fixes below were never published for it. ## Changes (Android only, in `patches/react-native-track-player+4.1.2.patch`) **1. Event emission in bridgeless mode** (doublesymmetry/react-native-track-player#2593, #2619) - `MusicService.emit()` / `emitList()` read the context from `reactNativeHost.reactInstanceManager.currentReactContext`. That is null in bridgeless mode, so every native-to-JS event was dropped: playback state, active track, remote/notification controls, progress and errors. - They now use the `reactContext` getter that `HeadlessJsTaskService` already provides. It returns `ReactInstanceManager.currentReactContext` on the old architecture (the same call as before) and `ReactHost.currentReactContext` in bridgeless mode. - That getter throws if `ReactHost` is null. Emission, `startTask` in `onStartCommand`, and `super.onDestroy()` now log and carry on instead of crashing the service. This is the same approach as upstream's fix for #2619 (ed52dc5, v5 line). **2. `@ReactMethod`s return `Unit`** - Every `TrackPlayerModule` method was written as `fun play(callback: Promise) = scope.launch { ... }`, so each one returned a `Job`. - The bridgeless TurboModule interop layer rejects a non-void return on an async method, so it threw while loading the module: > Unable to parse @ReactMethod annotations from native module: TrackPlayerModule On the new architecture the app aborted at startup (found in the spike, #14648). - They are now `fun play(callback: Promise) { scope.launch { ... } }`. The old bridge ignored the return value, so nothing changes there. The diff touches only the first and last line of each of the 37 methods. **Not applicable to 4.1.2:** - #2670 (`BackgroundServiceStartNotAllowedException` while emitting in the background) and #2662 (Android Auto cold start never starting the headless task) are v5 issues. In v5, emitting an event starts a `TrackPlayerTaskService`. In 4.1.2, `emit` never starts a service, and `onStartCommand` already calls `startTask`, which creates the React host when there is none. That cold path was checked on the new architecture in the spike. - iOS needs no change. The Swift `RCTEventEmitter` module works through the interop layer as it is. ## Verification (old architecture, `prodRelease` arm64 signed with the debug keystore, Android 16 emulator) I tested with a temporary JS harness that was not committed. It wraps the root component, logs every RNTP event with `console.warn` (visible in logcat) and in an on-screen overlay, queues 3 public tracks, seeks, and then seeks near the end. Signed out, `pm clear` before each launch, and logcat confirmed `Loading JS bundle from "assets://index.android.bundle"`. | Check | Result | |---|---| | Release build | ✅ | | Runtime arch | ✅ `bridgeless:false, fabric:false`, "Legacy Architecture" warning | | `PlaybackState` / `PlaybackActiveTrackChanged` reach JS | ✅ stopped → loading → buffering → ready → playing, `index:0` | | Seek | ✅ `seekTo(60)` → position 60–62.8 | | Auto-advance | ✅ `active-track-changed` 0 → 1 at the end of track 0, `getActiveTrackIndex()` = 1 | | `adb shell cmd media_session dispatch pause/play/next` | ✅ `remote-pause` / `remote-play` / `remote-next` reach JS, and the state follows | | Notification shade pause / play / previous | ✅ `remote-pause`, `remote-play`, `remote-previous` reach JS | | Screen off and backgrounded for 75 s | ✅ progress events kept arriving, MediaSession `PLAYING` | | iOS Release (old arch, 26.5 sim) | ✅ unchanged: events, seek and auto-advance all work | The same harness on the new architecture is in the spike PR. ## Still needs a real device Lock-screen and notification controls, Bluetooth, and background playback for more than 2 minutes, together with the rest of the 1.5.188 checklist in #14642. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1 of the React Native upgrade: 0.79.5 → 0.81.6 with the old architecture still on. **Base: `chore/mobile-native-release-1.5.188` (#14654).** Rebased onto it after 1.5.187 (#14645, which squashed #14627, #14640 and #14650) merged to main; the two lib-swap commits are gone from this branch. Merge #14647 and #14651 into this branch first, then this into #14654, then #14654 into main as one push (see #14654 for the full order). ## Rebase onto 1.5.187 (2026-10-08) - `patches/react-native+0.79.5.patch` (the ExceptionsManager Hermes fix from #14643) is renamed to `react-native+0.81.6.patch`. The anchors are unchanged in 0.81.6, and `scripts/patch-package.sh` applies it with `--error-on-fail` during `npm install` (checked: `getErrorStackSafe` is in the installed `ExceptionsManager.js`). - notifee and Firebase from #14640 are kept as they are: `@notifee/react-native` 9.1.8 is the latest release and `@react-native-firebase/app`/`messaging` 24.1.1 build on 0.81.6 on the old arch (both declare `react-native: *`). No bump needed. - The Firebase Android BoM stays pinned to 33.16.0. This branch moves Kotlin to 2.1.20, and BoM 34 needs 2.2, so the pin still applies; the comment now says so. - The UIScene and notifee lines in `AppDelegate.mm` (#14626, #14640) are untouched by this branch. - `package-lock.json` and `Podfile.lock` were regenerated (`npm install`, then `RCT_NEW_ARCH_ENABLED=0 bundle exec pod install`); the release xcconfig has no `RCT_NEW_ARCH_ENABLED=1`. The SwiftAudioEx post_install patch (#14650) still applies. **Needs a native release (1.5.188).** Don't OTA this JS onto older binaries. Old architecture stays on: Android `newArchEnabled=false`, iOS `RCTNewArchEnabled=false`, pods installed with `RCT_NEW_ARCH_ENABLED=0`. Two commits, one per step. Each step builds on iOS and Android: 1. 0.79.5 → 0.80.3 2. 0.80.3 → 0.81.6 (0.81.6 is the latest 0.81 patch on npm) ## Version bumps | Package | From | To | Why | |---|---|---|---| | react-native | 0.79.5 | 0.81.6 | the upgrade | | `@react-native/*` (babel-preset, eslint-config, metro-config, metro-babel-transformer, typescript-config) | 0.79.5 | 0.81.6 | must match RN | | `@react-native-community/cli`, `cli-platform-android`, `cli-platform-ios` | 18.0.0 | 20.1.3 | CLI line for 0.81; the platform packages were left at 18 on the old ladder branch | | react, react-dom, react-test-renderer (whole monorepo, root `overrides` too) | 19.0.0 | **19.1.4** | RN's bundled renderer throws at startup unless `react` is **exactly** its version: 19.1.0 for 0.80.3, 19.1.4 for 0.81.6. The old ladder branch used 19.1.8, which would throw "Incompatible React versions" on launch. | | @types/react / @types/react-dom | 19.0.0 | 19.1.17 / 19.1.11 | match React 19.1 | | react-native-gesture-handler | 2.25.0 | 2.28.0 | 2.28 is the first 2.x with official 0.81 support (2.25's table stops at 0.79). Stays on 2.x; v3 drops the old architecture. | | @amplitude/analytics-react-native | 1.4.11 | 1.4.14 | Kotlin 2.1 (RN 0.80+) turns the deprecated `toUpperCase(Locale)` in 1.4.11 into a compile error | | tiktok-opensdk-react-native | ^0.10.7 | 0.10.7 (pinned) | it now carries a patch. 0.10.9 fixes the Android signature, but drops the `handleOpenURL`/`handleUserActivity` declarations that SceneDelegate imports | Patches added (`patches/`): - `react-native-track-player+4.1.2.patch`: Kotlin 2.1 rejects `Arguments.fromBundle(Bundle?)`. Now resolves `null` when there is no item. 4.1.2 is the last 4.x and has no fixed release. - `tiktok-opensdk-react-native+0.10.7.patch`: `onNewIntent(intent: Intent)` (RN 0.80 made `ActivityEventListener` non-null) and `reactApplicationContext.currentActivity`. Checked against 0.81 on the old architecture and left alone: Reanimated 3.19.5 (the official table lists 3.19.x for 0.78–0.81 on Paper; Reanimated 4 is out of scope), track-player 4.1.2 (patched above), collapsible-tab-view 8.0.1 (JS only), screens 4.18.0 (0.81 support since 4.14; `react-native-screens+4.18.0.patch` still applies; 4.25+ drops the old arch), pager-view 6.7.1 (7+ drops the old arch), video 6.18.0, svg 15.15.0 (0.81 support since 15.12.1), google-cast 4.6.2 (5.x is new-arch only), notifications 5.1.0, code-push 12.3.2 (README lists 0.77–0.86; 13.x needs a new OTA history), bootsplash 6.3.11, safe-area-context 5.6.2, keyboard-controller 1.19.0, flash-list 1.8.3, datetimepicker 8.3.0 (builds against 0.81.6). ## Template changes (rn-diff-purge 0.79.5 → 0.81.6) - Android: Kotlin 2.0.21 → 2.1.20, Gradle 8.13 → 8.14.3, new `gradlew`/`gradlew.bat`/wrapper jar, `MainApplication` uses `loadReactNative(this)` in place of `SoLoader.init` plus `load()`, and `edgeToEdgeEnabled=false` is added as in the template. The app already draws edge-to-edge through react-native-bars and the SDK 36 target, so this flag changes nothing. - AGP comes from RN: 8.8 → 8.11.0, which supports compileSdk 36, so `android.suppressUnsupportedCompileSdk=36` is removed. - `settings.gradle` resolves `@react-native/gradle-plugin` through `require.resolve` from `react-native`, not a hard-coded hoisted path. With this lockfile npm nests it under `node_modules/react-native/node_modules`. - Kept: `newArchEnabled=false`, the `AudiusScrypt` module (`ScryptPackage` is still registered), the debug manifest (it adds `SYSTEM_ALERT_WINDOW`), and `android:enableOnBackInvokedCallback="false"`. - **Back handling:** since 0.80, `ReactActivity` registers an `OnBackPressedCallback` when targetSdk ≥ 36, which calls `onBackPressed()` → JS `BackHandler`. Our `invokeDefaultOnBackPressed` override (`moveTaskToBack`) still runs. The manifest opt-out is kept as asked and can probably go in a later release. - iOS: no template change between 0.79.5 and 0.81.6. `Info.plist` and `RichPushExtension/Info.plist` now set `RCTNewArchEnabled=false`, which RN 0.80+ reads **at runtime**; a missing key means new arch. `pod install` writes the key and re-sorts the plist each time, so the sorted version is committed. ## AppDelegate / UIScene No code change needed. `RCTAppDelegate` is marked deprecated in 0.81 in favour of `RCTReactNativeFactory`, so there is a `-Wdeprecated-declarations` warning, but every API we use is unchanged: `automaticallyLoadReactNativeWindow`, `rootViewFactory`, `createRootViewController`, `setRootView:toRootViewController:`, `customizeRootView:`, `bundleURL`, and `dependencyProvider`. `didFinishLaunching` still creates the factory and skips the window. Moving to `RCTReactNativeFactory` can wait for the 0.82+ phase. Verified on a Release build: cold launch, a deep link while killed, and a deep link while backgrounded, on iOS 26.5 and iOS 27. iOS 27 needs UIScene and showed no launch crash. ## Web / React 19.1 - Typecheck: web, common and harmony are clean, and so is mobile. - `vite build` (prod env): succeeds. - web vitest: 178 passed, 9 skipped. `TrackTile › Public Premium (non-owner)` timed out once while the machine was under heavy load, then passed 3 out of 3 when run alone. ## Verification matrix (before the rebase; see the bottom for the rebased stack) | | iOS 26.5 sim (Release) | iOS 27 sim (Release) | Android API 36 emu (prodRelease, arm64) | |---|---|---|---| | Build | ✅ Xcode 27, `generic/platform=iOS Simulator` | same binary | ✅ debug keystore | | Old arch at runtime | ✅ JS thread `com.facebook.react.JavaScript` | – | ✅ `ReactRootView` + "Legacy Architecture" warning | | Launch past splash | ✅ | ✅ | ✅ `assets://index.android.bundle` | | Sign-up / sign-in screens | (signed in) | ✅ | ✅ including Create Password | | Signed-in feed, scroll | ✅ | – | – | | Playback + now playing drawer (open, drag closed) | ✅ | – | – | | Background → foreground while playing | ✅ audio kept going (0:09 → 0:41) | – | – | | Search | ✅ | – | – | | Profile: collapsible header + tab swipe | ✅ | – | – | | Deep link, warm | ✅ `audius://audius` | ✅ link delivered | – | | Deep link, cold | ✅ `audius://deadmau5` | ✅ (signed out → sign-on) | – | | Hardware Back | – | – | ✅ pops Create Password; at root backgrounds the app without killing it | | Sign-on footer above nav bar | – | – | ✅ | | Red JS errors | none, apart from a recurring `JSON Parse error: Unexpected character: <` (not yet traced) | none | none | The CodePush folder was moved aside or cleared before every cold launch, so all results above are from the bundled JS. ## Still needs a real device - background audio with the screen locked for more than 2 minutes - lock-screen and notification controls - Chromecast - push notifications ## Notes - A Release build that downloads a production OTA built for 1.5.186 or 1.5.187 runs a 0.79 bundle on a 0.81 binary. 1.5.188 ships with its own package.json version (#14654) so it never receives those OTAs. - The Hermes `ExceptionsManager` fix now applies through patch-package (#14643), renamed for 0.81.6 above. ## Verification of the rebased stack (2026-10-08) Built from a local, unpushed integration branch: #14652's tip (which contains #14647) merged with #14651, on top of #14654. | Check | Result | |---|---| | `npx tsc --noEmit -p packages/mobile` | pass | | eslint on the touched files / `turbo run verify` lint | pass (1 existing warning in `HostRemixContestDrawer.tsx`) | | `cd packages/mobile && npm test` | 9 suites, 34 tests pass | | iOS Release, `generic/platform=iOS Simulator`, arm64, `ENVFILE=.env.prod` | builds; Info.plist 1.1.201, `RCTNewArchEnabled=false`; bundle carries 1.5.188 | | Android `assembleProdRelease`, arm64-v8a, debug keystore | builds; versionName 1.1.537 | | iOS 26.5 sim, installed over the signed-in app, CodePush moved aside | launch to feed; JS thread `com.facebook.react.JavaScript` (old arch) | | Playback from the 2nd feed track | plays; play bar tracker moves | | Auto-advance | seek to 2:43/2:45 on track 2, advanced to track 3 | | Now playing drawer | opens, scrubber advances, tap-to-seek works | | Profile tabs | swipe Tracks → Albums, tap Reposts | | notifee/RNFB APNs token | a temporary console log (not committed, rebuilt without it afterwards) printed an 80-byte lowercase APNs token at startup | | Android 16 emulator, `pm clear` | `Loading JS bundle from "assets://index.android.bundle"`, "Legacy Architecture" warning, sign-up and sign-in screens render | | Android hardware Back | **not verified**: the emulator's system_server was killed by its watchdog three times under host load, so Back never got a clean run | | JS errors on iOS | only `Could not cache profile images` (content node timeout) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary Version bump for native release **1.5.188**. Merging it starts the RC and production native builds. - `packages/mobile/package.json`: 1.5.187 → 1.5.188 (and the matching `package-lock.json` entry) - iOS `CFBundleShortVersionString`: 1.1.200 → 1.1.201 - Android `versionName`: 1.1.536 → 1.1.537 1.5.188 contains (all still on the old architecture): - #14642: React Native 0.79.5 → 0.81.6, React 19.1.4 monorepo-wide - #14647: RNTP 4.1.2 patch for bridgeless events and `Unit` module methods (no change on old arch) - #14651: playback progress and eq bars on the native driver - #14652: react-native-svg 15.15.1 and the Lottie `shouldBeRecycled NO` patch (no change on old arch) 1.5.189 is then only the new-architecture flag flip. ## Stack ``` main └── chore/mobile-native-release-1.5.188 (this PR) └── chore/mobile-rn-0.81 #14642 ├── fix/mobile-rntp-bridgeless #14647 │ └── fix/mobile-new-arch-prereqs #14652 └── fix/mobile-native-driver-progress #14651 ``` ## Verification of the rebased stack (2026-10-08) Built from a local, unpushed integration branch: #14652's tip (which contains #14647) merged with #14651, on top of #14654. | Check | Result | |---|---| | `npx tsc --noEmit -p packages/mobile` | pass | | eslint on the touched files / `turbo run verify` lint | pass (1 existing warning in `HostRemixContestDrawer.tsx`) | | `cd packages/mobile && npm test` | 9 suites, 34 tests pass | | iOS Release, `generic/platform=iOS Simulator`, arm64, `ENVFILE=.env.prod` | builds; Info.plist 1.1.201, `RCTNewArchEnabled=false`; bundle carries 1.5.188 | | Android `assembleProdRelease`, arm64-v8a, debug keystore | builds; versionName 1.1.537 | | iOS 26.5 sim, installed over the signed-in app, CodePush moved aside | launch to feed; JS thread `com.facebook.react.JavaScript` (old arch) | | Playback from the 2nd feed track | plays; play bar tracker moves | | Auto-advance | seek to 2:43/2:45 on track 2, advanced to track 3 | | Now playing drawer | opens, scrubber advances, tap-to-seek works | | Profile tabs | swipe Tracks → Albums, tap Reposts | | notifee/RNFB APNs token | a temporary console log (not committed, rebuilt without it afterwards) printed an 80-byte lowercase APNs token at startup | | Android 16 emulator, `pm clear` | `Loading JS bundle from "assets://index.android.bundle"`, "Legacy Architecture" warning, sign-up and sign-in screens render | | Android hardware Back | **not verified**: the emulator's system_server was killed by its watchdog three times under host load, so Back never got a clean run | | JS errors on iOS | only `Could not cache profile images` (content node timeout) | ## Merge order 1. **Wait until 1.5.187 is live in both stores.** Until then, any native change on main would go into a 1.5.187 rebuild. 2. **Dispatch a production OTA from main before merging anything below**, for every store version that can run main's JS: ``` gh workflow run mobile.yml --repo AudiusProject/apps --ref main -f ota_channel=production ``` With main at 1.5.187 this publishes to `production/1.5.187`. **Do not** dispatch `-f binary_version=1.5.186` or `1.5.185` from main any more: main now carries #14627, which calls `TurboModuleRegistry.getEnforcing('ReactNativeFs')` at import, so that JS crashes 1.5.186 and older binaries. If 1.5.186 users still need a fix, dispatch from a commit before 5c4ee7e. 3. **Land everything on main in one push.** Merge the stack into this branch from the top down: #14652 into #14647, #14647 and #14651 into #14642, #14642 into this branch. Then merge this PR into main. Any push to main that leaves the version unchanged publishes an RC OTA to the current binary's history, and RN 0.81 JS on a 0.79 binary will not start. With one push that also changes the version, the version check skips the OTA and starts the native builds. After that, OTAs route to `1.5.188` histories. 4. **Device-test the RC builds before submitting:** - cold launch on both platforms, old arch at runtime (iOS JS thread `com.facebook.react.JavaScript`; Android "Legacy Architecture" warning) - background audio with the screen locked for more than 2 minutes - lock-screen and notification controls, Bluetooth, Chromecast - auto-advance, including starting from the 2nd track of a list (#14649/#14650) - play bar and scrubber position: seek, drag, pause/resume, background then foreground - first play does not pause itself (Lottie) - push: token registered after the 1.5.187 → 1.5.188 update and taps open the right screen (#14640) - Android hardware Back on a pushed screen and at the root - gradients and offline downloads 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…sent (#14659) Web Build on main fails on a node_modules cache hit: ``` Error: Patch file found for package lottie-react-native which is not present at node_modules/lottie-react-native ``` The Lottie patch from #14652 lives in `packages/mobile/workspace-patches` because npm nests that package under `packages/mobile/node_modules`. web.yml's cache doesn't include that folder, so on a cache hit the package is missing and `--error-on-fail` exits. The mobile workflow caches the folder, so mobile builds are unaffected. The step now runs only when `packages/mobile/node_modules` exists. Full installs still apply the patch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Two native fixes the new-architecture flip (#14648) needs. They ship in 1.5.188 on the old arch, so 1.5.189 is only the flag flip.
Base:
fix/mobile-rntp-bridgeless(#14647). Rebased 2026-10-08 onto the rebased #14647. Merge this into #14647, not main. The lockfiles were checked after the rebase:npm installandpod install(old arch) leave them unchanged, andscripts/patch-package.shapplies the Lottie workspace patch with--error-on-fail. On old arch neither one changes behavior. The svg change is behindRCT_NEW_ARCH_ENABLED, and the Lottie method only exists on the Fabric component view.1. react-native-svg 15.15.0 → 15.15.1 (CPU regression, #14648 blocker 6)
On Fabric, 15.15.0 (software-mansion/react-native-svg#2802) has every SVG element push its bounds into a state update from
drawRect(updateShadowNodeMetrics). Each update is aShadowTree::commit. Reanimated 3's commit hook clones the tree and re-applies animated props on every commit, which redraws the SVGs, so the loop never settles. On an artist profile with nothing playing this held the app at ~150% CPU.Evidence: an lldb breakpoint on
EventDispatcher::dispatchStateUpdateon the profile showed every state update coming from-[RNSVGRenderable updateShadowNodeMetrics]/-[RNSVGGroup updateShadowNodeMetrics], called from-[RNSVGSvgView drawRect:]. Upstream reverted it in 15.15.1 (software-mansion/react-native-svg#2832, reported in #2831).Result on the same profile (new arch, iOS 26.5 simulator, Release): 151.5% → 20.2%. Old arch on the same route is 16.3%.
package-lock.jsonchanges only the nestedpackages/mobile/node_modules/react-native-svgentry, which stays nested becausemetro.config.jsresolves it there.Podfile.lockchanges only the RNSVG version and checksum.2. Lottie: opt out of Fabric view recycling (#14648 blocker 5)
lottie-react-native 7.3.4 keeps
autoPlay/loopand the loaded animation on the native container when Fabric recycles the view, while_propsgoes back to defaults. After the first play, the play bar's buffering spinner unmounts, its recycled view keeps playing, and itsonAnimationFinishreachesAnimatedButton, which dispatchespause. The fix is+shouldBeRecycled { return NO; }via patch-package.lottie-react-native is nested under
packages/mobile/node_modules, which the rootpatch-packagerun can't reach. So the patch lives inpackages/mobile/workspace-patches/, andscripts/patch-package.shapplies it from the mobile workspace.Verified on the simulator: previous session, 3 cold launches with no self-pause, and again today on the full spike build (the first play kept playing).
🤖 Generated with Claude Code