From 1265920adc48db8861fb26aaaab0804b5a8028ff Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:12:18 +0800 Subject: [PATCH 1/8] fix(web): source-alias schema form in frontend build --- apps/web/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 7043de4911..1e8eb07210 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ { find: /^@deepseek-ai\/dsh-client-web-react$/, replacement: src('../../packages/client/web-react/src/index.ts') }, { find: /^@deepseek-ai\/dsh-client-ui-slots$/, replacement: src('../../packages/client/ui-slots/src/index.ts') }, { find: /^@deepseek-ai\/dsh-client-ui-primitives$/, replacement: src('../../packages/client/ui-primitives/src/index.ts') }, + { find: /^@deepseek-ai\/dsh-client-schema-form$/, replacement: src('../../packages/client/schema-form/src/index.ts') }, { find: /^@deepseek-ai\/dsh-client-modules\/client$/, replacement: src('../../packages/client/modules/src/client/index.ts') }, ], }, From 668ba2bfe216f316f375771424d320750872496f Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:12:34 +0800 Subject: [PATCH 2/8] fix(trajectory): stabilize timeline pane layering --- .../client/ui-trajectory/src/client/TrajectoryTable.module.css | 1 + .../ui-trajectory/src/client/TrajectoryTimeline.module.css | 2 +- packages/client/ui-trajectory/src/client/views.module.css | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css index c7b7948845..bedaa2aec4 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css @@ -828,6 +828,7 @@ flex: 1; min-height: 0; overflow: auto; + scrollbar-gutter: stable; } .detailBodySummary { diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css b/packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css index 402387da48..1ca5ab2627 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css +++ b/packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css @@ -1,6 +1,6 @@ .root { position: relative; - z-index: 0; + z-index: 1; isolation: isolate; flex: none; border-bottom: 1px solid var(--dsw-alias-border-l2); diff --git a/packages/client/ui-trajectory/src/client/views.module.css b/packages/client/ui-trajectory/src/client/views.module.css index 3dd80c077a..326ac41a99 100644 --- a/packages/client/ui-trajectory/src/client/views.module.css +++ b/packages/client/ui-trajectory/src/client/views.module.css @@ -27,6 +27,9 @@ } .ledger { + position: relative; + z-index: 0; + isolation: isolate; display: flex; flex: 1; min-height: 0; From 4624c476de274e52be86f3d1dc5eec66c32587cb Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:12:43 +0800 Subject: [PATCH 3/8] fix(trajectory): persist idle-compressed duration mode --- .../src/client/TrajectoryToolbar.tsx | 2 +- .../src/client/TrajectoryView.tsx | 26 +++++++- .../ui-trajectory/src/client/timeline.ts | 58 ++++++++++------- .../client/ui-trajectory/tests/views.spec.tsx | 65 +++++++++++++++++++ 4 files changed, 124 insertions(+), 27 deletions(-) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx index 09d213c36e..50bb85ad70 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx @@ -8,7 +8,7 @@ export interface TrajectoryToolbarProps { actualDuration: boolean /** Select recorded-duration or equal-width blocks. */ onActualDurationChange: (actualDuration: boolean) => void - /** Whether recorded timing retains idle gaps between user turns. */ + /** Whether recorded timing retains idle gaps between operations. */ actualTime: boolean /** Select complete wall-clock timing or idle-compressed timing. */ onActualTimeChange: (actualTime: boolean) => void diff --git a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx index e09b03e51c..420c4ac5e9 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx @@ -26,6 +26,29 @@ import { import css from './views.module.css' const EMPTY_IDS: ReadonlySet = new Set() +const DURATION_STORAGE_KEY = 'dsh.trajectory.duration' + +/** Restore the browser-wide duration preference; absent or unreadable storage defaults off. */ +function restoreActualDuration(): boolean { + if (typeof localStorage === 'undefined') return false + try { + return localStorage.getItem(DURATION_STORAGE_KEY) === 'true' + } catch { + // Storage access can throw in privacy mode; the default remains usable. + return false + } +} + +/** Persist the browser-wide duration preference without making storage availability fatal. */ +function persistActualDuration(actualDuration: boolean): void { + if (typeof localStorage === 'undefined') return + try { + localStorage.setItem(DURATION_STORAGE_KEY, String(actualDuration)) + } catch { + // Storage access can throw in privacy mode or at quota; this mount still + // keeps the selected preference in React state. + } +} /** Session-history paging needed by the event-complete trajectory view. */ export interface TrajectoryViewInjected { @@ -143,7 +166,7 @@ export function TrajectoryView({ branchId: number range: TrajectoryTimeRange } | null>(null) - const [actualDuration, setActualDuration] = useState(false) + const [actualDuration, setActualDuration] = useState(restoreActualDuration) const [actualTime, setActualTime] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [selectedTimelineIndex, setSelectedTimelineIndex] = useState(null) @@ -457,6 +480,7 @@ export function TrajectoryView({ { + persistActualDuration(nextActualDuration) setActualDuration(nextActualDuration) setTimelineSelection(null) }} diff --git a/packages/client/ui-trajectory/src/client/timeline.ts b/packages/client/ui-trajectory/src/client/timeline.ts index 88682b04bb..b91ec51849 100644 --- a/packages/client/ui-trajectory/src/client/timeline.ts +++ b/packages/client/ui-trajectory/src/client/timeline.ts @@ -114,14 +114,9 @@ export function deriveTrajectoryTimeline( function deriveTimedTimeline( turns: readonly TrajectoryTurnModel[], actualDuration: boolean, - removeUserIdle: boolean, + compressIdle: boolean, ): TrajectoryTimelineModel | null { - const spans: TrajectoryTimelineSpan[] = [] - const turnBoundaries: TrajectoryTimelineTurnBoundary[] = [] - let removedUserIdle = 0 - let previousTurnEnd: number | null = null - - for (const turn of turns) { + const timedTurns = turns.flatMap((turn) => { const rawSpans = turn.groups.flatMap(group => group.cells.flatMap((cell): TrajectoryTimelineSpan[] => { if (cell.requestOnly === true) return [] @@ -138,28 +133,41 @@ function deriveTimedTimeline( }] }), ) - if (rawSpans.length === 0) continue + return rawSpans.length === 0 ? [] : [{ turn: turn.turn, rawSpans }] + }) + const rawSpans = timedTurns.flatMap(turn => turn.rawSpans) + if (rawSpans.length === 0) return null - const turnStart = Math.min(...rawSpans.map(span => span.start)) - const turnEnd = Math.max(...rawSpans.map(span => span.end)) - if (removeUserIdle && previousTurnEnd !== null) { - removedUserIdle += Math.max(0, turnStart - previousTurnEnd) + const removedIdleBySpan = new Map() + let removedIdle = 0 + let coveredUntil: number | null = null + for (const span of [...rawSpans].sort((left, right) => + left.start - right.start || left.end - right.end)) { + if (compressIdle && coveredUntil !== null && span.start > coveredUntil) { + removedIdle += span.start - coveredUntil } - spans.push(...rawSpans.map(span => ({ - ...span, - start: span.start - removedUserIdle, - end: (actualDuration ? span.end : span.start) - removedUserIdle, - }))) - turnBoundaries.push({ - turn: turn.turn, - time: turnStart - removedUserIdle, - }) - previousTurnEnd = previousTurnEnd === null - ? turnEnd - : Math.max(previousTurnEnd, turnEnd) + removedIdleBySpan.set(span, removedIdle) + coveredUntil = coveredUntil === null ? span.end : Math.max(coveredUntil, span.end) + } + + const spans: TrajectoryTimelineSpan[] = [] + const turnBoundaries: TrajectoryTimelineTurnBoundary[] = [] + for (const turn of timedTurns) { + const projected = turn.rawSpans.map((span): TrajectoryTimelineSpan => { + const offset = removedIdleBySpan.get(span) ?? 0 + return { + ...span, + start: span.start - offset, + end: (actualDuration ? span.end : span.start) - offset, + } + }) + spans.push(...projected) + turnBoundaries.push({ + turn: turn.turn, + time: Math.min(...projected.map(span => span.start)), + }) } - if (spans.length === 0) return null return { start: Math.min(...spans.map(span => span.start)), end: Math.max(...spans.map(span => span.end)), diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index ebb5f28bb8..e2d6560f4e 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -560,6 +560,53 @@ describe('timeline projection', () => { }) }) + it('compresses every idle gap in duration mode while actual mode retains wall time', () => { + const separatedTurns = [ + { + turn: 1, + groups: [{ + title: 'Step 1', + cells: [ + { index: 1, kind: 'message', text: 'first', startedAt: 1_000, timeSeconds: 1 }, + { index: 2, kind: 'tool', text: 'within-turn gap', startedAt: 4_000, timeSeconds: 1 }, + ], + }], + }, + { + turn: 2, + groups: [{ + title: 'Step 1', + cells: [ + { index: 3, kind: 'message', text: 'after user idle', startedAt: 40_000, timeSeconds: 1 }, + ], + }], + }, + ] satisfies readonly TrajectoryTurnModel[] + + expect(deriveTrajectoryTimeline(separatedTurns, 'duration')).toMatchObject({ + start: 1_000, + end: 4_000, + spans: [ + { index: 1, start: 1_000, end: 2_000 }, + { index: 2, start: 2_000, end: 3_000 }, + { index: 3, start: 3_000, end: 4_000 }, + ], + turnBoundaries: [ + { turn: 1, time: 1_000 }, + { turn: 2, time: 3_000 }, + ], + }) + expect(deriveTrajectoryTimeline(separatedTurns, 'actual')).toMatchObject({ + start: 1_000, + end: 41_000, + spans: [ + { index: 1, start: 1_000, end: 2_000 }, + { index: 2, start: 4_000, end: 5_000 }, + { index: 3, start: 40_000, end: 41_000 }, + ], + }) + }) + it('empty inputs produce no model and the standalone view reports its empty form', () => { expect(deriveTrajectoryTimeline([])).toBeNull() render(createElement( @@ -575,6 +622,24 @@ describe('timeline projection', () => { }) describe('TrajectoryView branches', () => { + it('persists the duration preference across trajectory view mounts', () => { + const props = { + ...standaloneProps(NODES), + ...standaloneHistory(historySnapshot(NODES)), + } + const first = render() + const duration = screen.getByRole('button', { name: 'Use actual duration' }) + + expect(duration.getAttribute('aria-pressed')).toBe('false') + fireEvent.click(duration) + expect(localStorage.getItem('dsh.trajectory.duration')).toBe('true') + first.unmount() + + render() + expect(screen.getByRole('button', { name: 'Use actual duration' }).getAttribute('aria-pressed')) + .toBe('true') + }) + it('renders only the selected rewind branch while retaining session-global requests', () => { const retained = { kind: 'user', From 5255eb698296744a07cc63cf42118b029b426066 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:28:01 +0800 Subject: [PATCH 4/8] fix(trajectory): contain timeline wheel zoom --- .../src/client/TrajectoryTimeline.tsx | 67 +++++++++++-------- .../client/ui-trajectory/tests/views.spec.tsx | 22 ++++++ 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTimeline.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTimeline.tsx index 89e3b8bb2b..704000526e 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTimeline.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryTimeline.tsx @@ -2,7 +2,7 @@ import { memo, useEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, - type PointerEvent, type WheelEvent, + type PointerEvent, } from 'react' import type { TrajectoryTurnModel } from './layout.ts' import { @@ -117,6 +117,8 @@ export const TrajectoryTimeline = memo(function TrajectoryTimeline({ anchorClientX: number recordIndex: number | null } | null>(null) + const rootRef = useRef(null) + const trackRef = useRef(null) const [draft, setDraft] = useState(null) const [hover, setHover] = useState(null) const [viewport, setViewport] = useState(null) @@ -189,10 +191,42 @@ export const TrajectoryTimeline = memo(function TrajectoryTimeline({ : rangeFraction(draft, domainStart, domainDuration) const visibleRange = draftFraction ?? committed const activeRange = draft ?? range + useEffect(() => { + const root = rootRef.current + if (root === null) return + const onWheel = (event: globalThis.WheelEvent): void => { + event.preventDefault() + const track = trackRef.current + if (track === null || model === null) return + setAnimateViewport(false) + const rect = track.getBoundingClientRect() + const anchorFraction = + clampFraction((event.clientX - rect.left) / Math.max(1, rect.width)) + const nextDuration = Math.min( + fullDuration, + Math.max( + Math.min(mode === 'sequence' ? MINIMUM_ZOOM_OPERATIONS : 20, fullDuration), + domainDuration * Math.exp(event.deltaY * 0.0015), + ), + ) + if (nextDuration >= fullDuration * 0.999) { + setViewport(null) + return + } + const anchorTime = domainStart + anchorFraction * domainDuration + const nextStart = Math.min( + Math.max(anchorTime - anchorFraction * nextDuration, model.start), + model.end - nextDuration, + ) + setViewport({ start: nextStart, end: nextStart + nextDuration }) + } + root.addEventListener('wheel', onWheel, { passive: false }) + return () => { root.removeEventListener('wheel', onWheel) } + }, [domainDuration, domainStart, fullDuration, mode, model]) if (model === null) { return ( -
+
@@ -339,36 +373,12 @@ export const TrajectoryTimeline = memo(function TrajectoryTimeline({ setHover(null) } - const onWheel = (event: WheelEvent) => { - event.preventDefault() - setAnimateViewport(false) - const rect = event.currentTarget.getBoundingClientRect() - const anchorFraction = - clampFraction((event.clientX - rect.left) / Math.max(1, rect.width)) - const nextDuration = Math.min( - fullDuration, - Math.max( - Math.min(mode === 'sequence' ? MINIMUM_ZOOM_OPERATIONS : 20, fullDuration), - domainDuration * Math.exp(event.deltaY * 0.0015), - ), - ) - if (nextDuration >= fullDuration * 0.999) { - setViewport(null) - return - } - const anchorTime = domainStart + anchorFraction * domainDuration - const nextStart = Math.min( - Math.max(anchorTime - anchorFraction * nextDuration, model.start), - model.end - nextDuration, - ) - setViewport({ start: nextStart, end: nextStart + nextDuration }) - } - return ( -
+
{ event.preventDefault() setAnimateViewport(false) diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index 2d3cf02e8a..ff390d45ea 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -391,6 +391,28 @@ describe('timeline projection', () => { }], }] satisfies readonly TrajectoryTurnModel[] + it('cancels native scrolling across the timeline while zooming', () => { + render( + , + ) + const plot = screen.getByLabelText('Timeline overview; drag horizontally to focus events') + vi.spyOn(plot, 'getBoundingClientRect').mockReturnValue({ + x: 44, y: 0, left: 44, top: 0, right: 144, bottom: 50, width: 100, height: 50, + toJSON: () => ({}), + }) + + expect(fireEvent.wheel(plot, { clientX: 94, deltaY: -100 })).toBe(false) + expect(fireEvent.wheel(screen.getByText('Input'), { + clientX: 20, + deltaY: -100, + })).toBe(false) + }) + it('pans the zoomed viewport only far enough to reveal a newly selected record', async () => { const onRangeChange = vi.fn() const view = render( From 72eb82858a7d810b3b748d9e97614e56611b6c54 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:38:49 +0800 Subject: [PATCH 5/8] fix(trajectory): refine timeline drag and tooltips --- .../src/client/TrajectoryTable.module.css | 7 +++++++ .../ui-trajectory/src/client/TrajectoryTable.tsx | 5 ++++- .../src/client/TrajectoryTimeline.tsx | 16 +++++++++++----- .../client/ui-trajectory/tests/table.spec.tsx | 4 +++- .../client/ui-trajectory/tests/views.spec.tsx | 15 ++++++++++++++- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css index bedaa2aec4..92b78a6661 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css @@ -321,6 +321,13 @@ width: 76px; } +.kindSlot :global([role='tooltip']) { + border: 1px solid var(--dsw-alias-border-l2); + background: var(--dsw-alias-bg-layer-2); + box-shadow: var(--dsw-shadow-lv2); + color: var(--dsw-alias-label-primary); +} + .content { padding-left: 4px !important; color: var(--dsw-alias-label-primary); diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx index 5a78e95a40..2ab44d2e55 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx @@ -1868,7 +1868,10 @@ export function TrajectoryTable({ - + { diff --git a/packages/client/ui-trajectory/tests/table.spec.tsx b/packages/client/ui-trajectory/tests/table.spec.tsx index d9959ae30c..f02eb6e0cc 100644 --- a/packages/client/ui-trajectory/tests/table.spec.tsx +++ b/packages/client/ui-trajectory/tests/table.spec.tsx @@ -184,7 +184,9 @@ describe('TrajectoryTable', () => { expect(toolTag?.querySelector('[data-role-icon="wrench"]')).toBeTruthy() fireEvent.mouseEnter(toolTag as HTMLElement) - expect(screen.getByRole('tooltip').textContent).toBe('TOOL') + const tooltip = screen.getByRole('tooltip') + expect(tooltip.textContent).toBe('TOOL') + expect(tooltip.getAttribute('data-side')).toBe('right') fireEvent.mouseLeave(toolTag as HTMLElement) expect(screen.queryByRole('tooltip')).toBeNull() }) diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index ff390d45ea..2ab119a5d9 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -465,7 +465,7 @@ describe('timeline projection', () => { it('auto-pans a zoomed viewport while a range drag pushes against an edge', () => { const onRangeChange = vi.fn() - render( + const view = render( { for (let index = 0; index < 24; index++) { fireEvent.pointerMove(plot, { clientX: 99, pointerId: 1 }) } + const draftSelection = view.container.querySelectorAll( + '[data-dragging="true"]', + ) + expect(draftSelection).toHaveLength(2) + for (const overlay of draftSelection) { + expect(Number.parseFloat( + overlay.style.getPropertyValue('--trajectory-selection-left'), + )).toBeLessThan(0) + } fireEvent.pointerUp(plot, { clientX: 99, pointerId: 1 }) const selectedRange = onRangeChange.mock.calls.at(-1)?.[0] as | { start: number; end: number } | undefined + const fullRange = deriveTrajectoryTimeline(longTurns) expect(selectedRange).toBeDefined() + expect(fullRange).not.toBeNull() expect((selectedRange?.end ?? 0) - (selectedRange?.start ?? 0)).toBeGreaterThan(4) + expect(selectedRange?.start).toBeGreaterThanOrEqual(fullRange?.start ?? 0) + expect(selectedRange?.end).toBeLessThanOrEqual(fullRange?.end ?? 0) }) it('uses equal-width operation slots and stable semantic lanes', () => { From 4b8316dcd5963d25e1af44a759d6607fff3558d1 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Fri, 31 Jul 2026 11:39:37 +0800 Subject: [PATCH 6/8] fix(trajectory): keep fold controls available --- .../ui-trajectory/src/client/TrajectoryToolbar.module.css | 7 +------ .../client/ui-trajectory/src/client/TrajectoryToolbar.tsx | 8 -------- .../client/ui-trajectory/src/client/TrajectoryView.tsx | 2 -- packages/client/ui-trajectory/tests/views.spec.tsx | 6 ++++++ 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css index 6e3dd27916..aa38650411 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css +++ b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css @@ -149,7 +149,7 @@ font: var(--dsw-font-xxs-12); } -.action:hover:not(:disabled) { +.action:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); } @@ -159,11 +159,6 @@ outline-offset: 1px; } -.action:disabled { - color: var(--dsw-alias-label-dimmed); - cursor: not-allowed; -} - .actionIcon { color: var(--dsw-alias-label-tertiary); font: 14px/14px var(--ds-font-family-code); diff --git a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx index 50bb85ad70..5db9f56e80 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryToolbar.tsx @@ -12,14 +12,10 @@ export interface TrajectoryToolbarProps { actualTime: boolean /** Select complete wall-clock timing or idle-compressed timing. */ onActualTimeChange: (actualTime: boolean) => void - /** Number of turns containing more than one row. */ - collapsibleTurns: number /** Whether every collapsible turn is currently folded. */ allTurnsCollapsed: boolean /** Fold or expand every collapsible turn. */ onToggleAllTurns: () => void - /** Number of assistant messages followed by tool calls. */ - collapsibleAssistants: number /** Whether every collapsible assistant's tool calls are currently folded. */ allAssistantsCollapsed: boolean /** Fold or expand tool calls under every collapsible assistant. */ @@ -40,10 +36,8 @@ export function TrajectoryToolbar({ onActualDurationChange, actualTime, onActualTimeChange, - collapsibleTurns, allTurnsCollapsed, onToggleAllTurns, - collapsibleAssistants, allAssistantsCollapsed, onToggleAllAssistants, searchQuery, @@ -91,7 +85,6 @@ export function TrajectoryToolbar({ aria-label={allTurnsCollapsed ? 'Expand turns' : 'Collapse turns'} aria-pressed={allTurnsCollapsed} title={allTurnsCollapsed ? 'Expand turns' : 'Collapse turns'} - disabled={collapsibleTurns === 0} onClick={onToggleAllTurns} >