From 468ec2ea81f64986ce7f7ffa85c2d2d8c8228751 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 14 Jan 2026 12:06:02 -0800 Subject: [PATCH] fix(terminal-colors): change algo to compute colors based on hash of execution id and pointer from bottom (#2817) --- .../components/terminal/terminal.tsx | 117 ++++++++++-------- apps/sim/lib/core/utils/formatting.ts | 16 +++ 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx index 311c2ff22..56b9dc8bb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx @@ -36,6 +36,7 @@ import { Tooltip, } from '@/components/emcn' import { getEnv, isTruthy } from '@/lib/core/config/env' +import { formatTimeWithSeconds } from '@/lib/core/utils/formatting' import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider' import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils' import { @@ -82,18 +83,6 @@ const COLUMN_WIDTHS = { OUTPUT_PANEL: 'w-[400px]', } as const -/** - * Color palette for run IDs - matching code syntax highlighting colors - */ -const RUN_ID_COLORS = [ - { text: '#4ADE80' }, // Green - { text: '#F472B6' }, // Pink - { text: '#60C5FF' }, // Blue - { text: '#FF8533' }, // Orange - { text: '#C084FC' }, // Purple - { text: '#FCD34D' }, // Yellow -] as const - /** * Shared styling constants */ @@ -183,22 +172,6 @@ const ToggleButton = ({ ) -/** - * Formats timestamp to H:MM:SS AM/PM TZ format - */ -const formatTimestamp = (timestamp: string): string => { - const date = new Date(timestamp) - const fullString = date.toLocaleTimeString('en-US', { - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - hour12: true, - timeZoneName: 'short', - }) - // Format: "5:54:55 PM PST" - return as is - return fullString -} - /** * Truncates execution ID for display as run ID */ @@ -208,16 +181,25 @@ const formatRunId = (executionId?: string): string => { } /** - * Gets color for a run ID based on its index in the execution ID order map + * Run ID colors */ -const getRunIdColor = ( - executionId: string | undefined, - executionIdOrderMap: Map -) => { +const RUN_ID_COLORS = [ + '#4ADE80', // Green + '#F472B6', // Pink + '#60C5FF', // Blue + '#FF8533', // Orange + '#C084FC', // Purple + '#EAB308', // Yellow + '#2DD4BF', // Teal + '#FB7185', // Rose +] as const + +/** + * Gets color for a run ID from the precomputed color map. + */ +const getRunIdColor = (executionId: string | undefined, colorMap: Map) => { if (!executionId) return null - const colorIndex = executionIdOrderMap.get(executionId) - if (colorIndex === undefined) return null - return RUN_ID_COLORS[colorIndex % RUN_ID_COLORS.length] + return colorMap.get(executionId) ?? null } /** @@ -464,25 +446,52 @@ export function Terminal() { }, [allWorkflowEntries]) /** - * Create stable execution ID to color index mapping based on order of first appearance. - * Once an execution ID is assigned a color index, it keeps that index. - * Uses all workflow entries to maintain consistent colors regardless of active filters. + * Track color offset - increments when old executions are trimmed + * so remaining executions keep their colors. */ - const executionIdOrderMap = useMemo(() => { - const orderMap = new Map() - let colorIndex = 0 + const colorStateRef = useRef<{ executionIds: string[]; offset: number }>({ + executionIds: [], + offset: 0, + }) - // Process entries in reverse order (oldest first) since entries array is newest-first - // Use allWorkflowEntries to ensure colors remain consistent when filters change + /** + * Compute colors for each execution ID using sequential assignment. + * Colors cycle through RUN_ID_COLORS based on position + offset. + * When old executions are trimmed, offset increments to preserve colors. + */ + const executionColorMap = useMemo(() => { + const currentIds: string[] = [] + const seen = new Set() for (let i = allWorkflowEntries.length - 1; i >= 0; i--) { - const entry = allWorkflowEntries[i] - if (entry.executionId && !orderMap.has(entry.executionId)) { - orderMap.set(entry.executionId, colorIndex) - colorIndex++ + const execId = allWorkflowEntries[i].executionId + if (execId && !seen.has(execId)) { + currentIds.push(execId) + seen.add(execId) } } - return orderMap + const { executionIds: prevIds, offset: prevOffset } = colorStateRef.current + let newOffset = prevOffset + + if (prevIds.length > 0 && currentIds.length > 0) { + const currentOldest = currentIds[0] + if (prevIds[0] !== currentOldest) { + const trimmedCount = prevIds.indexOf(currentOldest) + if (trimmedCount > 0) { + newOffset = (prevOffset + trimmedCount) % RUN_ID_COLORS.length + } + } + } + + const colorMap = new Map() + for (let i = 0; i < currentIds.length; i++) { + const colorIndex = (newOffset + i) % RUN_ID_COLORS.length + colorMap.set(currentIds[i], RUN_ID_COLORS[colorIndex]) + } + + colorStateRef.current = { executionIds: currentIds, offset: newOffset } + + return colorMap }, [allWorkflowEntries]) /** @@ -1128,7 +1137,7 @@ export function Terminal() { {uniqueRunIds.map((runId, index) => { const isSelected = filters.runIds.has(runId) - const runIdColor = getRunIdColor(runId, executionIdOrderMap) + const runIdColor = getRunIdColor(runId, executionColorMap) return ( {formatRunId(runId)} @@ -1335,7 +1344,7 @@ export function Terminal() { const statusInfo = getStatusInfo(entry.success, entry.error) const isSelected = selectedEntry?.id === entry.id const BlockIcon = getBlockIcon(entry.blockType) - const runIdColor = getRunIdColor(entry.executionId, executionIdOrderMap) + const runIdColor = getRunIdColor(entry.executionId, executionColorMap) return (
{formatRunId(entry.executionId)} @@ -1411,7 +1420,7 @@ export function Terminal() { ROW_TEXT_CLASS )} > - {formatTimestamp(entry.timestamp)} + {formatTimeWithSeconds(new Date(entry.timestamp))}
) diff --git a/apps/sim/lib/core/utils/formatting.ts b/apps/sim/lib/core/utils/formatting.ts index 3d6d1e902..3fcf3d6f1 100644 --- a/apps/sim/lib/core/utils/formatting.ts +++ b/apps/sim/lib/core/utils/formatting.ts @@ -102,6 +102,22 @@ export function formatTime(date: Date): string { }) } +/** + * Format a time with seconds and timezone + * @param date - The date to format + * @param includeTimezone - Whether to include the timezone abbreviation + * @returns A formatted time string in the format "h:mm:ss AM/PM TZ" + */ +export function formatTimeWithSeconds(date: Date, includeTimezone = true): string { + return date.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: true, + timeZoneName: includeTimezone ? 'short' : undefined, + }) +} + /** * Format an ISO timestamp into a compact format for UI display * @param iso - ISO timestamp string