diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
index 36c81fc2ad..b2ad410b5a 100644
--- a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
+++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
@@ -71,6 +71,7 @@ interface ToolCallTextParts {
interface SelectedRequest {
turn: number | null
+ section: number
number: number
group: string
}
@@ -1453,6 +1454,7 @@ export function TrajectoryTable({
? []
: allRecords.filter(record =>
record.turn === selectedRequest.turn
+ && record.section === selectedRequest.section
&& record.group === selectedRequest.group,
)
const selectedRequestAssistant = selectedRequestRecords.find(
@@ -1505,6 +1507,7 @@ export function TrajectoryTable({
selectedRequestInfo?.cumulativeUsage ?? selectedRequestUsage
const selectedRequestOptions = selectedRequestInfo?.requestConfig
const activeTurn = selectedRequest === null ? selected?.turn : selectedRequest.turn
+ const activeSection = selectedRequest === null ? selected?.section : selectedRequest.section
const selectedTabs = selectedRequest !== null
? REQUEST_TABS.filter(tab => tab.id !== 'options' || selectedRequestOptions !== undefined)
: selected === undefined ? [] : detailTabs(selected)
@@ -1520,6 +1523,7 @@ export function TrajectoryTable({
selected !== undefined && selectedAssistantRequest !== undefined
? {
turn: selected.turn,
+ section: selected.section,
number: selectedAssistantRequest,
group: selected.group,
}
@@ -1629,7 +1633,11 @@ export function TrajectoryTable({
: `Request #${request}${requestInfo?.purpose === 'compaction' ? ' · Compaction' : ''}`
const requestSelected = request !== undefined
&& selectedRequest?.turn === record.turn
+ && selectedRequest.section === record.section
&& selectedRequest.number === request
+ const sectionActive = record.turn === null
+ ? activeSection === record.section
+ : activeTurn === record.turn
return (
diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx
index d81092c4b4..e10869ea8d 100644
--- a/packages/client/ui-trajectory/tests/views.spec.tsx
+++ b/packages/client/ui-trajectory/tests/views.spec.tsx
@@ -312,6 +312,70 @@ describe('tab switching in ConversationRoot', () => {
expect(view.container.textContent).not.toContain('Turn null')
})
+ it('activates only the selected standalone compaction section', async () => {
+ const nodes = [
+ { kind: 'user', seq: 1, time: 1_000, content: [], source: null },
+ {
+ kind: 'assistant', seq: 2, time: 2_000, turn: 1, step: 1,
+ blocks: [{ kind: 'text', text: 'before first compaction' }],
+ },
+ { kind: 'user', seq: 5, time: 5_000, content: [], source: null },
+ {
+ kind: 'assistant', seq: 6, time: 6_000, turn: 2, step: 1,
+ blocks: [{ kind: 'text', text: 'between compactions' }],
+ },
+ { kind: 'user', seq: 9, time: 9_000, content: [], source: null },
+ {
+ kind: 'assistant', seq: 10, time: 10_000, turn: 3, step: 1,
+ blocks: [{ kind: 'text', text: 'after second compaction' }],
+ },
+ ] as unknown as ConversationSnapshot['nodes']
+ const compactions: RequestView[] = [
+ {
+ purpose: 'compaction',
+ startSeq: 3,
+ turn: null,
+ step: 0,
+ startedAt: 3_000,
+ completedAt: 4_000,
+ status: 'complete',
+ summary: [{ type: 'text', text: 'first standalone summary' }],
+ },
+ {
+ purpose: 'compaction',
+ startSeq: 7,
+ turn: null,
+ step: 0,
+ startedAt: 7_000,
+ completedAt: 8_000,
+ status: 'complete',
+ summary: [{ type: 'text', text: 'second standalone summary' }],
+ },
+ ]
+ const b = await bench(historySnapshot(nodes, { requests: compactions }))
+ mount(b.slots, nodes)
+ fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' }))
+
+ const firstRequest = screen.getByRole('button', { name: 'Request #2 · Compaction' })
+ const secondRequest = screen.getByRole('button', { name: 'Request #4 · Compaction' })
+ const firstSection = firstRequest.closest('tr')?.querySelector('span')
+ const secondSection = secondRequest.closest('tr')?.querySelector('span')
+ expect(firstSection?.textContent).toBe('Between turns')
+ expect(secondSection?.textContent).toBe('Between turns')
+
+ fireEvent.click(firstRequest)
+ expect(firstSection?.className).toMatch(/turnLabelActive/)
+ expect(secondSection?.className).not.toMatch(/turnLabelActive/)
+ expect(screen.getByText('Request #2')).toBeTruthy()
+ expect(screen.getByText('Compaction · Between turns')).toBeTruthy()
+
+ fireEvent.click(secondRequest)
+ expect(firstSection?.className).not.toMatch(/turnLabelActive/)
+ expect(secondSection?.className).toMatch(/turnLabelActive/)
+ expect(screen.getByText('Request #4')).toBeTruthy()
+ expect(screen.getByText('Compaction · Between turns')).toBeTruthy()
+ })
+
it('dragging the overview focuses overlapping records without filtering the ledger', async () => {
const b = await bench()
mount(b.slots)