From f2cd61ace5080a27d2248881b78752673e00cdd2 Mon Sep 17 00:00:00 2001 From: Ognjen Bostjancic Date: Mon, 6 Jul 2026 11:03:45 +0200 Subject: [PATCH 1/7] feat(traces): Use new transcript in AI trace tabs Behind the gen-ai-conversations-redesign flag, the trace AI tab now leads with the redesigned transcript instead of the Timeline/Spans tab, and renders the Seer Explorer-styled transcript (MessagesPanelNew) with the redesigned span detail panel that opens on span selection. The flag-off path is unchanged: Spans first, legacy MessagesPanel. Refs TET-2604 --- .../traceDrawer/tabs/traceAiConversations.tsx | 210 +++++++++++++++--- 1 file changed, 179 insertions(+), 31 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 0241a1762a90..d174b1948387 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -15,8 +15,16 @@ import { ConversationSplitLayout, ConversationViewSkeleton, } from 'sentry/views/explore/conversations/components/conversationLayout'; +import { + ConversationSpanDetail, + type DetailTab, +} from 'sentry/views/explore/conversations/components/conversationSpanDetail'; import {ConversationAggregatesBar} from 'sentry/views/explore/conversations/components/conversationSummary'; import {MessagesPanel} from 'sentry/views/explore/conversations/components/messagesPanel'; +import { + MessagesPanelNew, + MessagesPanelSkeleton, +} from 'sentry/views/explore/conversations/components/messagesPanelNew'; import {useConversation} from 'sentry/views/explore/conversations/hooks/useConversation'; import {useConversationSelection} from 'sentry/views/explore/conversations/hooks/useConversationSelection'; import {CONVERSATIONS_LANDING_SUB_PATH} from 'sentry/views/explore/conversations/settings'; @@ -39,7 +47,12 @@ export function TraceAiConversations({ traceSlug, }: TraceAiConversationsProps) { const organization = useOrganization(); - const [activeSubTab, setActiveSubTab] = useState('spans'); + const hasRedesign = hasGenAiConversationsRedesignFeature(organization); + // The redesign leads with the transcript, so default to the first + // conversation tab instead of the Timeline/Spans tab. + const [activeSubTab, setActiveSubTab] = useState(() => + hasRedesign && conversationIds.length > 0 ? `chat-${conversationIds[0]}` : 'spans' + ); const [selectedSpanId, setSelectedSpanId] = useState(null); const handleTabChange = useCallback((key: Key) => { @@ -72,29 +85,33 @@ export function TraceAiConversations({ [conversationNodes, nodeTraceMap, traceSlug] ); - const hasRedesign = hasGenAiConversationsRedesignFeature(organization); + const tabItems = useMemo((): Array<{ + conversationId: string | null; + key: string; + label: string; + }> => { + const spansTab = { + key: 'spans', + label: hasRedesign ? t('Timeline') : t('Spans'), + conversationId: null, + }; + const conversationTabs = conversationIds.map(id => ({ + key: `chat-${id}`, + label: hasRedesign + ? conversationIds.length === 1 + ? t('Transcript') + : t('Transcript %s', id.slice(0, 8)) + : conversationIds.length === 1 + ? t('Chat') + : t('Chat %s', id.slice(0, 8)), + conversationId: id, + })); - const tabItems = useMemo( - (): Array<{conversationId: string | null; key: string; label: string}> => [ - { - key: 'spans', - label: hasRedesign ? t('Timeline') : t('Spans'), - conversationId: null, - }, - ...conversationIds.map(id => ({ - key: `chat-${id}`, - label: hasRedesign - ? conversationIds.length === 1 - ? t('Transcript') - : t('Transcript %s', id.slice(0, 8)) - : conversationIds.length === 1 - ? t('Chat') - : t('Chat %s', id.slice(0, 8)), - conversationId: id, - })), - ], - [conversationIds, hasRedesign] - ); + // The redesign leads with the transcript(s), then the timeline. + return hasRedesign + ? [...conversationTabs, spansTab] + : [spansTab, ...conversationTabs]; + }, [conversationIds, hasRedesign]); const linkConversationId = activeConversationId ?? conversationIds[0] ?? null; const conversationUrl = linkConversationId @@ -134,14 +151,25 @@ export function TraceAiConversations({ {tabItems.map(item => item.conversationId ? ( - + {hasRedesign ? ( + + ) : ( + + )} ) : ( @@ -240,6 +268,126 @@ function TraceConversationChat({ ); } +/** + * Redesigned transcript for the trace AI tab, gated behind + * `gen-ai-conversations-redesign`. Mirrors `ConversationViewContentNew`: the + * Seer Explorer-styled transcript on the left and the redesigned span detail + * panel on the right, opened only when the user selects a span. + */ +function TraceConversationTranscript({ + nodes, + nodeTraceMap, + isLoading, + error, + selectedSpanId, + onSelectSpan, +}: { + error: boolean; + isLoading: boolean; + nodeTraceMap: Map; + nodes: AITraceSpanNode[]; + onSelectSpan: (spanId: string) => void; + selectedSpanId: string | null; +}) { + const {selectedNode, handleSelectNode} = useConversationSelection({ + nodes, + selectedSpanId, + onSelectSpan, + isLoading, + // The redesign opens the span detail only when the user selects a span. + autoSelectDefaultNode: false, + }); + + const [detailState, setDetailState] = useState<{open: boolean; tab: DetailTab}>({ + open: true, + tab: 'input', + }); + + const handleSelectAndOpenDetail = useCallback( + (node: AITraceSpanNode) => { + setDetailState(prev => ({...prev, open: true})); + handleSelectNode(node); + }, + [handleSelectNode] + ); + + if (error) { + return {t('Failed to load conversation')}; + } + + if (!isLoading && nodes.length === 0) { + return ( + + {t('No chat messages in this portion of the conversation')} + + ); + } + + return ( + + + + + + + {isLoading ? ( + + ) : ( + + )} + + {detailState.open && selectedNode ? ( + + setDetailState(prev => ({...prev, tab}))} + onClose={() => setDetailState(prev => ({...prev, open: false}))} + /> + + ) : null} + + + + + + ); +} + const StyledTabs = styled(Tabs)` min-height: 0; flex: 1; From 143effd47b4ccc9b59078fa16b56569ff314717d Mon Sep 17 00:00:00 2001 From: Ognjen Bostjancic Date: Mon, 6 Jul 2026 12:33:49 +0200 Subject: [PATCH 2/7] ref(traces): Remove comments and hardcoded sizes from new transcript Replaces the copied fixed-width responsive layout (320px/430px hardcoded sizes) with ConversationSplitLayout, which is already used by the legacy path and handles sizing via a resizable split panel. Drops the manual open/close detail state in favour of ConversationSplitLayout's right-panel empty state. Removes the explanatory comments and the now-unused MessagesPanelSkeleton import. Refs TET-2604 --- .../traceDrawer/tabs/traceAiConversations.tsx | 118 ++++++------------ 1 file changed, 38 insertions(+), 80 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index d174b1948387..4d2eacbde9c7 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -21,10 +21,7 @@ import { } from 'sentry/views/explore/conversations/components/conversationSpanDetail'; import {ConversationAggregatesBar} from 'sentry/views/explore/conversations/components/conversationSummary'; import {MessagesPanel} from 'sentry/views/explore/conversations/components/messagesPanel'; -import { - MessagesPanelNew, - MessagesPanelSkeleton, -} from 'sentry/views/explore/conversations/components/messagesPanelNew'; +import {MessagesPanelNew} from 'sentry/views/explore/conversations/components/messagesPanelNew'; import {useConversation} from 'sentry/views/explore/conversations/hooks/useConversation'; import {useConversationSelection} from 'sentry/views/explore/conversations/hooks/useConversationSelection'; import {CONVERSATIONS_LANDING_SUB_PATH} from 'sentry/views/explore/conversations/settings'; @@ -48,8 +45,6 @@ export function TraceAiConversations({ }: TraceAiConversationsProps) { const organization = useOrganization(); const hasRedesign = hasGenAiConversationsRedesignFeature(organization); - // The redesign leads with the transcript, so default to the first - // conversation tab instead of the Timeline/Spans tab. const [activeSubTab, setActiveSubTab] = useState(() => hasRedesign && conversationIds.length > 0 ? `chat-${conversationIds[0]}` : 'spans' ); @@ -107,7 +102,6 @@ export function TraceAiConversations({ conversationId: id, })); - // The redesign leads with the transcript(s), then the timeline. return hasRedesign ? [...conversationTabs, spansTab] : [spansTab, ...conversationTabs]; @@ -268,12 +262,6 @@ function TraceConversationChat({ ); } -/** - * Redesigned transcript for the trace AI tab, gated behind - * `gen-ai-conversations-redesign`. Mirrors `ConversationViewContentNew`: the - * Seer Explorer-styled transcript on the left and the redesigned span detail - * panel on the right, opened only when the user selects a span. - */ function TraceConversationTranscript({ nodes, nodeTraceMap, @@ -294,28 +282,20 @@ function TraceConversationTranscript({ selectedSpanId, onSelectSpan, isLoading, - // The redesign opens the span detail only when the user selects a span. autoSelectDefaultNode: false, }); - const [detailState, setDetailState] = useState<{open: boolean; tab: DetailTab}>({ - open: true, - tab: 'input', - }); + const [detailTab, setDetailTab] = useState('input'); - const handleSelectAndOpenDetail = useCallback( - (node: AITraceSpanNode) => { - setDetailState(prev => ({...prev, open: true})); - handleSelectNode(node); - }, - [handleSelectNode] - ); + if (isLoading) { + return ; + } if (error) { return {t('Failed to load conversation')}; } - if (!isLoading && nodes.length === 0) { + if (nodes.length === 0) { return ( {t('No chat messages in this portion of the conversation')} @@ -325,65 +305,43 @@ function TraceConversationTranscript({ return ( - - - + - - {isLoading ? ( - - ) : ( - - )} - - {detailState.open && selectedNode ? ( - - setDetailState(prev => ({...prev, tab}))} - onClose={() => setDetailState(prev => ({...prev, open: false}))} - /> - - ) : null} + - - - + + } + right={ + selectedNode ? ( + + + + ) : ( + {t('Select a span to see its details')} + ) + } + /> ); } From e09df7f9d09256bbbbd8eccdf262dfebb0ffae66 Mon Sep 17 00:00:00 2001 From: Ognjen Bostjancic Date: Tue, 7 Jul 2026 10:53:44 +0200 Subject: [PATCH 3/7] ref(traces): Restore Timeline-first tab order in AI conversations Put Timeline tab before Transcript tab(s) and default to Timeline on mount, regardless of the redesign flag. Co-Authored-By: Claude Sonnet 4 --- .../traceDrawer/tabs/traceAiConversations.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 4d2eacbde9c7..83a87e6197e3 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -45,9 +45,7 @@ export function TraceAiConversations({ }: TraceAiConversationsProps) { const organization = useOrganization(); const hasRedesign = hasGenAiConversationsRedesignFeature(organization); - const [activeSubTab, setActiveSubTab] = useState(() => - hasRedesign && conversationIds.length > 0 ? `chat-${conversationIds[0]}` : 'spans' - ); + const [activeSubTab, setActiveSubTab] = useState('spans'); const [selectedSpanId, setSelectedSpanId] = useState(null); const handleTabChange = useCallback((key: Key) => { @@ -102,9 +100,7 @@ export function TraceAiConversations({ conversationId: id, })); - return hasRedesign - ? [...conversationTabs, spansTab] - : [spansTab, ...conversationTabs]; + return [spansTab, ...conversationTabs]; }, [conversationIds, hasRedesign]); const linkConversationId = activeConversationId ?? conversationIds[0] ?? null; From 816c8f46911f5deedb942729f00449db6b27d721 Mon Sep 17 00:00:00 2001 From: Ognjen Bostjancic Date: Tue, 7 Jul 2026 11:03:33 +0200 Subject: [PATCH 4/7] ref(traces): Remove redundant Flex wrapper around ConversationSpanDetail ConversationSpanDetail already sets flex=1, minHeight=0, and background=primary on its root Stack, so the outer Flex was redundant. Co-Authored-By: Claude Sonnet 4 --- .../traceDrawer/tabs/traceAiConversations.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 83a87e6197e3..5b524853d7ee 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -324,15 +324,13 @@ function TraceConversationTranscript({ } right={ selectedNode ? ( - - - + ) : ( {t('Select a span to see its details')} ) From 4e6de298f00213b39bf611c43c119f56c99ebce7 Mon Sep 17 00:00:00 2001 From: Ognjen Bostjancic Date: Tue, 7 Jul 2026 11:10:33 +0200 Subject: [PATCH 5/7] ref(traces): Use MessagesPanelSkeleton for redesigned transcript loading state The transcript was still using the old ConversationViewSkeleton. Switch to MessagesPanelSkeleton which mirrors the conversation bubble layout of MessagesPanelNew. Co-Authored-By: Claude Sonnet 4 --- .../traceDrawer/tabs/traceAiConversations.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 5b524853d7ee..eff7bd203cf9 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -21,7 +21,10 @@ import { } from 'sentry/views/explore/conversations/components/conversationSpanDetail'; import {ConversationAggregatesBar} from 'sentry/views/explore/conversations/components/conversationSummary'; import {MessagesPanel} from 'sentry/views/explore/conversations/components/messagesPanel'; -import {MessagesPanelNew} from 'sentry/views/explore/conversations/components/messagesPanelNew'; +import { + MessagesPanelNew, + MessagesPanelSkeleton, +} from 'sentry/views/explore/conversations/components/messagesPanelNew'; import {useConversation} from 'sentry/views/explore/conversations/hooks/useConversation'; import {useConversationSelection} from 'sentry/views/explore/conversations/hooks/useConversationSelection'; import {CONVERSATIONS_LANDING_SUB_PATH} from 'sentry/views/explore/conversations/settings'; @@ -284,7 +287,7 @@ function TraceConversationTranscript({ const [detailTab, setDetailTab] = useState('input'); if (isLoading) { - return ; + return ; } if (error) { From dfff39300ff299dc9744448c562854a3f79dacf7 Mon Sep 17 00:00:00 2001 From: Ogi Date: Tue, 7 Jul 2026 12:05:38 +0200 Subject: [PATCH 6/7] Update static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx Co-authored-by: Priscila Oliveira --- .../traceDrawer/tabs/traceAiConversations.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index eff7bd203cf9..4988f4ee99c7 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -308,14 +308,7 @@ function TraceConversationTranscript({ sizeStorageKey="trace-conversation-split-size" left={ - + Date: Tue, 7 Jul 2026 10:06:56 +0000 Subject: [PATCH 7/7] :hammer_and_wrench: apply pre-commit fixes --- .../traceDrawer/tabs/traceAiConversations.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx index 4988f4ee99c7..297c1b6c9402 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/tabs/traceAiConversations.tsx @@ -1,5 +1,6 @@ import {type Key, useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; +import * as qs from 'query-string'; import {LinkButton} from '@sentry/scraps/button'; import {Container, Flex} from '@sentry/scraps/layout'; @@ -109,7 +110,12 @@ export function TraceAiConversations({ const linkConversationId = activeConversationId ?? conversationIds[0] ?? null; const conversationUrl = linkConversationId ? normalizeUrl( - `/organizations/${organization.slug}/explore/${CONVERSATIONS_LANDING_SUB_PATH}/${linkConversationId}/${selectedSpanId && activeConversationId ? `?spanId=${encodeURIComponent(selectedSpanId)}` : ''}` + `/organizations/${organization.slug}/explore/${CONVERSATIONS_LANDING_SUB_PATH}/${linkConversationId}/?${qs.stringify( + { + referrer: 'trace-view', + ...(selectedSpanId && activeConversationId ? {spanId: selectedSpanId} : {}), + } + )}` ) : null; @@ -308,7 +314,7 @@ function TraceConversationTranscript({ sizeStorageKey="trace-conversation-split-size" left={ - +