@@ -33,7 +33,13 @@ import { QueueName } from "../runs/v3/QueueName";
3333
3434const MAX_STRING_DISPLAY_LENGTH = 64 ;
3535const ROW_HEIGHT = 33 ; // Estimated row height in pixels
36- const DEFAULT_COLUMN_SIZE = 150 ; // Default column width
36+
37+ // Column width calculation constants
38+ const MIN_COLUMN_WIDTH = 60 ;
39+ const MAX_COLUMN_WIDTH = 400 ;
40+ const CHAR_WIDTH_PX = 7.5 ; // Approximate width of a monospace character at text-xs (12px)
41+ const CELL_PADDING_PX = 40 ; // px-2 (8px) on each side + buffer for copy button
42+ const SAMPLE_SIZE = 100 ; // Number of rows to sample for width calculation
3743
3844// Type for row data
3945type RowData = Record < string , unknown > ;
@@ -44,6 +50,113 @@ interface ColumnMeta {
4450 alignment : "left" | "right" ;
4551}
4652
53+ /**
54+ * Get the approximate display length (in characters) of a value based on its type and formatting
55+ */
56+ function getDisplayLength ( value : unknown , column : OutputColumnMetadata ) : number {
57+ if ( value === null ) return 4 ; // "NULL"
58+ if ( value === undefined ) return 9 ; // "UNDEFINED"
59+
60+ // Handle custom render types - estimate their rendered width
61+ if ( column . customRenderType ) {
62+ switch ( column . customRenderType ) {
63+ case "runId" :
64+ // Run IDs are typically like "run_abc123xyz"
65+ return typeof value === "string" ? Math . min ( value . length , MAX_STRING_DISPLAY_LENGTH ) : 15 ;
66+ case "runStatus" :
67+ // Status badges have icon + text, approximate width
68+ return 12 ;
69+ case "duration" :
70+ if ( typeof value === "number" ) {
71+ // Format and measure: "1h 23m 45s" style
72+ const formatted = formatDurationMilliseconds ( value , { style : "short" } ) ;
73+ return formatted . length ;
74+ }
75+ return 10 ;
76+ case "durationSeconds" :
77+ if ( typeof value === "number" ) {
78+ const formatted = formatDurationMilliseconds ( value * 1000 , { style : "short" } ) ;
79+ return formatted . length ;
80+ }
81+ return 10 ;
82+ case "cost" :
83+ case "costInDollars" :
84+ // Currency format: "$1,234.56"
85+ if ( typeof value === "number" ) {
86+ const amount = column . customRenderType === "cost" ? value / 100 : value ;
87+ return formatCurrencyAccurate ( amount ) . length ;
88+ }
89+ return 12 ;
90+ case "machine" :
91+ // Machine preset names like "small-1x"
92+ return typeof value === "string" ? value . length : 10 ;
93+ case "environmentType" :
94+ // Environment labels: "PRODUCTION", "STAGING", etc.
95+ return 12 ;
96+ case "project" :
97+ case "environment" :
98+ return typeof value === "string" ? Math . min ( value . length , 20 ) : 12 ;
99+ case "queue" :
100+ return typeof value === "string" ? Math . min ( value . length , 25 ) : 15 ;
101+ }
102+ }
103+
104+ // Handle by ClickHouse type
105+ if ( isDateTimeType ( column . type ) ) {
106+ // DateTime format: "Jan 15, 2026, 12:34:56 PM"
107+ return 24 ;
108+ }
109+
110+ if ( column . type === "JSON" || column . type . startsWith ( "Array" ) ) {
111+ if ( typeof value === "object" ) {
112+ const jsonStr = JSON . stringify ( value ) ;
113+ return Math . min ( jsonStr . length , MAX_STRING_DISPLAY_LENGTH ) ;
114+ }
115+ }
116+
117+ if ( isBooleanType ( column . type ) ) {
118+ return 5 ; // "true" or "false"
119+ }
120+
121+ if ( isNumericType ( column . type ) ) {
122+ if ( typeof value === "number" ) {
123+ return formatNumber ( value ) . length ;
124+ }
125+ }
126+
127+ // Default: string length capped at max display length
128+ const strValue = String ( value ) ;
129+ return Math . min ( strValue . length , MAX_STRING_DISPLAY_LENGTH ) ;
130+ }
131+
132+ /**
133+ * Calculate the optimal width for a column based on its content
134+ */
135+ function calculateColumnWidth (
136+ columnName : string ,
137+ rows : RowData [ ] ,
138+ column : OutputColumnMetadata
139+ ) : number {
140+ // Start with header length
141+ let maxLength = columnName . length ;
142+
143+ // Sample rows to find max content length
144+ const sampleRows = rows . slice ( 0 , SAMPLE_SIZE ) ;
145+ for ( const row of sampleRows ) {
146+ const value = row [ columnName ] ;
147+ const displayLength = getDisplayLength ( value , column ) ;
148+ if ( displayLength > maxLength ) {
149+ maxLength = displayLength ;
150+ }
151+ }
152+
153+ // Calculate pixel width: characters * char width + padding
154+ const calculatedWidth = Math . ceil ( maxLength * CHAR_WIDTH_PX + CELL_PADDING_PX ) ;
155+
156+ // Apply min/max bounds
157+ return Math . min ( MAX_COLUMN_WIDTH , Math . max ( MIN_COLUMN_WIDTH , calculatedWidth ) ) ;
158+ }
159+
47160/**
48161 * Truncate a string for display, adding ellipsis if it exceeds max length
49162 */
@@ -526,6 +639,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
526639 const tableContainerRef = useRef < HTMLDivElement > ( null ) ;
527640
528641 // Create TanStack Table column definitions from OutputColumnMetadata
642+ // Calculate column widths based on content
529643 const columnDefs = useMemo < ColumnDef < RowData , unknown > [ ] > (
530644 ( ) =>
531645 columns . map ( ( col ) => ( {
@@ -543,9 +657,9 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
543657 outputColumn : col ,
544658 alignment : isRightAlignedColumn ( col ) ? "right" : "left" ,
545659 } as ColumnMeta ,
546- size : DEFAULT_COLUMN_SIZE ,
660+ size : calculateColumnWidth ( col . name , rows , col ) ,
547661 } ) ) ,
548- [ columns , prettyFormatting ]
662+ [ columns , rows , prettyFormatting ]
549663 ) ;
550664
551665 // Initialize TanStack Table
0 commit comments