Compare commits

..

9 Commits

Author SHA1 Message Date
Gaurav Tewari
cefcc01414 feat(llm-observability): open traces on the thread tab 2026-10-21 21:00:11 +05:30
Gaurav Tewari
711732643e feat(trace-details): thread tab view 2026-10-21 21:00:11 +05:30
Gaurav Tewari
7716218ecf feat(trace-details): show AI messages in span details 2026-10-21 21:00:11 +05:30
Gaurav Tewari
4178e17b4f feat(trace-details): add AI message model and renderer 2026-10-21 21:00:11 +05:30
Gaurav Tewari
6e2bd63db9 fix(pretty-view): skip the actions menu when it has no items 2026-10-21 21:00:11 +05:30
Gaurav Tewari
28d68c28bc refactor: review changes 2026-10-21 17:07:54 +05:30
Gaurav Tewari
09b8d764c0 feat(trace-details): overview and thread tabs 2026-10-21 16:30:28 +05:30
Gaurav Tewari
3ae12a4eaf refactor(trace-details): lift header and store sync into page shell 2026-10-21 16:29:36 +05:30
Gaurav Tewari
077ce9529d refactor(trace-details): read search params at call time
The router's search goes stale once nuqs writes to the URL,
so span selection and links read the live URL instead.
2026-10-21 16:28:40 +05:30
3 changed files with 17 additions and 10 deletions

View File

@@ -68,10 +68,13 @@ function TraceDetailsHeader({
const isOverview = tab === TraceDetailsTab.Overview;
// Overview-only panels must not linger over another tab.
// The filter query lives in Filters' local state, which unmounts with the
// Overview tab; drop its result too so both start empty on return.
useEffect(() => {
setIsPreviewFieldsOpen(false);
setIsAnalyticsOpen(false);
}, [tab]);
onFilteredSpansChange([], false);
}, [tab, onFilteredSpansChange]);
const previewFields = useTraceStore((s) => s.previewFields);
const setPreviewFields = useTraceStore((s) => s.setPreviewFields);

View File

@@ -383,6 +383,16 @@ describe('TraceDetailsHeader – tabs', () => {
expect(screen.getByText(metadataText)).toBeInTheDocument();
});
it('clears the filter result when switching to the Thread tab', async () => {
const user = userEvent.setup({ delay: null });
renderHeader();
baseProps.onFilteredSpansChange.mockClear();
await user.click(screen.getByTestId('trace-details-tab-thread'));
expect(baseProps.onFilteredSpansChange).toHaveBeenCalledWith([], false);
});
it('closes the Analytics panel when switching to the Thread tab', async () => {
const user = userEvent.setup({ delay: null });
renderHeader();

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { TraceDetailsTab } from './constants';
import { useTraceDetailsTab } from './hooks/useTraceDetailsTab';
@@ -13,6 +13,8 @@ function TraceDetailsV3(): JSX.Element {
const [filteredSpanIds, setFilteredSpanIds] = useState<string[]>([]);
const [isFilterActive, setIsFilterActive] = useState(false);
const [hasTraceData, setHasTraceData] = useState(false);
// Tracks which traceId the load event already fired for, so navigating
// between traces (the route component stays mounted) re-fires it once each.
const dataLoadedFiredForRef = useRef<string>('');
const handleFilteredSpansChange = useCallback(
@@ -23,14 +25,6 @@ function TraceDetailsV3(): JSX.Element {
[],
);
// The filter query lives in Filters' local state, which unmounts with the
// Overview tab; drop its result too so both start empty on return.
useEffect(() => {
if (!isOverview) {
handleFilteredSpansChange([], false);
}
}, [isOverview, handleFilteredSpansChange]);
return (
<TraceDetailsWrapper>
<TraceDetailsHeader