Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f344f95
feat(issues): Add native stack trace component
scttcper Apr 25, 2026
3f44186
add display dropdown specific to native
scttcper Apr 27, 2026
1494a6e
Merge branch 'master' into scttcper/native-stack-trace
scttcper May 18, 2026
41c157b
feat(stacktrace): Persist native display options
scttcper May 18, 2026
44f4d0f
feat(issues): Add native threaded stack trace
scttcper May 18, 2026
ef3d67d
fix(issues): Tighten native frame header layout
scttcper May 19, 2026
82bd40b
make sure character is not clipped
scttcper May 19, 2026
7abb725
Merge remote-tracking branch 'origin/master' into scttcper/native-sta…
scttcper May 29, 2026
e5e76ab
fix(issues): Load raw native crash reports
scttcper May 29, 2026
c03a7d5
fix(issues): Fill native stack trace parity gaps
scttcper May 29, 2026
8adad86
fix(issues): Hide Dart async native frames
scttcper May 29, 2026
896dc8a
fix(issues): Match native lead hint visibility
scttcper May 29, 2026
12e1afe
fix(issues): Restore native hidden frame styling
scttcper May 29, 2026
88767b5
fix(issues): Show native grouping markers by default
scttcper May 29, 2026
43a4a46
fix(issues): Match native verbose function display
scttcper May 29, 2026
86b2932
fix(issues): Match native stack trace behavior
scttcper May 29, 2026
f8b2828
fix(issues): Show git provider banner for native exceptions
scttcper May 29, 2026
37bd4ac
ref(issues): Use React state for native thread selection
scttcper May 29, 2026
e836ec0
ref(issues): Simplify native thread stack trace context
scttcper May 29, 2026
804ce63
ref(issues): Simplify native frame status handling
scttcper May 29, 2026
173e943
knip
scttcper May 29, 2026
9c3346d
smaller breakpoint
scttcper May 29, 2026
7714792
Merge branch 'master' of https://github.com/getsentry/sentry into sct…
scttcper Jun 3, 2026
970a542
Merge branch 'master' into scttcper/native-stack-trace
scttcper Jun 22, 2026
9aff59f
fix(issues): Restore native thread stack trace parity
scttcper Jun 22, 2026
87ce0e6
knip
scttcper Jun 22, 2026
e881a14
chore(stacktrace): Merge master into native stack trace
scttcper Jul 31, 2026
ee98fca
feat(stacktrace): Add native stack trace previews
scttcper Jul 31, 2026
6784506
fix(stacktrace): Isolate native stack trace state
scttcper Jul 31, 2026
8b58bd7
ref(stacktrace): Use React 19 context providers
scttcper Jul 31, 2026
cbbf12a
chore(issues): Merge master into native stack trace branch
scttcper Sep 10, 2026
466c247
fix(issues): Preserve native stack trace display behavior
scttcper Sep 10, 2026
4e90043
ref(issues): Simplify native stack trace components
scttcper Sep 10, 2026
677e609
ref(issues): Scope native stack providers to trace content
scttcper Sep 10, 2026
b28b87e
feat(issues): Cover native exceptions and standalone stacks
scttcper Sep 10, 2026
d73c8b9
test(issues): Fix native stack trace CI checks
scttcper Sep 10, 2026
1326d2d
chore: Merge latest master
scttcper Oct 1, 2026
add8fc6
fix(issues): Port native stack trace updates
scttcper Oct 1, 2026
5bf395b
style(issues): Combine native stack trace imports
scttcper Oct 1, 2026
e2cbdc1
test(issues): Wait for native stack trace source links
scttcper Oct 1, 2026
c08dce6
fix(issues): Restore native stack trace desktop breakpoint
scttcper Oct 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,17 @@ export function NativeContent({
address: frame.instructionAddr,
});

try {
const relativeAddress = (
parseAddress(frame.instructionAddr) -
parseAddress(correspondingImage.image_addr)
).toString(16);

return maxLengthUntilThisPoint > relativeAddress.length
? maxLengthUntilThisPoint
: relativeAddress.length;
} catch {
if (!frame.instructionAddr || !correspondingImage?.image_addr) {
return maxLengthUntilThisPoint;
}

const relativeAddress = (
parseAddress(frame.instructionAddr) - parseAddress(correspondingImage.image_addr)
).toString(16);

return maxLengthUntilThisPoint > relativeAddress.length
? maxLengthUntilThisPoint
: relativeAddress.length;
},
0
);
Expand Down
4 changes: 2 additions & 2 deletions static/app/components/events/interfaces/nativeFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {IconFileBroken} from 'sentry/icons/iconFileBroken';
import {IconRefresh} from 'sentry/icons/iconRefresh';
import {IconWarning} from 'sentry/icons/iconWarning';
import {t, tn} from 'sentry/locale';
import type {ImageWithCombinedStatus} from 'sentry/types/debugImage';
import type {Image} from 'sentry/types/debugImage';
import type {Event, Frame} from 'sentry/types/event';
import type {SentryAppSchemaStacktraceLink} from 'sentry/types/integrations';
import type {PlatformKey} from 'sentry/types/platform';
Expand All @@ -56,7 +56,7 @@ type Props = {
frame: Frame;
frameMeta: Record<any, any>;
hiddenFrameCount: number | undefined;
image: ImageWithCombinedStatus;
image: Image | null;
isFirstInAppFrame: boolean;
/**
* Is the stack trace being previewed in a hovercard?
Expand Down
8 changes: 6 additions & 2 deletions static/app/components/events/interfaces/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ interface HiddenFrameIndicesProps {
toggleFrameMap: Record<number, boolean>;
}

export function findImageForAddress({event, addrMode, address}: ImageForAddressProps) {
export function findImageForAddress({
event,
addrMode,
address,
}: ImageForAddressProps): Image | null {
const images = event.entries.find(entry => entry.type === 'debugmeta')?.data?.images;

if (!images || !address) {
Expand All @@ -45,7 +49,7 @@ export function findImageForAddress({event, addrMode, address}: ImageForAddressP
return addrMode === `rel:${idx}`;
});

return image;
return image ?? null;
}

export function isRepeatedFrame(frame: Frame, nextFrame?: Frame) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import {EventFixture} from 'sentry-fixture/event';
import {EventStacktraceFrameFixture} from 'sentry-fixture/eventStacktraceFrame';
import {OrganizationFixture} from 'sentry-fixture/organization';

import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';

import type {EventError, ExceptionType, ExceptionValue, Frame} from 'sentry/types/event';
import {EntryType} from 'sentry/types/event';

import {StackTracePreview} from './stackTracePreview';
import type {StacktraceType} from 'sentry/types/stacktrace';

import {StackTracePreview, StackTracePreviewContent} from './stackTracePreview';

function makeNativePreview() {
const stacktrace: StacktraceType = {
frames: [
EventStacktraceFrameFixture({
filename: 'ViewController.m',
function: 'ViewController.causeCrash',
inApp: true,
instructionAddr: '0x100001000',
package: '/build/CrashyApp.app/CrashyApp',
platform: 'cocoa',
}),
],
framesOmitted: null,
hasSystemFrames: false,
registers: null,
};

return {
event: EventFixture({platform: 'cocoa'}),
stacktrace,
};
}

beforeEach(() => {
MockApiClient.clearMockResponses();
Expand Down Expand Up @@ -106,4 +132,47 @@ describe('StackTracePreview', () => {
// Hide the platform icon for stack trace previews
expect(screen.queryAllByRole('img')).toHaveLength(0);
});

it('renders legacy native stack trace previews without the feature flag', () => {
const {event, stacktrace} = makeNativePreview();

render(<StackTracePreviewContent event={event} stacktrace={stacktrace} />);

expect(screen.getByTestId('stack-trace-frame')).toBeInTheDocument();
expect(
screen.queryByTestId('native-stack-trace-frame-title')
).not.toBeInTheDocument();
});

it('renders new native stack trace previews with the feature flag', () => {
const {event, stacktrace} = makeNativePreview();

render(<StackTracePreviewContent event={event} stacktrace={stacktrace} />, {
organization: OrganizationFixture({
features: ['issue-details-native-stack-trace'],
}),
});

expect(screen.getByTestId('native-stack-trace-frame-title')).toBeInTheDocument();
expect(screen.queryByTestId('stack-trace-frame')).not.toBeInTheDocument();
});

it('shows every system-only native frame in the new preview', () => {
const {event, stacktrace} = makeNativePreview();
stacktrace.frames = ['system_start', 'system_wait', 'system_end'].map(functionName =>
EventStacktraceFrameFixture({
function: functionName,
inApp: false,
platform: 'cocoa',
})
);

render(<StackTracePreviewContent event={event} stacktrace={stacktrace} />, {
organization: OrganizationFixture({features: ['issue-details-native-stack-trace']}),
});

expect(screen.getByText('system_start')).toBeInTheDocument();
expect(screen.getByText('system_wait')).toBeInTheDocument();
expect(screen.getByText('system_end')).toBeInTheDocument();
});
});
14 changes: 14 additions & 0 deletions static/app/components/groupPreviewTooltip/stackTracePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import {
} from 'sentry/components/groupPreviewTooltip/utils';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {IssueStackTracePreview} from 'sentry/components/stackTrace/issueStackTrace/issueStackTracePreview';
import {NativeStackTracePreview} from 'sentry/components/stackTrace/native/nativeStackTracePreview';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import {EntryType} from 'sentry/types/event';
import type {StacktraceType} from 'sentry/types/stacktrace';
import {defined} from 'sentry/utils/defined';
import {isNativePlatform} from 'sentry/utils/platform';
import {useOrganization} from 'sentry/utils/useOrganization';

export function getStacktrace(event: Event): StacktraceType | null {
const exceptionsWithStacktrace =
Expand Down Expand Up @@ -60,6 +62,7 @@ export function StackTracePreviewContent({
stacktrace: StacktraceType;
groupingCurrentLevel?: number;
}) {
const organization = useOrganization();
const includeSystemFrames = useMemo(() => {
return stacktrace?.frames?.every(frame => !frame.inApp) ?? false;
}, [stacktrace]);
Expand All @@ -80,6 +83,17 @@ export function StackTracePreviewContent({
| Partial<React.ComponentProps<typeof StackTraceContent>>;

if (isNativePlatform(platform)) {
if (organization.features.includes('issue-details-native-stack-trace')) {
return (
<NativeStackTracePreview
event={event}
stacktrace={stacktrace}
platform={platform}
groupingCurrentLevel={groupingCurrentLevel}
/>
);
}

return <NativeContent {...commonProps} groupingCurrentLevel={groupingCurrentLevel} />;
}

Expand Down
23 changes: 20 additions & 3 deletions static/app/components/stackTrace/frame/actions/chevron.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
import styled from '@emotion/styled';

import {Button} from '@sentry/scraps/button';

import {
useStackTraceContext,
useStackTraceFrameContext,
} from 'sentry/components/stackTrace/stackTraceContext';
import {IconChevron} from 'sentry/icons';
import {t} from 'sentry/locale';

const CHEVRON_SLOT_SIZE = 24;

export function ChevronAction() {
const {hasAnyExpandableFrames} = useStackTraceContext();
const {isExpandable, isExpanded} = useStackTraceFrameContext();
const {frameContextId, isExpandable, isExpanded, toggleExpansion} =
useStackTraceFrameContext();

if (!hasAnyExpandableFrames) {
return null;
}

return (
<ChevronSlot data-test-id="core-stacktrace-chevron-slot" aria-hidden>
<ChevronSlot data-test-id="core-stacktrace-chevron-slot">
{isExpandable ? (
<IconChevron direction={isExpanded ? 'down' : 'right'} size="xs" />
<Button
aria-controls={frameContextId}
aria-expanded={isExpanded}
aria-label={
isExpanded ? t('Collapse frame details') : t('Expand frame details')
}
icon={<IconChevron direction={isExpanded ? 'down' : 'right'} size="xs" />}
size="zero"
variant="transparent"
onClick={event => {
event.stopPropagation();
toggleExpansion();
}}
/>
) : null}
</ChevronSlot>
);
Expand Down
13 changes: 2 additions & 11 deletions static/app/components/stackTrace/frame/frameHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,8 @@ interface FrameHeaderProps {

export function FrameHeader({actions}: FrameHeaderProps) {
const [isHovering, setIsHovering] = useState(false);
const {
frame,
frameContextId,
isExpandable,
isExpanded,
nextFrame,
platform,
toggleExpansion,
} = useStackTraceFrameContext();
const {frame, isExpandable, isExpanded, nextFrame, platform, toggleExpansion} =
useStackTraceFrameContext();

const resolvedActions = typeof actions === 'function' ? actions({isHovering}) : actions;
const leadsToApp = !frame.inApp && (nextFrame?.inApp || !nextFrame);
Expand All @@ -80,8 +73,6 @@ export function FrameHeader({actions}: FrameHeaderProps) {
data-test-id="core-stacktrace-frame-title"
isExpandable={isExpandable}
hasLeadHint={hasLeadHint}
aria-expanded={isExpandable ? isExpanded : undefined}
aria-controls={isExpandable ? frameContextId : undefined}
onClick={() => {
const selectedText = window.getSelection()?.toString();
if (isExpandable && !selectedText) {
Expand Down
20 changes: 17 additions & 3 deletions static/app/components/stackTrace/frame/frameRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const StackTraceFrameRowRoot = memo(function StackTraceFrameRowRootImpl({
const {
collapseAll,
event,
defaultExpandedFrameIndex,
emptySourceNotation,
frames,
hasScmSourceContext,
lastFrameIndex,
Expand All @@ -39,15 +41,23 @@ const StackTraceFrameRowRoot = memo(function StackTraceFrameRowRootImpl({
} = useStackTraceContext();

const registers = row.frameIndex === frames.length - 1 ? stacktrace.registers : {};
const isSingleEmptySourceFrame =
emptySourceNotation && frames.length === 1 && row.frameIndex === 0;
const [isExpanded, setIsExpanded] = useState(
() => !collapseAll && row.frameIndex === lastFrameIndex
() =>
!collapseAll &&
row.frameIndex ===
(defaultExpandedFrameIndex === undefined
? lastFrameIndex
: defaultExpandedFrameIndex)
);

const isFrameExpandable = frameHasExpandableDetails({
frame: row.frame,
registers,
platform,
hasScmSourceContext,
emptySourceNotation: isSingleEmptySourceFrame,
});

const frameContextId = useId();
Expand All @@ -62,6 +72,8 @@ const StackTraceFrameRowRoot = memo(function StackTraceFrameRowRootImpl({
hiddenFramesExpanded: !!hiddenFrameToggleMap[row.frameIndex],
isExpandable: isFrameExpandable,
isExpanded,
isSubFrame: row.isSubFrame,
isUsedForGrouping: row.isUsedForGrouping,
nextFrame: row.nextFrame,
platform,
timesRepeated: row.timesRepeated,
Expand All @@ -78,6 +90,8 @@ const StackTraceFrameRowRoot = memo(function StackTraceFrameRowRootImpl({
hiddenFrameToggleMap,
isExpanded,
isFrameExpandable,
row.isSubFrame,
row.isUsedForGrouping,
platform,
row.frame,
row.frameIndex,
Expand All @@ -89,11 +103,11 @@ const StackTraceFrameRowRoot = memo(function StackTraceFrameRowRootImpl({
);

return (
<StackTraceFrameContext.Provider value={value}>
<StackTraceFrameContext value={value}>
<FrameRowContainer data-test-id="core-stacktrace-frame-row">
{children}
</FrameRowContainer>
</StackTraceFrameContext.Provider>
</StackTraceFrameContext>
);
});

Expand Down
Loading
Loading