Skip to content

Commit c512cf7

Browse files
authored
fix(dashboard): contain transcript detail on mobile (#1660)
1 parent 2c94ce8 commit c512cf7

4 files changed

Lines changed: 57 additions & 37 deletions

File tree

apps/dashboard/src/components/EvalDetail.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ export function EvalDetail({
171171
};
172172

173173
return (
174-
<div className="flex h-full min-h-0 flex-col">
174+
<div className="flex h-full min-h-0 min-w-0 flex-col">
175175
{/* Tab navigation — at the top so Files tab editor fills maximum height */}
176-
<div className="border-b border-gray-800">
177-
<div className="flex gap-1 px-4">
176+
<div className="min-w-0 border-b border-gray-800">
177+
<div className="flex min-w-0 gap-1 overflow-x-auto px-4">
178178
{tabs.map((tab) => (
179179
<button
180180
type="button"
@@ -193,9 +193,9 @@ export function EvalDetail({
193193
</div>
194194

195195
{/* Tab content */}
196-
<div className="min-h-0 flex-1 overflow-hidden">
196+
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
197197
{activeTab === 'checks' && (
198-
<div className="overflow-auto p-4">
198+
<div className="min-w-0 overflow-auto p-4">
199199
{showAggregateRepeat ? (
200200
<RepeatAggregateChecksTab
201201
result={result}
@@ -227,7 +227,7 @@ export function EvalDetail({
227227
</div>
228228
)}
229229
{activeTab === 'transcript' && (
230-
<div className="overflow-auto p-4">
230+
<div className="min-w-0 overflow-auto p-4">
231231
{showAggregateRepeat ? (
232232
<RepeatAggregateTranscriptTab
233233
result={result}
@@ -255,7 +255,7 @@ export function EvalDetail({
255255
</div>
256256
)}
257257
{activeTab === 'source' && (
258-
<div className="overflow-auto p-4">
258+
<div className="min-w-0 overflow-auto p-4">
259259
<SourceTab result={detailResult} />
260260
</div>
261261
)}

apps/dashboard/src/components/ResultTable.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,9 @@ export function ResultTable({
420420

421421
<div
422422
className={
423-
selectedRow ? 'grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(360px,42rem)]' : ''
423+
selectedRow
424+
? 'grid min-w-0 max-w-full gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(360px,42rem)]'
425+
: ''
424426
}
425427
>
426428
<div className="min-w-0">
@@ -1013,7 +1015,7 @@ function ResultDetailPanel({
10131015
<aside
10141016
key={panelScrollKey}
10151017
ref={scrollPanelIntoView}
1016-
className="min-w-0 rounded-lg border border-gray-800 bg-gray-950/80 xl:sticky xl:top-4 xl:max-h-[calc(100vh-2rem)]"
1018+
className="min-w-0 max-w-full overflow-hidden rounded-lg border border-gray-800 bg-gray-950/80 xl:sticky xl:top-4 xl:max-h-[calc(100vh-2rem)]"
10171019
>
10181020
<div className="flex min-w-0 items-start justify-between gap-3 border-b border-gray-800 px-4 py-3">
10191021
<div className="min-w-0">
@@ -1042,7 +1044,7 @@ function ResultDetailPanel({
10421044
</button>
10431045
</div>
10441046
</div>
1045-
<div className="h-[36rem] min-h-[28rem] overflow-hidden xl:h-[calc(100vh-9rem)]">
1047+
<div className="h-[36rem] min-h-[28rem] min-w-0 overflow-hidden xl:h-[calc(100vh-9rem)]">
10461048
<EvalDetail
10471049
key={`${row.key}:${selectedTrialPath ?? 'aggregate'}:${initialFilePath ?? ''}`}
10481050
eval={row.result}

apps/dashboard/src/components/TranscriptTimeline.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ function JsonBlock({
510510
}: { label: string; value: unknown; tone?: 'default' | 'error' }) {
511511
if (!hasValue(value)) return null;
512512
return (
513-
<div className="space-y-1">
513+
<div className="min-w-0 space-y-1">
514514
<div
515515
className={
516516
tone === 'error'
@@ -520,7 +520,7 @@ function JsonBlock({
520520
>
521521
{label}
522522
</div>
523-
<pre className="max-h-80 overflow-auto rounded-md border border-gray-800 bg-gray-950 p-3 text-xs text-gray-200">
523+
<pre className="max-h-80 max-w-full overflow-auto rounded-md border border-gray-800 bg-gray-950 p-3 text-xs text-gray-200">
524524
<code>{formatUnknown(value)}</code>
525525
</pre>
526526
</div>
@@ -529,7 +529,7 @@ function JsonBlock({
529529

530530
function MetadataPill({ children }: { children: ReactNode }) {
531531
return (
532-
<span className="rounded-md border border-gray-800 bg-gray-950 px-2 py-0.5 text-xs text-gray-400">
532+
<span className="inline-flex min-w-0 max-w-full break-all rounded-md border border-gray-800 bg-gray-950 px-2 py-0.5 text-xs text-gray-400">
533533
{children}
534534
</span>
535535
);
@@ -586,15 +586,15 @@ function ToolCallDetails({
586586

587587
return (
588588
<details
589-
className="rounded-md border border-gray-800 bg-gray-950/80 p-3"
589+
className="min-w-0 rounded-md border border-gray-800 bg-gray-950/80 p-3"
590590
open={expanded}
591591
data-testid={`tool-call-${callId ?? index}`}
592592
data-expanded={expanded ? 'true' : 'false'}
593593
onToggle={(event: SyntheticEvent<HTMLDetailsElement>) =>
594594
onToggle(toolCall.id, event.currentTarget.open)
595595
}
596596
>
597-
<summary className="cursor-pointer list-none text-sm font-medium text-gray-200">
597+
<summary className="min-w-0 cursor-pointer list-none text-sm font-medium text-gray-200">
598598
<span className="inline-flex min-w-0 flex-wrap items-center gap-2">
599599
<span className="text-xs text-gray-500">{expanded ? '-' : '+'}</span>
600600
<span>Tool call</span>
@@ -605,7 +605,7 @@ function ToolCallDetails({
605605
{duration && <span className="tabular-nums text-xs text-gray-500">{duration}</span>}
606606
</span>
607607
</summary>
608-
<div className="mt-3 space-y-3">
608+
<div className="mt-3 min-w-0 space-y-3">
609609
<div className="flex flex-wrap gap-2">
610610
{callId && <MetadataPill>id: {callId}</MetadataPill>}
611611
{duration && <MetadataPill>duration: {duration}</MetadataPill>}
@@ -624,12 +624,12 @@ function ToolResultDetails({ line }: { line: TranscriptJsonLine }) {
624624
const duration = formatDurationMs(line.duration_ms);
625625
const tokenUsage = formatTokenUsage(line.token_usage);
626626
return (
627-
<details className="rounded-md border border-amber-900/50 bg-gray-950/60 p-3">
627+
<details className="min-w-0 rounded-md border border-amber-900/50 bg-gray-950/60 p-3">
628628
<summary className="cursor-pointer text-sm font-medium text-amber-300">
629629
{line.name ? `Tool result · ${line.name}` : 'Tool result'}
630630
{duration && <span className="ml-2 tabular-nums text-xs text-gray-500">{duration}</span>}
631631
</summary>
632-
<div className="mt-3 space-y-3">
632+
<div className="mt-3 min-w-0 space-y-3">
633633
<div className="flex flex-wrap gap-2">
634634
{line.name && <MetadataPill>name: {line.name}</MetadataPill>}
635635
{duration && <MetadataPill>duration: {duration}</MetadataPill>}
@@ -660,15 +660,15 @@ function TranscriptMessageCard({
660660
return (
661661
<details
662662
id={message.anchorId}
663-
className={`scroll-mt-6 rounded-lg border ${roleStyle.container}`}
663+
className={`min-w-0 scroll-mt-6 rounded-lg border ${roleStyle.container}`}
664664
open={expanded}
665665
data-testid={`message-row-${ordinal + 1}`}
666666
data-expanded={expanded ? 'true' : 'false'}
667667
onToggle={(event: SyntheticEvent<HTMLDetailsElement>) =>
668668
onToggleMessage(message.id, event.currentTarget.open)
669669
}
670670
>
671-
<summary className="cursor-pointer list-none px-4 py-3">
671+
<summary className="min-w-0 cursor-pointer list-none px-4 py-3">
672672
<div className="flex flex-wrap items-center justify-between gap-3">
673673
<div className="flex min-w-0 flex-wrap items-center gap-2">
674674
<span className="text-xs text-gray-500">{expanded ? '-' : '+'}</span>
@@ -707,7 +707,7 @@ function TranscriptMessageCard({
707707
{line.role === 'tool' || line.role === 'function' ? (
708708
<ToolResultDetails line={line} />
709709
) : content.trim().length > 0 ? (
710-
<pre className="whitespace-pre-wrap break-words text-sm leading-6 text-gray-200">
710+
<pre className="max-w-full whitespace-pre-wrap break-words text-sm leading-6 text-gray-200">
711711
{content}
712712
</pre>
713713
) : (
@@ -728,7 +728,7 @@ function TranscriptMessageCard({
728728
)}
729729

730730
{line.metadata && line.role !== 'tool' && line.role !== 'function' && (
731-
<details className="rounded-md border border-gray-800 bg-gray-950/60 p-3">
731+
<details className="min-w-0 rounded-md border border-gray-800 bg-gray-950/60 p-3">
732732
<summary className="cursor-pointer text-xs font-medium text-gray-400">
733733
Message metadata
734734
</summary>
@@ -757,7 +757,7 @@ function TranscriptSummary({
757757
const toolCounts = summarizeToolCounts(messages);
758758

759759
return (
760-
<div className="flex flex-wrap gap-2">
760+
<div className="flex min-w-0 flex-wrap gap-2">
761761
<MetadataPill>{messages.length} messages</MetadataPill>
762762
{Array.from(roleCounts.entries()).map(([role, count]) => (
763763
<MetadataPill key={role}>
@@ -905,37 +905,37 @@ export function TranscriptTimeline({
905905
}
906906

907907
return (
908-
<div className="space-y-4">
908+
<div className="min-w-0 space-y-4">
909909
{hasCanonicalAnswer && (
910-
<section className="rounded-lg border border-emerald-900/50 bg-emerald-950/20 p-4">
911-
<div className="flex flex-wrap items-center justify-between gap-3">
912-
<div>
910+
<section className="min-w-0 rounded-lg border border-emerald-900/50 bg-emerald-950/20 p-4">
911+
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3">
912+
<div className="min-w-0">
913913
<h3 className="text-sm font-medium text-emerald-300">Final answer</h3>
914914
<p className="mt-1 text-xs text-gray-500">
915915
Highlighted from canonical <code>outputs/answer.md</code>; transcript context stays
916916
below.
917917
</p>
918918
</div>
919-
<div className="flex flex-wrap items-center gap-2">
919+
<div className="flex min-w-0 flex-wrap items-center gap-2">
920920
<OpenFileButton path={answerPath} onOpenFile={onOpenFile}>
921921
Open answer.md in Files
922922
</OpenFileButton>
923923
<ActionLink href={answerHref}>Open answer.md</ActionLink>
924924
</div>
925925
</div>
926-
<pre className="mt-3 max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-md border border-emerald-900/40 bg-gray-950/80 p-3 text-sm leading-6 text-gray-100">
926+
<pre className="mt-3 max-h-64 max-w-full overflow-auto whitespace-pre-wrap break-words rounded-md border border-emerald-900/40 bg-gray-950/80 p-3 text-sm leading-6 text-gray-100">
927927
{finalAnswer && finalAnswer.trim().length > 0 ? finalAnswer : 'answer.md is empty.'}
928928
</pre>
929929
</section>
930930
)}
931931

932-
<section className="rounded-lg border border-gray-800 bg-gray-900 p-4">
933-
<div className="flex flex-wrap items-start justify-between gap-3">
934-
<div className="space-y-2">
932+
<section className="min-w-0 rounded-lg border border-gray-800 bg-gray-900 p-4">
933+
<div className="flex min-w-0 flex-wrap items-start justify-between gap-3">
934+
<div className="min-w-0 space-y-2">
935935
<h3 className="text-sm font-medium text-gray-300">Transcript timeline</h3>
936936
<TranscriptSummary messages={messages} transcriptPath={transcriptPath} />
937937
</div>
938-
<div className="flex flex-wrap items-center gap-2">
938+
<div className="flex min-w-0 flex-wrap items-center gap-2">
939939
<OpenFileButton path={transcriptPath} onOpenFile={onOpenFile}>
940940
Open transcript.json in Files
941941
</OpenFileButton>
@@ -947,9 +947,9 @@ export function TranscriptTimeline({
947947
</div>
948948
</section>
949949

950-
<section className="rounded-lg border border-gray-800 bg-gray-900 p-3">
951-
<div className="flex flex-wrap items-center justify-between gap-3">
952-
<div className="flex flex-wrap gap-2">
950+
<section className="min-w-0 rounded-lg border border-gray-800 bg-gray-900 p-3">
951+
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3">
952+
<div className="flex min-w-0 flex-wrap gap-2">
953953
<FilterButton
954954
active={filter === 'all'}
955955
count={messages.length}
@@ -980,7 +980,7 @@ export function TranscriptTimeline({
980980
</FilterButton>
981981
</div>
982982
{allToolIds.length > 0 && (
983-
<div className="flex flex-wrap gap-2">
983+
<div className="flex min-w-0 flex-wrap gap-2">
984984
<button
985985
type="button"
986986
onClick={expandAllToolCalls}

apps/dashboard/src/components/transcript-timeline.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,22 @@ describe('TranscriptTimeline', () => {
189189
expect(html).toContain('Download normalized JSON');
190190
expect(html).toContain('{&quot;answer&quot;:42,&quot;source&quot;:&quot;src/app.ts&quot;}');
191191
});
192+
193+
it('keeps long transcript artifact labels and JSON blocks constrained for narrow panels', () => {
194+
const parsed = parseTranscriptJsonl(structuredTranscriptJsonl);
195+
const html = renderToStaticMarkup(
196+
<TranscriptTimeline
197+
entries={parsed.entries}
198+
transcriptPath="very-long-project-name/very-long-test-case-name/sample-1/transcript.json"
199+
transcriptHref="/api/raw-transcript"
200+
transcriptDownloadHref="/api/download-transcript"
201+
/>,
202+
);
203+
204+
expect(html).toContain('break-all');
205+
expect(html).toContain('max-w-full overflow-auto');
206+
expect(html).toContain(
207+
'very-long-project-name/very-long-test-case-name/sample-1/transcript.json',
208+
);
209+
});
192210
});

0 commit comments

Comments
 (0)