Skip to content

Commit 9da734f

Browse files
samejrclaude
andcommitted
feat(webapp): truncate long smart column values and polish the header
- Widen the smart column dialog by 70px (860 -> 930). - Use the bolt icon for the "Add smart column" menu item. - Fix long text escaping above the preview panel's header. The rows are h-8 with items-center, so wrapped text taller than the row was centred and half of it overflowed upwards past scroll-top, where it was unreachable and clipped, losing the start of the string. Rows are now single-line, so content starts under the header and flows down; the panel still scrolls horizontally. - Say "sample payload" rather than "sample" in the intro. - Middle-truncate long text cells at 600px via the existing MiddleTruncate, with the full value in a tooltip. MiddleTruncate gains an optional tooltipDelay (it defaulted to opening instantly, which fires while merely scanning rows) and the runs table passes 500ms. The dialog preview opts out and keeps scrolling instead. - Move the smart column's bolt to the right of the header label at size-4, and give the header a tooltip naming the path, source and display type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b56742d commit 9da734f

5 files changed

Lines changed: 58 additions & 14 deletions

File tree

apps/webapp/app/components/primitives/MiddleTruncate.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { SimpleTooltip } from "./Tooltip";
55
type MiddleTruncateProps = {
66
text: string;
77
className?: string;
8+
/** Hover delay before the full-text tooltip opens. Defaults to the tooltip default (0). */
9+
tooltipDelay?: number;
810
};
911

1012
/**
@@ -13,7 +15,7 @@ type MiddleTruncateProps = {
1315
*
1416
* Example: "namespace:category:subcategory:task-name" becomes "namespace:cat…task-name"
1517
*/
16-
export function MiddleTruncate({ text, className }: MiddleTruncateProps) {
18+
export function MiddleTruncate({ text, className, tooltipDelay }: MiddleTruncateProps) {
1719
const containerRef = useRef<HTMLSpanElement>(null);
1820
const measureRef = useRef<HTMLSpanElement>(null);
1921
const [displayText, setDisplayText] = useState(text);
@@ -154,6 +156,7 @@ export function MiddleTruncate({ text, className }: MiddleTruncateProps) {
154156
content={<span className="max-w-xs break-all font-mono text-xs">{text}</span>}
155157
side="top"
156158
asChild
159+
delayDuration={tooltipDelay}
157160
/>
158161
);
159162
}

apps/webapp/app/components/runs/v3/AddSmartColumnDialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,12 @@ export function AddSmartColumnDialog({
169169
<Dialog open={open} onOpenChange={onOpenChange}>
170170
{/* Bounded height with the columns absorbing it, so the stacked form can't push the
171171
header or footer off a short screen. */}
172-
<DialogContent className="max-h-[90vh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-[860px]!">
172+
<DialogContent className="max-h-[90vh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-[930px]!">
173173
<DialogHeader>{editing ? "Edit smart column" : "Add smart column"}</DialogHeader>
174174
<div className="flex min-h-0 flex-col gap-5 pt-3">
175175
<Paragraph variant="base/bright">
176-
Pick a source, then click a value in the sample to turn it into a column. Smart columns
177-
are display only, so you can't sort or filter by them.
176+
Pick a source, then click a value in the sample payload to turn it into a column. Smart
177+
columns are display only, so you can't sort or filter by them.
178178
</Paragraph>
179179

180180
<div className="grid min-h-0 grid-cols-1 items-stretch gap-2.5 md:grid-cols-3">
@@ -375,7 +375,7 @@ function SmartColumnPreview({
375375
<div
376376
key={index}
377377
className={cn(
378-
"flex h-8 items-center border-b border-grid-dimmed/60 px-2.5 text-sm last:border-b-0",
378+
"flex h-8 items-center whitespace-nowrap border-b border-grid-dimmed/60 px-2.5 text-sm last:border-b-0",
379379
alignClass
380380
)}
381381
>

apps/webapp/app/components/runs/v3/RunsDisplayOptions.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
ArrowUturnLeftIcon,
33
PencilSquareIcon,
4-
PlusIcon,
54
StarIcon as StarIconSolid,
65
XMarkIcon,
76
} from "@heroicons/react/20/solid";
@@ -237,7 +236,7 @@ export function RunsDisplayOptions({
237236
</div>
238237
<div className="flex flex-col p-1">
239238
<PopoverMenuItem
240-
icon={PlusIcon}
239+
icon={SmartColumnIcon}
241240
title="Add smart column…"
242241
onClick={() => setAddOpen(true)}
243242
className="h-8"

apps/webapp/app/components/runs/v3/TaskRunsTable.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,12 +486,33 @@ const STANDARD_RENDERERS: Record<string, StandardColumnRenderer> = {
486486
},
487487
};
488488

489+
const SMART_SOURCE_LABELS: Record<SmartColumnSource, string> = {
490+
payload: "payload",
491+
metadata: "metadata",
492+
output: "output",
493+
};
494+
489495
function SmartColumnHeader({ def }: { def: SmartColumnDef }) {
490496
return (
491-
<TableHeaderCell>
497+
<TableHeaderCell
498+
tooltip={
499+
<div className="flex max-w-xs flex-col gap-1 p-1">
500+
<Paragraph variant="small" className="text-text-bright">
501+
Smart column
502+
</Paragraph>
503+
<Paragraph variant="extra-small" className="text-wrap! text-text-dimmed">
504+
Reads <span className="font-mono text-text-bright">{def.path}</span> from each run's{" "}
505+
{SMART_SOURCE_LABELS[def.source]}, shown as {def.displayAs}.
506+
</Paragraph>
507+
<Paragraph variant="extra-small" className="text-wrap! text-text-dimmed">
508+
Display only, so this column can't be sorted or filtered.
509+
</Paragraph>
510+
</div>
511+
}
512+
>
492513
<span className="flex items-center gap-1">
493-
<SmartColumnIcon className="size-3.5 flex-none text-text-dimmed" />
494514
<span className="truncate">{def.label}</span>
515+
<SmartColumnIcon className="size-4 flex-none text-text-dimmed" />
495516
</span>
496517
</TableHeaderCell>
497518
);
@@ -513,7 +534,7 @@ function SmartColumnCell({
513534

514535
return (
515536
<TableCell to={path} className={numeric ? "text-right tabular-nums" : undefined}>
516-
<SmartCellContent cell={cell} def={def} provisional={!run.hasFinished} />
537+
<SmartCellContent cell={cell} def={def} provisional={!run.hasFinished} truncate />
517538
</TableCell>
518539
);
519540
}

apps/webapp/app/components/runs/v3/smartColumnCell.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
22
import { Badge } from "~/components/primitives/Badge";
3+
import { MiddleTruncate } from "~/components/primitives/MiddleTruncate";
34
import { SimpleTooltip } from "~/components/primitives/Tooltip";
45
import { cn } from "~/utils/cn";
56
import type { SmartColumnDef } from "./runColumns";
@@ -32,7 +33,16 @@ function toFiniteNumber(value: unknown): number {
3233
return NaN;
3334
}
3435

35-
function renderSmartValue(value: unknown, displayAs: SmartColumnDef["displayAs"]): React.ReactNode {
36+
/** How wide a truncated text cell may get before the middle is elided. */
37+
const TEXT_CELL_MAX_WIDTH = "max-w-[600px]";
38+
/** Long values are common enough that an instant tooltip would fire while just scanning rows. */
39+
const TEXT_CELL_TOOLTIP_DELAY_MS = 500;
40+
41+
function renderSmartValue(
42+
value: unknown,
43+
displayAs: SmartColumnDef["displayAs"],
44+
truncate: boolean
45+
): React.ReactNode {
3646
switch (displayAs) {
3747
case "number": {
3848
const n = toFiniteNumber(value);
@@ -46,8 +56,16 @@ function renderSmartValue(value: unknown, displayAs: SmartColumnDef["displayAs"]
4656
}
4757
case "badge":
4858
return <Badge variant="extra-small">{stringifySmartValue(value)}</Badge>;
49-
default:
50-
return stringifySmartValue(value);
59+
default: {
60+
const text = stringifySmartValue(value);
61+
if (!truncate) return text;
62+
// MiddleTruncate measures against its parent, so it needs the width cap around it.
63+
return (
64+
<span className={cn("block min-w-0", TEXT_CELL_MAX_WIDTH)}>
65+
<MiddleTruncate text={text} tooltipDelay={TEXT_CELL_TOOLTIP_DELAY_MS} />
66+
</span>
67+
);
68+
}
5169
}
5270
}
5371

@@ -61,10 +79,13 @@ export function SmartCellContent({
6179
cell,
6280
def,
6381
provisional,
82+
truncate = false,
6483
}: {
6584
cell: SmartCellValue;
6685
def: SmartColumnDef;
6786
provisional: boolean;
87+
/** Middle-truncate long text to a fixed cap. On for the table; the preview scrolls instead. */
88+
truncate?: boolean;
6889
}) {
6990
if (cell.state === "offloaded") {
7091
return (
@@ -86,7 +107,7 @@ export function SmartCellContent({
86107

87108
return (
88109
<span className={cn(provisional && "border-b border-dotted border-text-dimmed/50")}>
89-
{renderSmartValue(cell.value, def.displayAs)}
110+
{renderSmartValue(cell.value, def.displayAs, truncate)}
90111
</span>
91112
);
92113
}

0 commit comments

Comments
 (0)