From 57320c7535b98147f92ff30721e7e4651c284bce Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 29 Jun 2026 10:47:05 +0200 Subject: [PATCH 1/2] feat(tracing): Emit app.vitals.start.screen on standalone app start (RN-691) Set the `app.vitals.start.screen` attribute on the standalone `app.start` transaction from the current route tracked by the tracing integration. Unlike the non-standalone `ui.load` transaction (named after the screen, which Relay backfills from), the standalone transaction is named `App Start`, so the screen is set explicitly. Omitted when no route has been registered yet at capture time. Co-Authored-By: Claude Opus 4.8 --- .../src/js/tracing/integrations/appStart.ts | 11 ++++++++ .../core/src/js/tracing/semanticAttributes.ts | 1 + .../tracing/integrations/appStart.test.ts | 28 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index c628fa279e..79c2c0f79b 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -31,7 +31,9 @@ import { UI_LOAD as UI_LOAD_OP, } from '../ops'; import { SPAN_ORIGIN_AUTO_APP_START, SPAN_ORIGIN_MANUAL_APP_START } from '../origin'; +import { getCurrentReactNativeTracingIntegration } from '../reactnativetracing'; import { + SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN, SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE, SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE, SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -659,6 +661,15 @@ export const appStartIntegration = ({ event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE] = appStartDurationMs; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE] = appStart.type; + // Screen shown when app start completes. Unlike the non-standalone `ui.load` transaction + // (whose name is the screen, which Relay backfills from), the standalone transaction is named + // `App Start`, so we set the screen explicitly. Sourced from the current route tracked by the + // tracing integration; omitted when no route has been registered yet at capture time. + const screen = getCurrentReactNativeTracingIntegration()?.state.currentRoute; + if (screen) { + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN] = screen; + } + // Minimal parent referencing the root transaction span, so the breakdown spans attach // directly under it (the helpers only read op/origin/span_id/trace_id/start_timestamp). // `data` is shared with the root trace context so frame data lands on the root span. diff --git a/packages/core/src/js/tracing/semanticAttributes.ts b/packages/core/src/js/tracing/semanticAttributes.ts index ab8f033000..1e7d3b4474 100644 --- a/packages/core/src/js/tracing/semanticAttributes.ts +++ b/packages/core/src/js/tracing/semanticAttributes.ts @@ -22,3 +22,4 @@ export const SEMANTIC_ATTRIBUTE_NAVIGATION_ACTION_TYPE = 'navigation.action_type // App start vitals (Span V2 / EAP). Emitted on the standalone `app.start` transaction. export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE = 'app.vitals.start.value'; export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE = 'app.vitals.start.type'; +export const SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN = 'app.vitals.start.screen'; diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index 9baf5be660..9f3f7661ef 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -36,7 +36,9 @@ import { setRootComponentCreationTimestampMs, } from '../../../src/js/tracing/integrations/appStart'; import { SPAN_ORIGIN_AUTO_APP_START, SPAN_ORIGIN_MANUAL_APP_START } from '../../../src/js/tracing/origin'; +import * as ReactNativeTracing from '../../../src/js/tracing/reactnativetracing'; import { + SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN, SEMANTIC_ATTRIBUTE_APP_VITALS_START_TYPE, SEMANTIC_ATTRIBUTE_APP_VITALS_START_VALUE, } from '../../../src/js/tracing/semanticAttributes'; @@ -147,6 +149,32 @@ describe('App Start Integration', () => { }); }); + it('sets app.vitals.start.screen from the current route', async () => { + const [timeOriginMilliseconds, appStartTimeMilliseconds] = mockAppStart({ cold: true }); + const screenSpy = jest + .spyOn(ReactNativeTracing, 'getCurrentReactNativeTracingIntegration') + .mockReturnValue({ state: { currentRoute: 'HomeScreen' } } as ReturnType< + typeof ReactNativeTracing.getCurrentReactNativeTracingIntegration + >); + + try { + const actualEvent = await captureStandAloneAppStart(); + expect(actualEvent).toEqual( + expectEventWithStandaloneColdAppStart(actualEvent, { timeOriginMilliseconds, appStartTimeMilliseconds }), + ); + expect(actualEvent?.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN]).toBe('HomeScreen'); + } finally { + screenSpy.mockRestore(); + } + }); + + it('omits app.vitals.start.screen when no route has been registered', async () => { + mockAppStart({ cold: true }); + + const actualEvent = await captureStandAloneAppStart(); + expect(actualEvent?.contexts?.trace?.data).not.toHaveProperty(SEMANTIC_ATTRIBUTE_APP_VITALS_START_SCREEN); + }); + it('Does not add any spans or measurements when App Start Span is longer than threshold', async () => { set__DEV__(false); mockTooLongAppStart(); From 77f2b5d4a59cd62db9f48cc6d86faa1d8c903b2f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 29 Jun 2026 10:47:56 +0200 Subject: [PATCH 2/2] docs: Add changelog entry for app.vitals.start.screen Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd6ec2b0b..34b682d16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Features - Add experimental `enableStandaloneAppStartTracing` to send app start as a standalone `app.start` transaction ([#6359](https://github.com/getsentry/sentry-react-native/pull/6359)) +- Set `app.vitals.start.screen` on the standalone `app.start` transaction from the current route ([#6369](https://github.com/getsentry/sentry-react-native/pull/6369)) - Use the runtime's native `btoa` for envelope base64 encoding when available, to improve `captureEnvelope` performance. Falls back to the bundled JS encoder if `btoa` is missing ([#6351](https://github.com/getsentry/sentry-react-native/pull/6351)). - Opt-in build-time auto-wrap for Expo Router's per-route `ErrorBoundary` ([#6347](https://github.com/getsentry/sentry-react-native/pull/6347))