Skip to content

Commit 468ec2e

Browse files
authored
fix(terminal-colors): change algo to compute colors based on hash of execution id and pointer from bottom (#2817)
1 parent d7e0d9b commit 468ec2e

File tree

2 files changed

+79
-54
lines changed

2 files changed

+79
-54
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
Tooltip,
3737
} from '@/components/emcn'
3838
import { getEnv, isTruthy } from '@/lib/core/config/env'
39+
import { formatTimeWithSeconds } from '@/lib/core/utils/formatting'
3940
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
4041
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
4142
import {
@@ -82,18 +83,6 @@ const COLUMN_WIDTHS = {
8283
OUTPUT_PANEL: 'w-[400px]',
8384
} as const
8485

85-
/**
86-
* Color palette for run IDs - matching code syntax highlighting colors
87-
*/
88-
const RUN_ID_COLORS = [
89-
{ text: '#4ADE80' }, // Green
90-
{ text: '#F472B6' }, // Pink
91-
{ text: '#60C5FF' }, // Blue
92-
{ text: '#FF8533' }, // Orange
93-
{ text: '#C084FC' }, // Purple
94-
{ text: '#FCD34D' }, // Yellow
95-
] as const
96-
9786
/**
9887
* Shared styling constants
9988
*/
@@ -183,22 +172,6 @@ const ToggleButton = ({
183172
</Button>
184173
)
185174

186-
/**
187-
* Formats timestamp to H:MM:SS AM/PM TZ format
188-
*/
189-
const formatTimestamp = (timestamp: string): string => {
190-
const date = new Date(timestamp)
191-
const fullString = date.toLocaleTimeString('en-US', {
192-
hour: 'numeric',
193-
minute: '2-digit',
194-
second: '2-digit',
195-
hour12: true,
196-
timeZoneName: 'short',
197-
})
198-
// Format: "5:54:55 PM PST" - return as is
199-
return fullString
200-
}
201-
202175
/**
203176
* Truncates execution ID for display as run ID
204177
*/
@@ -208,16 +181,25 @@ const formatRunId = (executionId?: string): string => {
208181
}
209182

210183
/**
211-
* Gets color for a run ID based on its index in the execution ID order map
184+
* Run ID colors
185+
*/
186+
const RUN_ID_COLORS = [
187+
'#4ADE80', // Green
188+
'#F472B6', // Pink
189+
'#60C5FF', // Blue
190+
'#FF8533', // Orange
191+
'#C084FC', // Purple
192+
'#EAB308', // Yellow
193+
'#2DD4BF', // Teal
194+
'#FB7185', // Rose
195+
] as const
196+
197+
/**
198+
* Gets color for a run ID from the precomputed color map.
212199
*/
213-
const getRunIdColor = (
214-
executionId: string | undefined,
215-
executionIdOrderMap: Map<string, number>
216-
) => {
200+
const getRunIdColor = (executionId: string | undefined, colorMap: Map<string, string>) => {
217201
if (!executionId) return null
218-
const colorIndex = executionIdOrderMap.get(executionId)
219-
if (colorIndex === undefined) return null
220-
return RUN_ID_COLORS[colorIndex % RUN_ID_COLORS.length]
202+
return colorMap.get(executionId) ?? null
221203
}
222204

223205
/**
@@ -464,25 +446,52 @@ export function Terminal() {
464446
}, [allWorkflowEntries])
465447

466448
/**
467-
* Create stable execution ID to color index mapping based on order of first appearance.
468-
* Once an execution ID is assigned a color index, it keeps that index.
469-
* Uses all workflow entries to maintain consistent colors regardless of active filters.
449+
* Track color offset - increments when old executions are trimmed
450+
* so remaining executions keep their colors.
470451
*/
471-
const executionIdOrderMap = useMemo(() => {
472-
const orderMap = new Map<string, number>()
473-
let colorIndex = 0
452+
const colorStateRef = useRef<{ executionIds: string[]; offset: number }>({
453+
executionIds: [],
454+
offset: 0,
455+
})
474456

475-
// Process entries in reverse order (oldest first) since entries array is newest-first
476-
// Use allWorkflowEntries to ensure colors remain consistent when filters change
457+
/**
458+
* Compute colors for each execution ID using sequential assignment.
459+
* Colors cycle through RUN_ID_COLORS based on position + offset.
460+
* When old executions are trimmed, offset increments to preserve colors.
461+
*/
462+
const executionColorMap = useMemo(() => {
463+
const currentIds: string[] = []
464+
const seen = new Set<string>()
477465
for (let i = allWorkflowEntries.length - 1; i >= 0; i--) {
478-
const entry = allWorkflowEntries[i]
479-
if (entry.executionId && !orderMap.has(entry.executionId)) {
480-
orderMap.set(entry.executionId, colorIndex)
481-
colorIndex++
466+
const execId = allWorkflowEntries[i].executionId
467+
if (execId && !seen.has(execId)) {
468+
currentIds.push(execId)
469+
seen.add(execId)
482470
}
483471
}
484472

485-
return orderMap
473+
const { executionIds: prevIds, offset: prevOffset } = colorStateRef.current
474+
let newOffset = prevOffset
475+
476+
if (prevIds.length > 0 && currentIds.length > 0) {
477+
const currentOldest = currentIds[0]
478+
if (prevIds[0] !== currentOldest) {
479+
const trimmedCount = prevIds.indexOf(currentOldest)
480+
if (trimmedCount > 0) {
481+
newOffset = (prevOffset + trimmedCount) % RUN_ID_COLORS.length
482+
}
483+
}
484+
}
485+
486+
const colorMap = new Map<string, string>()
487+
for (let i = 0; i < currentIds.length; i++) {
488+
const colorIndex = (newOffset + i) % RUN_ID_COLORS.length
489+
colorMap.set(currentIds[i], RUN_ID_COLORS[colorIndex])
490+
}
491+
492+
colorStateRef.current = { executionIds: currentIds, offset: newOffset }
493+
494+
return colorMap
486495
}, [allWorkflowEntries])
487496

488497
/**
@@ -1128,7 +1137,7 @@ export function Terminal() {
11281137
<PopoverScrollArea style={{ maxHeight: '140px' }}>
11291138
{uniqueRunIds.map((runId, index) => {
11301139
const isSelected = filters.runIds.has(runId)
1131-
const runIdColor = getRunIdColor(runId, executionIdOrderMap)
1140+
const runIdColor = getRunIdColor(runId, executionColorMap)
11321141

11331142
return (
11341143
<PopoverItem
@@ -1139,7 +1148,7 @@ export function Terminal() {
11391148
>
11401149
<span
11411150
className='flex-1 font-mono text-[12px]'
1142-
style={{ color: runIdColor?.text || '#D2D2D2' }}
1151+
style={{ color: runIdColor || '#D2D2D2' }}
11431152
>
11441153
{formatRunId(runId)}
11451154
</span>
@@ -1335,7 +1344,7 @@ export function Terminal() {
13351344
const statusInfo = getStatusInfo(entry.success, entry.error)
13361345
const isSelected = selectedEntry?.id === entry.id
13371346
const BlockIcon = getBlockIcon(entry.blockType)
1338-
const runIdColor = getRunIdColor(entry.executionId, executionIdOrderMap)
1347+
const runIdColor = getRunIdColor(entry.executionId, executionColorMap)
13391348

13401349
return (
13411350
<div
@@ -1385,7 +1394,7 @@ export function Terminal() {
13851394
COLUMN_BASE_CLASS,
13861395
'truncate font-medium font-mono text-[12px]'
13871396
)}
1388-
style={{ color: runIdColor?.text || '#D2D2D2' }}
1397+
style={{ color: runIdColor || '#D2D2D2' }}
13891398
>
13901399
{formatRunId(entry.executionId)}
13911400
</span>
@@ -1411,7 +1420,7 @@ export function Terminal() {
14111420
ROW_TEXT_CLASS
14121421
)}
14131422
>
1414-
{formatTimestamp(entry.timestamp)}
1423+
{formatTimeWithSeconds(new Date(entry.timestamp))}
14151424
</span>
14161425
</div>
14171426
)

apps/sim/lib/core/utils/formatting.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ export function formatTime(date: Date): string {
102102
})
103103
}
104104

105+
/**
106+
* Format a time with seconds and timezone
107+
* @param date - The date to format
108+
* @param includeTimezone - Whether to include the timezone abbreviation
109+
* @returns A formatted time string in the format "h:mm:ss AM/PM TZ"
110+
*/
111+
export function formatTimeWithSeconds(date: Date, includeTimezone = true): string {
112+
return date.toLocaleTimeString('en-US', {
113+
hour: 'numeric',
114+
minute: '2-digit',
115+
second: '2-digit',
116+
hour12: true,
117+
timeZoneName: includeTimezone ? 'short' : undefined,
118+
})
119+
}
120+
105121
/**
106122
* Format an ISO timestamp into a compact format for UI display
107123
* @param iso - ISO timestamp string

0 commit comments

Comments
 (0)