diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css index 3e0e96b838..2d1f415340 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.module.css @@ -16,6 +16,7 @@ flex: 1; min-width: 0; overflow: auto; + container: trajectory-table / inline-size; } .table { @@ -279,6 +280,10 @@ white-space: nowrap; } +.turnLabelCompact { + display: none; +} + .turnLabelActive { color: color-mix( in srgb, @@ -325,11 +330,66 @@ user-select: none; } +.kindTagIcon { + display: none; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; +} + +.kindTagLabel { + display: inline; +} + .table .kindSlot .message { justify-content: center; width: 100%; } +@container trajectory-table (max-width: 620px) { + .eventColumn { + width: 50px; + } + + .event { + padding-right: 3px !important; + padding-left: 28px !important; + } + + .requestBoundaryControl { + left: 6px; + } + + .kindSlot { + width: 19px; + } + + .kindTag, + .table .kindSlot .message { + justify-content: center; + width: 19px; + padding-right: 0; + padding-left: 0; + } + + .kindTagIcon { + display: inline-flex; + } + + .kindTagLabel { + display: none; + } + + .turnLabelFull { + display: none; + } + + .turnLabelCompact { + display: inline; + } +} + .user { color: var(--dsw-alias-state-business-primary); background: var(--dsw-alias-state-business-tertiary); diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx index 82769b3d6f..4c30f770d1 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx @@ -3,7 +3,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { CSSProperties, ReactNode } from 'react' import { - extractMarkdownPlainText, IconChevronRightOutline14, JsonTree, MarkdownText, + extractMarkdownPlainText, + IconChevronRightOutline14, + IconSettingsOutline16, + IconSparkle16, + IconUserOutline16, + JsonTree, + MarkdownText, + Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' import { structuredPatch } from 'diff' import type { @@ -26,6 +33,77 @@ const KIND_LABEL: Record = { subtool: 'SUBTOOL', } +function ToolWrenchIcon(): ReactNode { + return ( + + ) +} + +function InformationIcon(): ReactNode { + return ( + + ) +} + +function CompactedIcon(): ReactNode { + return ( + + ) +} + +const KIND_ICON: Record = { + system: , + user: , + context: , + compacted: , + message: , + tool: , + subtool: , +} + interface TableRecord { turn: number group: string @@ -1726,8 +1804,14 @@ export function TrajectoryTable({ className={activeTurn === record.turn ? `${css.turnLabel} ${css.turnLabelActive}` : css.turnLabel} + aria-label={`Turn ${record.turn}`} > - Turn {record.turn} + + )}
@@ -1735,24 +1819,33 @@ export function TrajectoryTable({ - - {KIND_LABEL[record.cell.kind]} - + + + + + {KIND_LABEL[record.cell.kind]} + + + )}
diff --git a/packages/client/ui-trajectory/tests/table.spec.tsx b/packages/client/ui-trajectory/tests/table.spec.tsx index fb21ca45b7..64e8a23f5b 100644 --- a/packages/client/ui-trajectory/tests/table.spec.tsx +++ b/packages/client/ui-trajectory/tests/table.spec.tsx @@ -148,6 +148,49 @@ describe('TrajectoryTable', () => { expect(screen.getByText('ToolError: non_zero_exit')).toBeTruthy() }) + it('renders responsive role icons with a custom tooltip', () => { + const view = render() + const toolTag = view.container.querySelector('[data-role-kind="tool"]') + + expect(toolTag).not.toBeNull() + expect(toolTag?.getAttribute('title')).toBeNull() + expect(toolTag?.querySelector('[data-role-icon="wrench"]')).toBeTruthy() + + fireEvent.mouseEnter(toolTag as HTMLElement) + expect(screen.getByRole('tooltip').textContent).toBe('TOOL') + fireEvent.mouseLeave(toolTag as HTMLElement) + expect(screen.queryByRole('tooltip')).toBeNull() + }) + + it('uses information and compression glyphs for injected and compacted context', () => { + const turns: readonly TrajectoryTurnModel[] = [{ + turn: 1, + groups: [{ + title: 'Context', + cells: [ + { index: 1, kind: 'context', text: 'Workspace context', timeSeconds: 0 }, + { index: 2, kind: 'compacted', text: 'Compacted history', timeSeconds: 0 }, + ], + }], + }] + const view = render() + + expect(view.container.querySelector( + '[data-role-kind="context"] [data-role-icon="information"]', + )).toBeTruthy() + expect(view.container.querySelector( + '[data-role-kind="compacted"] [data-role-icon="compacted"]', + )).toBeTruthy() + }) + + it('keeps a compact turn label available for narrow layouts', () => { + render() + const turnLabel = screen.getByLabelText('Turn 1') + + expect(turnLabel.textContent).toContain('Turn 1') + expect(turnLabel.textContent).toContain('#1') + }) + it('renders a single-text JSON tool result as a JSON tree', () => { const turns: readonly TrajectoryTurnModel[] = [{ turn: 1,