Skip to content

[pull] main from expo:main#794

Merged
pull[bot] merged 20 commits into
code:mainfrom
expo:main
Apr 22, 2026
Merged

[pull] main from expo:main#794
pull[bot] merged 20 commits into
code:mainfrom
expo:main

Conversation

@pull

@pull pull Bot commented Apr 22, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

chrfalch and others added 20 commits April 22, 2026 12:00
# Why

ExpoWidgetsTarget is a Swift-only app extension. It transitively links
libExpoModulesCore.a and libExpoModulesJSI.a, both of which compile C++
(common/cpp/**/*.cpp, ios/JSI/**/*.{cpp,mm}) and therefore reference
std::*, __cxa_*, and the C++ EH personality __gxx_personality_v0.
Because the widget target itself contains zero C++/Obj-C++ sources,
Xcode picks clang as the link driver rather than clang++, and libc++ /
the C++ ABI are never implicitly linked → every C++ runtime symbol is
undefined.

# How

Adding -lc++ to ExpoModulesCore.podspec fixes this.

user_target_xcconfig = { 'OTHER_LDFLAGS' => '$(inherited) -lc++' }
instructs CocoaPods to merge -lc++ into the OTHER_LDFLAGS of every
aggregate xcconfig that consumes ExpoModulesCore — including
Pods-ExpoWidgetsTarget.{debug,release}.xcconfig.

# Test-plan

Build test-widgets project in both xcframework and source - now works.

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [x] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
…#44732)

# Why

The `splash`, `ios.splash` / `android.splash`, and `jsEngine` accessors
on `expo-manifests` are dead: no runtime consumer reads them anymore.
Expo Go was the last caller, and it was actually rendering a splash
screen that actually doesn't use any value from it (the app icon and
name on a white background).

# How

**`expo-manifests`**: Removed `iosSplashBackgroundColor` /
`iosSplashImageUrl` / `iosSplashImageResizeMode` /
`getAndroidSplashInfo` / `getRootSplashInfo` / `jsEngine`, and the
matching expectations in `EmbeddedManifestSpec` +
`ExpoUpdatesManifestSpec`.

**`@expo/cli`**: Trimmed the `getExpoSchema` mock in
`resolveAssets-test.ts` to drop the legacy splash entries.

**Expo Go (Android)**: `ManagedAppSplashScreenConfiguration` is now just
`imageUrl` (from `manifest.getIconUrl()`) + `appName`. Ripped out every
`resizeMode` reference (the enum, the manifest keys, the
singleton/provider/listener parameter, the string-resource lookup, and
`Constants.SPLASH_SCREEN_IMAGE_RESIZE_MODE`). `SplashScreenView`
composable swaps Coil's `AsyncImage` + `crossfade` for
`rememberAsyncImagePainter` and fades the whole `Column` in via an alpha
bound to the painter state.

**Expo Go (iOS)**: `ManagedAppSplashScreenConfiguration` is `appName` +
`imageUrl` (from `manifest.iosAppIconUrl()`);
`SplashScreenImageResizeMode` and its use in the builder are gone.
`ManagedSplashscreenViewProvider` dropped the dead `splashImageView`
property and two unused params, and `createImageView` now takes an
`onLoad` callback that fades the stack view in from `alpha = 0`.

# Test Plan

- Expo Go (Android + iOS): splash shows app icon + name and fades in
once the icon loads; no placeholder flash.
- `expo-manifests`: iOS test specs pass.
- `@expo/cli`: `resolveAssets-test.ts` passes.

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
# Why

To verify compatible versions.

# How

Added `expo-widgets` with main version.

# Test Plan

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
#44993)

… as a public header

# Why

`et prebuild expo-modules-core` was failing at the SPM explicit-PCM step
because `SwiftUIVirtualViewSharedImpl+Private.h` was leaking into the
public ExpoModulesCore module umbrella. That file is not actually a
standalone header — it's a code fragment that
`SwiftUIVirtualViewObjC.mm` and `SwiftUIVirtualViewObjCDev.mm`
`#include` inside their `@implementation` blocks (introduced in #44118
when the view was split into dev/release variants).

When clang tried to compile the fragment as a standalone submodule
header during PCM generation, it hit parse errors on the top-level
method definitions and `react::` types, which only make sense inside an
`@implementation` context. The visible diagnostics looked like
`RCTBridgeModule` warnings, but the real fatal error was the fragment
parse. CocoaPods builds never tripped on this because pods don't emit an
umbrella-PCM over the public header tree — that's why the regression
wasn't caught at PR review time.

# How

Fix: add a `fileMapping` entry in
`packages/expo-modules-core/spm.config.json` that routes the fragment
into the target source tree next to its including `.mm` files. The SPM
generator skips files listed in `fileMapping` when populating the public
`include/` umbrella, so the fragment no longer leaks in, and the
relative `#include` still resolves because the file is symlinked
alongside the `.mm` sources in the staging dir. No rename, no
IDE-ergonomics regression on the `.h` file.

Added a regression test at
`tools/src/prebuilds/SPMGenerator.headerStaging.test.ts` that asserts no
`@implementation` fragment leaks into the public include dir for the
`ExpoModulesCore_ios_objc` target — would have caught this at PR time.

# Test-plan

Verified: `et prebuild expo-modules-core -f Debug --clean` now succeeds
on both the ios-arm64 and ios-arm64_x86_64-simulator slices.

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [x] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
# Why

It is currently not possible to determine whether the currently playing
media is HDR.

# How

Add `videoRange` field to `VideoTrack`, which contains information about
HDR content.

# Test Plan

Tested on iOS and Android with a Dolby Vision HLS stream (iOS) and a
locally recorded HDR video (Android)
…#44346)

# Why

`getLiveActivityUrl` in `Utils.swift` uses
`WidgetsStorage.getData(forKey:)` to read the Live Activity URL, but the
URL is stored as a `String` via `WidgetsStorage.set(_:forKey:)` in
`LiveActivityFactory.swift`. `UserDefaults.data(forKey:)` returns `nil`
for keys stored as strings, so the widget URL is always `nil` and the
Dynamic Island's `.widgetURL()` never navigates anywhere.

## How to reproduce

1. Start a Live Activity with a URL:

```ts
import { startActivity } from 'expo-widgets';

startActivity('MyActivity', { title: 'Hello' }, { url: 'myapp://details/123' });
```

2. Tap the Dynamic Island or expanded Live Activity
3. Nothing happens — the app does not navigate to the deep link URL

# How

Changed `getLiveActivityUrl` to use `WidgetsStorage.getString(forKey:)`
instead of `getData(forKey:)`, matching how the URL is stored.

```diff
 func getLiveActivityUrl(forName name: String) -> URL? {
-  let data = WidgetsStorage.getData(forKey: "__expo_widgets_live_activity_\(name)_url")
-  if let data, let url = String(data: data, encoding: .utf8) {
-    return URL(string: url)
+  guard let urlString = WidgetsStorage.getString(forKey: "__expo_widgets_live_activity_\(name)_url") else {
+    return nil
   }
-  return nil
+  return URL(string: urlString)
 }
```

# Test Plan

- Start a Live Activity with a URL via `startActivity(name, props, { url
})`
- Tap the Dynamic Island / expanded Live Activity
- Verify the app opens to the specified deep link URL

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [x] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [x] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)

Co-authored-by: Jakub Grzywacz <kontakt@jakubgrzywacz.pl>
To make sure we have smoke tested the prebuilds we run a short
precompile of expo-modules-core. If any other packages have changes -
they will also be rebuilt.

Will be run for changes in the packages directory and the
tools/prebuild.
# Why

Fixes #44757

After entering PiP from a fullscreen video, on iOS, the player will
return to the non-fullscreen video view after exiting PiP.

# How

Add three options of methods of returning to fullscreen from pip -
depending on usage you may want something else - on Android we don't
need this since PiP can't be used within the app there.


 - `'always'`: Always re-enter fullscreen when PiP stops.
- `'autoEnter'`: Re-enter fullscreen only when PiP was started
automatically by the app going to the background. (default)
 - `'never'`: Do not re-enter fullscreen when PiP stops.
 

Adding this required a bit of work - when entering PiP `AVPlayer` will
always exit fullscreen - but we can use
`restoreUserInterfaceForPictureInPictureStopWithCompletionHandler` to
re-enter Fullscreen before exiting PiP.

# Test Plan

Tested on iPhone 13 running iOS 18
# Why

It's useful in widgets (but not exclusively).

# How

Added `containerBackground` modifier.

# Test Plan

```tsx
import { Text, VStack } from '@expo/ui/swift-ui';
import { containerBackground } from '@expo/ui/swift-ui/modifiers';
import { createWidget, WidgetEnvironment } from 'expo-widgets';

type MyWidgetProps = {
  emoji: string;
};

const MyWidget = (props: MyWidgetProps, env: WidgetEnvironment) => {
  'widget';
  return (
    <VStack>
      <Text>Time:</Text>
      <Text>{env.date.toLocaleTimeString().split(' ')[0].split(':').slice(0, 2).join(':')}</Text>
      <Text modifiers={[containerBackground('#ff0000', 'widget')]}>Emoji:</Text>
      <Text>{props.emoji}</Text>
    </VStack>
  );
};

export default createWidget('Widget2', MyWidget);
```

<img width="300"
alt="SimShot_iPhone_17_Pro_with_Apple_Watch_2026-03-23_22-41-31"
src="https://github.com/user-attachments/assets/8f18ba1a-3c94-470a-b827-981feaee9e9b"
/>

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [x] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [x] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
## Why

The `detect` step of the iOS XCFramework prebuild smoke test was failing
on PRs that touched no eligible packages. EAS's `set-output` helper
rejects an empty VALUE and exits 2:

```
Resolved packages: <none — skipping smoke test>
Usage: set-output NAME VALUE
... exited with non-zero code: 2
```

That turned the intended "nothing to smoke-test → skip" path into a hard
failure for every PR matching the workflow's `paths:` filters without
resolving packages.

## How

- `scripts/detect-prebuild-packages.sh`: when `$PKGS` is empty, emit the
sentinel `none` via `set-output packages "${PKGS:-none}"` instead of an
empty string.
- `.eas/workflows/ios-prebuild-xcframeworks-smoke.yml`: switch the three
gated steps from `packages != ''` to `packages != 'none'` so they still
skip in the no-packages case.

Net result: the job reports success with the three real-work steps
skipped (visible in the EAS UI), matching the comment at the top of the
script.

## Test plan

- [ ] This PR itself touches the script + workflow, so the `detect` step
should resolve to `expo-modules-core` (via the tools/workflow-changed
branch) and run the smoke test end-to-end.
- [ ] Confirm on a separate PR that touches no eligible packages, the
job goes green with `use_npm_token`, `install_node_modules`, and
`Prebuild XCFrameworks` marked skipped.
…for updates bandwidth estimates (#44946)

Co-authored-by: Aman Mittal <amandeepmittal@live.com>
)

# Why
Adds real-time access to the microphone's PCM stream on iOS so apps can feed audio into speech recognition, custom VAD, streaming transcription, waveform visualizers, or any pipeline that needs samples as they're captured, not after a recording finishes.

# How
Introduces a new `AudioStream` `SharedObject`

- Uses `AVAudioEngine` with a tap on `inputNode`.
- Buffers are emitted as `audioStreamBuffer` events carrying a `NativeArrayBuffer`
- `audioStreamStatus` events

# Test Plan
Bare expo
…44901)

# Why

Exposes real-time PCM stream on Android so consumers can feed the microphone into speech recognition, custom VAD, streaming transcription, or waveform UIs.

# How

Adds `AudioStream` `SharedObject`
- Uses `AudioRecord` with `MediaRecorder.AudioSource.MIC`.
- Events (`audioStreamBuffer`, `audioStreamStatus`) 

Ran into an issue with emitting `NativeArrayBuffer` from a sharedObject. The default converter doesn't know how
to handle a `NativeArrayBuffer`, so emitted buffers arrive as `null`. Passing `useExperimentalConverter = true` routes the call through the path that handles `NativeArrayBuffer` correctly. cc @lukmccall 

# Test Plan
Bare expo
# Why
Adds the JS api for the audio stream feature and a demo screen.

# How
Adds `useAudioStream()` hook and supporting types

# Test Plan
Bare expo
# Why

I noticed this TS error when wrapping Sentry + Observe

<img width="1252" height="303" alt="Screenshot 2026-04-22 at 11 58 03"
src="https://github.com/user-attachments/assets/7e2c916e-478e-4553-b794-ce55ed5b8b31"
/>

When `AppMetricsRoot.wrap` is called with a zero-prop component (e.g. an
Expo Router `RootLayout`), TypeScript has no inference candidate for `P`
and falls back to the generic's constraint, so `P` resolves to `object`
and the return type becomes `ComponentType<object>`. That collapsed type
does not compose with other HOCs such as `Sentry.wrap`, which expect
`ComponentType<Record<string, unknown>>`, because `object` gives no
guarantee about string-keyed access.

<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How

Adding a default of `Record<string, unknown>` (`P extends object =
Record<string, unknown>`). This only changes the fallback when inference
has no candidate, leaving prop-ful callers untouched, and makes
zero-prop components resolve to `ComponentType<Record<string,
unknown>>`, which composes cleanly with any HOC.

<!--
How did you build this feature or fix this bug and why?
-->

# Test Plan

Verified locally that the TS error went away.

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
@pull pull Bot locked and limited conversation to collaborators Apr 22, 2026
@pull pull Bot added the ⤵️ pull label Apr 22, 2026
@pull pull Bot merged commit 5cc8e95 into code:main Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants