Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
28b8da6
[router] Fix drawer toggle asset path (#45170)
sleda May 11, 2026
ce79d13
[docs] Update Expo Skills page (#45569)
amandeepmittal May 11, 2026
a096d6c
[docs] Add TabsGroup in Clerk guide source file (#45561)
amandeepmittal May 11, 2026
99684f9
[docs] Fix Tailwind v4 important syntax in arbitrary value classes (#…
amandeepmittal May 11, 2026
cbbe925
[docs] Fix missing API docs data for AccessoryWidgetBackground in Exp…
amandeepmittal May 11, 2026
48d2c7a
[expo-router] replace jest image mapper with transform (#45624)
Ubax May 11, 2026
4a42108
[expo-go] Set constants to `EXConstantsBinding` (#45630)
alanjhughes May 11, 2026
9bfcb24
[app-metrics] Run spotless (#45631)
alanjhughes May 11, 2026
34cb5ac
[expo][Android] Suppress `DelicateCoroutinesApi` warning in `ExpoModu…
lukmccall May 11, 2026
da405fb
[docs] add experimental stack docs (#45625)
Ubax May 11, 2026
6f94ec1
[docs][ui] Add React Native trigger example for Menu and DropdownMenu…
intergalacticspacehighway May 11, 2026
a4d572f
feat(expo-doctor): Update `MetroConfigCheck` for SDK 56+ (#45567)
kitten May 11, 2026
d189f1a
[expo-observe] add useObserve hook (#45559)
Ubax May 11, 2026
f532750
[docs] Add react-navigation migration callout to react navigation mig…
Ubax May 11, 2026
cbc5290
[docs] add smart_retry to maestro job (#45535)
hSATAC May 11, 2026
3d415fe
[core][Android] Replace `JString::toString()` with faster alternative…
lukmccall May 11, 2026
2389562
[core][Android] Update KSP version lookup (#45642)
lukmccall May 11, 2026
42f92cc
[expo-app-metrics][expo-observe] move initial route name assignment t…
Ubax May 11, 2026
c98a8f7
[expo-observe][android] map to OTel by metric name and category (#45570)
Ubax May 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/bare-expo/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ThemeProvider } from 'ThemeProvider';
import { AppMetricsRoot } from 'expo-observe';
import { ObserveRoot } from 'expo-observe';
import * as Splashscreen from 'expo-splash-screen';
import React from 'react';
import * as DevMenu from 'expo-dev-menu';
Expand Down Expand Up @@ -94,8 +94,8 @@ export default function Main() {
const isLoaded = useLoaded();

return (
<AppMetricsRoot>
<ObserveRoot>
<ThemeProvider>{isLoaded ? <MainNavigator /> : null}</ThemeProvider>
</AppMetricsRoot>
</ObserveRoot>
);
}
1 change: 1 addition & 0 deletions apps/expo-go/ios/Exponent-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@
#import "EXReactAppManager.h"
#import "EXAbstractLoader.h"
#import "EXProgressHUD.h"
#import "EXConstantsBinding.h"
7 changes: 7 additions & 0 deletions apps/expo-go/ios/Exponent/Versioned/Core/VersionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ final class VersionManager: EXVersionManagerObjC {
self.appContext = appContext
self.legacyModuleRegistry = legacyModuleRegistry

// The ConstantsProvider hardcodes `executionEnvironment ti "bare"` and reads `manifest` from
// EXConstants.bundle. In Expo Go we need `executionEnvironment to be
// "storeClient". EXConstantsBinding merges them on top of the
// base constants, so installing it here makes `Constants.expoConfig`,
// `Constants.executionEnvironment` resolve correctly.
appContext.constants = EXConstantsBinding(params: params)

registerExpoModules(appContext)
hasRegisteredExpoModules = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DropdownMenuItem,
Host,
Icon,
RNHostView,
Row,
Switch,
Text as ComposeText,
Expand All @@ -14,7 +15,7 @@ import {
} from '@expo/ui/jetpack-compose';
import { background } from '@expo/ui/jetpack-compose/modifiers';
import * as React from 'react';
import { View, Text, Alert } from 'react-native';
import { View, Text, Alert, Pressable } from 'react-native';

import { Section } from '../../components/Page';

Expand All @@ -39,6 +40,7 @@ export default function DropdownMenuScreen() {
const [colorfulMenuExpanded, setColorfulMenuExpanded] = React.useState(false);
const [sectionsMenuExpanded, setSectionsMenuExpanded] = React.useState(false);
const [submenuExpanded, setSubmenuExpanded] = React.useState(false);
const [rnTriggerMenuExpanded, setRnTriggerMenuExpanded] = React.useState(false);

React.useEffect(() => {
if (sectionsMenuExpanded === false) {
Expand Down Expand Up @@ -319,6 +321,41 @@ export default function DropdownMenuScreen() {
</DropdownMenu>
</Host>
</Section>
<Section title="Dropdown Menu with RN Trigger">
<Host matchContents>
<DropdownMenu
expanded={rnTriggerMenuExpanded}
onDismissRequest={() => setRnTriggerMenuExpanded(false)}>
<DropdownMenu.Trigger>
<RNHostView matchContents>
<Pressable
onPress={() => setRnTriggerMenuExpanded(true)}
style={{
alignSelf: 'flex-start',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 8,
backgroundColor: '#9B59B6',
}}>
<Text style={{ color: 'white', fontWeight: '600' }}>RN Pressable Trigger</Text>
</Pressable>
</RNHostView>
</DropdownMenu.Trigger>
<DropdownMenu.Items>
<DropdownMenuItem onClick={() => setRnTriggerMenuExpanded(false)}>
<DropdownMenuItem.Text>
<ComposeText>Item 1</ComposeText>
</DropdownMenuItem.Text>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setRnTriggerMenuExpanded(false)}>
<DropdownMenuItem.Text>
<ComposeText>Item 2</ComposeText>
</DropdownMenuItem.Text>
</DropdownMenuItem>
</DropdownMenu.Items>
</DropdownMenu>
</Host>
</Section>
</View>
);
}
Expand Down
26 changes: 26 additions & 0 deletions apps/native-component-list/src/screens/UI/MenuScreen.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Section,
Divider,
Picker,
RNHostView,
} from '@expo/ui/swift-ui';
import {
buttonStyle,
Expand All @@ -18,6 +19,7 @@ import {
tag,
} from '@expo/ui/swift-ui/modifiers';
import * as React from 'react';
import { Pressable, Text as RNText } from 'react-native';

export default function MenuScreen() {
const [selectedIndex, setSelectedIndex] = React.useState<number | undefined>(1);
Expand Down Expand Up @@ -54,6 +56,30 @@ export default function MenuScreen() {
</Menu>
</Section>

<Section title="RN Pressable label">
<Menu
label={
<RNHostView matchContents>
<Pressable
onPress={() => console.log('RN trigger pressed')}
style={{
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 8,
alignSelf: 'flex-start',
backgroundColor: '#9B59B6',
}}>
<RNText style={{ color: 'white', fontWeight: '600' }}>
RN Pressable Trigger
</RNText>
</Pressable>
</RNHostView>
}>
<Button onPress={() => console.log('Item 1')} label="Item 1" />
<Button onPress={() => console.log('Item 2')} label="Item 2" />
</Menu>
</Section>

<Section title="Menu with Picker">
<Menu label="Select Option" systemImage="list.bullet">
<Picker
Expand Down
9 changes: 4 additions & 5 deletions apps/observe-tester/app/(tabs)/(metrics)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import AppMetrics, { type Metric } from 'expo-app-metrics';
import ExpoObserve from 'expo-observe';
import ExpoObserve, { useObserve } from 'expo-observe';
import { Link } from 'expo-router';
import { checkForUpdateAsync, fetchUpdateAsync, reloadAsync, useUpdates } from 'expo-updates';
import { useCallback, useEffect, useState } from 'react';
import { Platform, ScrollView, StyleSheet, Text, View } from 'react-native';

import { Button } from '@/components/Button';
import { Divider } from '@/components/Divider';
import { JSONView } from '@/components/JSONView';
import { useRouterMetricsHelpers } from '@/router-metrics-integration';
import { useTheme } from '@/utils/theme';

export default function Index() {
Expand All @@ -16,7 +16,7 @@ export default function Index() {
const [showEntries, setShowEntries] = useState(false);
const { isUpdateAvailable, isUpdatePending, availableUpdate, currentlyRunning } = useUpdates();

const { markPageInteractive } = useRouterMetricsHelpers();
const { markInteractive } = useObserve();

const updateStoredEntries = useCallback(async () => {
const events = await AppMetrics.getStoredEntries();
Expand All @@ -28,8 +28,7 @@ export default function Index() {
}, [updateStoredEntries]);

async function handleMarkInteractive() {
await AppMetrics.markInteractive();
await markPageInteractive();
await markInteractive();
await updateStoredEntries();
}

Expand Down
10 changes: 9 additions & 1 deletion apps/observe-tester/app/(tabs)/(sessions)/sessions/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AppMetrics, { type Session } from 'expo-app-metrics';
import { useObserve } from 'expo-observe';
import { Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { Platform, Pressable, ScrollView, StyleSheet, Text } from 'react-native';

import { CallStackTreeView } from '@/components/CallStackTreeView';
Expand All @@ -20,6 +21,13 @@ export default function SessionDetail() {
const [loaded, setLoaded] = useState(false);
const [showRawJson, setShowRawJson] = useState(false);

const { markInteractive } = useObserve();
useEffect(() => {
setTimeout(() => {
markInteractive();
}, 100);
}, []);

useFocusEffect(
useCallback(() => {
AppMetrics.getAllSessions().then((sessions) => {
Expand Down
10 changes: 9 additions & 1 deletion apps/observe-tester/app/(tabs)/(sessions)/sessions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AppMetrics, { type Session } from 'expo-app-metrics';
import { useObserve } from 'expo-observe';
import { router, Stack, useFocusEffect } from 'expo-router';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
ActivityIndicator,
Platform,
Expand All @@ -23,6 +24,13 @@ export default function SessionsList() {
const isActive = (s: Session) =>
!s.endDate && currentMainStart != null && s.startDate >= currentMainStart;

const { markInteractive } = useObserve();
useEffect(() => {
setTimeout(() => {
markInteractive();
}, 100);
}, []);

const refresh = useCallback(async () => {
const result = await AppMetrics.getAllSessions();
const sorted = [...result].sort((a, b) => (a.startDate < b.startDate ? 1 : -1));
Expand Down
8 changes: 4 additions & 4 deletions apps/observe-tester/app/(tabs)/debug/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppMetrics from 'expo-app-metrics';
import { useObserve } from 'expo-observe';
import { useEffect, useState } from 'react';
import { ScrollView, StyleSheet } from 'react-native';

Expand All @@ -7,20 +8,19 @@ import { CrashReportsSection } from '@/components/CrashReportsSection';
import { Divider } from '@/components/Divider';
import { JSAnimation } from '@/components/JSAnimation';
import { LogEventsSection } from '@/components/LogEventsSection';
import { useRouterMetricsHelpers } from '@/router-metrics-integration';
import { useTheme } from '@/utils/theme';

export default function Debug() {
const theme = useTheme();
const [showAnimation, setShowAnimation] = useState(false);

const { markPageInteractive } = useRouterMetricsHelpers();
const { markInteractive } = useObserve();

useEffect(() => {
setTimeout(() => {
markPageInteractive();
markInteractive();
}, 1000);
}, [markPageInteractive]);
}, []);

return (
<ScrollView
Expand Down
15 changes: 3 additions & 12 deletions apps/observe-tester/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import ExpoObserve, { AppMetricsRoot } from 'expo-observe';
import ExpoObserve, { ObserveRoot } from 'expo-observe';
import { Stack } from 'expo-router';
import { useColorScheme } from 'react-native';

import { startLoggingRouterMetrics } from '@/router-metrics-integration';

// Toggle to enable per screen router metrics logging
const IS_ROUTER_INTEGRATION_ENABLED = false;

if (IS_ROUTER_INTEGRATION_ENABLED) {
startLoggingRouterMetrics();
}

ExpoObserve.configure({
environment: 'custom-env',
dispatchingEnabled: true,
Expand All @@ -21,14 +12,14 @@ ExpoObserve.configure({
export default function RootLayout() {
const scheme = useColorScheme();
return (
<AppMetricsRoot>
<ObserveRoot>
<ThemeProvider value={scheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack
screenOptions={{
headerShown: false,
}}
/>
</ThemeProvider>
</AppMetricsRoot>
</ObserveRoot>
);
}
2 changes: 2 additions & 0 deletions apps/observe-tester/components/SessionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function SessionHeader({ session }: { session: Session }) {
? formatDuration(endDate.getTime() - startDate.getTime())
: 'still running';
const crashed = session.type === 'main' && !!session.crashReport;
const routeName = session.metrics.find((m) => m.name === 'timeToInteractive')?.routeName;

return (
<View style={styles.container}>
Expand All @@ -32,6 +33,7 @@ export function SessionHeader({ session }: { session: Session }) {
<Field label="Ended" value={endDate ? formatDateTime(endDate) : '—'} />
<Field label="Duration" value={duration} />
<Field label="Metrics" value={String(session.metrics.length)} />
{routeName ? <Field label="Initial route" value={routeName} monospace /> : null}
<Field
label="Session ID"
value={session.id}
Expand Down
Loading
Loading