Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore: fix conflicts
  • Loading branch information
adithyaakrishna committed Mar 30, 2026
commit f225168ad943fc7bdf5ac0295e20dea1649fbb6a
1 change: 0 additions & 1 deletion apps/sim/app/(landing)/partners/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Metadata } from 'next'
import Link from 'next/link'
import { getNavBlogPosts } from '@/lib/blog/registry'
import { martianMono } from '@/app/_styles/fonts/martian-mono/martian-mono'
import { season } from '@/app/_styles/fonts/season/season'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,11 +711,24 @@ export const StructuredOutput = memo(function StructuredOutput({
}, [])

// Reset expanded paths when data changes
const prevDataJsonRef = useRef<string>('')

useEffect(() => {
if (prevDataRef.current !== data || prevIsErrorRef.current !== isError) {
if (prevIsErrorRef.current !== isError) {
prevDataRef.current = data
prevIsErrorRef.current = isError
prevDataJsonRef.current = JSON.stringify(data)
setExpandedPaths(computeInitialPaths(data, isError))
return
}

if (prevDataRef.current !== data) {
const newJson = JSON.stringify(data)
if (prevDataJsonRef.current !== newJson) {
prevDataJsonRef.current = newJson
setExpandedPaths(computeInitialPaths(data, isError))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unguarded JSON.stringify can throw on non-serializable data

Medium Severity

The new deep-equality check calls JSON.stringify(data) without a try-catch. If data contains circular references, BigInt values, or other non-JSON-serializable structures, this will throw an unhandled error and crash the structured output component. The previous code only compared by reference (prevDataRef.current !== data), which can never throw.

Additional Locations (1)
Fix in Cursor Fix in Web
prevDataRef.current = data
}
}, [data, isError])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,18 @@ export function useTerminalFilters() {
*/
const filterEntries = useCallback(
(entries: ConsoleEntry[]): ConsoleEntry[] => {
// Apply filters first
if (!hasActiveFilters && sortConfig.direction === 'desc') {
return entries
}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

let result = entries

if (hasActiveFilters) {
result = entries.filter((entry) => {
// Block ID filter
if (filters.blockIds.size > 0 && !filters.blockIds.has(entry.blockId)) {
return false
}

// Status filter
if (filters.statuses.size > 0) {
const isError = !!entry.error
const hasStatus = isError ? filters.statuses.has('error') : filters.statuses.has('info')
Expand All @@ -105,7 +106,6 @@ export function useTerminalFilters() {
})
}

// Sort by executionOrder (monotonically increasing integer from server)
result = [...result].sort((a, b) => {
const comparison = a.executionOrder - b.executionOrder
return sortConfig.direction === 'asc' ? comparison : -comparison
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,20 +535,20 @@ const EntryNodeRow = memo(function EntryNodeRow({
)
})

interface TerminalLogListRowProps {
interface TerminalLogListDataRef {
rows: VisibleTerminalRow[]
selectedEntryId: string | null
onSelectEntry: (entry: ConsoleEntry) => void
expandedNodes: Set<string>
onToggleNode: (nodeId: string) => void
}

function TerminalLogListRow({
index,
style,
...props
}: RowComponentProps<TerminalLogListRowProps>) {
const { rows, selectedEntryId, onSelectEntry, expandedNodes, onToggleNode } = props
interface TerminalLogListRowProps {
dataRef: React.RefObject<TerminalLogListDataRef>
}

function TerminalLogListRow({ index, style, dataRef }: RowComponentProps<TerminalLogListRowProps>) {
const { rows, selectedEntryId, onSelectEntry, expandedNodes, onToggleNode } = dataRef.current
const row = rows[index]

if (row.rowType === 'separator') {
Expand Down Expand Up @@ -591,6 +591,8 @@ const TerminalLogsPane = memo(function TerminalLogsPane({
const containerRef = useRef<HTMLDivElement>(null)
const listRef = useListRef(null)
const [listHeight, setListHeight] = useState(400)
const prevRowCountRef = useRef(0)
const isNearBottomRef = useRef(true)

const rows = useMemo(
() => flattenVisibleExecutionRows(executionGroups, expandedNodes),
Expand All @@ -613,13 +615,59 @@ const TerminalLogsPane = memo(function TerminalLogsPane({
return () => resizeObserver.disconnect()
}, [])

const rowsRef = useRef(rows)
rowsRef.current = rows
useEffect(() => {
const tryAttach = () => {
const outerEl = listRef.current?.element
if (!outerEl) return false

const handleScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = outerEl
const distanceFromBottom = scrollHeight - scrollTop - clientHeight
isNearBottomRef.current = distanceFromBottom < TERMINAL_CONFIG.LOG_ROW_HEIGHT_PX * 3
}

outerEl.addEventListener('scroll', handleScroll, { passive: true })
cleanupRef.current = () => outerEl.removeEventListener('scroll', handleScroll)
return true
}

const cleanupRef = { current: () => {} }

if (!tryAttach()) {
const frameId = requestAnimationFrame(() => tryAttach())
return () => {
cancelAnimationFrame(frameId)
cleanupRef.current()
}
}

return () => cleanupRef.current()
}, [listRef])

useEffect(() => {
const newCount = rows.length
const prevCount = prevRowCountRef.current
prevRowCountRef.current = newCount

if (newCount <= prevCount || newCount === 0) return
if (!isNearBottomRef.current) return

listRef.current?.scrollToRow({ index: newCount - 1, align: 'end' })
}, [rows.length, listRef])

const dataRef = useRef<TerminalLogListDataRef>({
rows,
selectedEntryId,
onSelectEntry,
expandedNodes,
onToggleNode,
})
dataRef.current = { rows, selectedEntryId, onSelectEntry, expandedNodes, onToggleNode }

useEffect(() => {
if (!selectedEntryId) return

const currentRows = rowsRef.current
const currentRows = dataRef.current.rows
const rowIndex = currentRows.findIndex(
(row) => row.rowType === 'node' && row.node?.entry.id === selectedEntryId
)
Expand All @@ -629,27 +677,19 @@ const TerminalLogsPane = memo(function TerminalLogsPane({
}
}, [selectedEntryId, listRef])

const rowProps = useMemo<TerminalLogListRowProps>(
() => ({
rows,
selectedEntryId,
onSelectEntry,
expandedNodes,
onToggleNode,
}),
[rows, selectedEntryId, onSelectEntry, expandedNodes, onToggleNode]
)
const rowProps = useMemo<TerminalLogListRowProps>(() => ({ dataRef }), [dataRef])

return (
<div ref={containerRef} className='h-full'>
<div ref={containerRef} className='h-full bg-[var(--bg-primary)]' style={{ contain: 'strict' }}>
<List
className='bg-[var(--bg-primary)]'
listRef={listRef}
defaultHeight={listHeight}
rowCount={rows.length}
rowHeight={TERMINAL_CONFIG.LOG_ROW_HEIGHT_PX}
rowComponent={TerminalLogListRow}
rowProps={rowProps}
overscanCount={8}
overscanCount={30}
/>
</div>
)
Expand Down Expand Up @@ -697,6 +737,7 @@ export const Terminal = memo(function Terminal() {
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [showInput, setShowInput] = useState(false)
const [autoSelectEnabled, setAutoSelectEnabled] = useState(true)
const autoSelectExecutionIdRef = useRef<string | null>(null)
const [mainOptionsOpen, setMainOptionsOpen] = useState(false)

const [isTrainingEnvEnabled] = useState(() =>
Expand Down Expand Up @@ -773,12 +814,24 @@ export const Terminal = memo(function Terminal() {
return result
}, [executionGroups])

const prevAutoExpandKeyRef = useRef('')
const prevAutoExpandIdsRef = useRef<string[]>([])

const autoExpandNodeIds = useMemo(() => {
if (executionGroups.length === 0) {
return []
prevAutoExpandKeyRef.current = ''
prevAutoExpandIdsRef.current = []
return prevAutoExpandIdsRef.current
}

return collectExpandableNodeIds(executionGroups[0].entryTree)
const ids = collectExpandableNodeIds(executionGroups[0].entryTree)
const key = ids.join(',')
if (key === prevAutoExpandKeyRef.current) {
return prevAutoExpandIdsRef.current
}
prevAutoExpandKeyRef.current = key
prevAutoExpandIdsRef.current = ids
return ids
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}, [executionGroups])

/**
Expand Down Expand Up @@ -877,17 +930,13 @@ export const Terminal = memo(function Terminal() {
useEffect(() => {
if (autoExpandNodeIds.length === 0) return

const rafId = requestAnimationFrame(() => {
setExpandedNodes((prev) => {
const hasAll = autoExpandNodeIds.every((id) => prev.has(id))
if (hasAll) return prev
const next = new Set(prev)
autoExpandNodeIds.forEach((id) => next.add(id))
return next
})
setExpandedNodes((prev) => {
const hasAll = autoExpandNodeIds.every((id) => prev.has(id))
if (hasAll) return prev
const next = new Set(prev)
autoExpandNodeIds.forEach((id) => next.add(id))
return next
})

return () => cancelAnimationFrame(rafId)
}, [autoExpandNodeIds])

/**
Expand Down Expand Up @@ -1095,12 +1144,21 @@ export const Terminal = memo(function Terminal() {
if (executionGroups.length === 0 || navigableEntries.length === 0) {
setAutoSelectEnabled(true)
setSelectedEntryId(null)
autoSelectExecutionIdRef.current = null
return
}

if (!autoSelectEnabled) return

const newestExecutionId = executionGroups[0].executionId
const isNewExecution = newestExecutionId !== autoSelectExecutionIdRef.current

if (isNewExecution) {
autoSelectExecutionIdRef.current = newestExecutionId
} else if (selectedEntryId !== null) {
return
}

let lastNavEntry: NavigableBlockEntry | null = null

for (const navEntry of navigableEntries) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,24 @@ export function buildEntryTree(entries: ConsoleEntry[], idPrefix = ''): EntryNod
}
}

const nestedByContainerId = new Map<string, ConsoleEntry[]>()
for (const e of nestedIterationEntries) {
const parent = e.parentIterations?.[0]
if (!parent) continue
const key = parent.iterationContainerId
const list = nestedByContainerId.get(key)
if (list) {
list.push(e)
} else {
nestedByContainerId.set(key, [e])
}
}

const subflowNodes: EntryNode[] = []
for (const subflowGroup of subflowGroups.values()) {
const { iterationType, iterationContainerId, groups: iterationGroups } = subflowGroup

const nestedForThisSubflow = nestedIterationEntries.filter((e) => {
const parent = e.parentIterations?.[0]
return parent && parent.iterationContainerId === iterationContainerId
})
const nestedForThisSubflow = nestedByContainerId.get(iterationContainerId) ?? []

const allDirectBlocks = iterationGroups.flatMap((g) => g.blocks)
const allRelevantBlocks = [...allDirectBlocks, ...nestedForThisSubflow]
Expand Down Expand Up @@ -406,12 +416,21 @@ export function buildEntryTree(entries: ConsoleEntry[], idPrefix = ''): EntryNod
iterationContainerId,
}

const nestedByIteration = new Map<number, ConsoleEntry[]>()
for (const e of nestedForThisSubflow) {
const iterNum = e.parentIterations?.[0]?.iterationCurrent
if (iterNum === undefined) continue
const list = nestedByIteration.get(iterNum)
if (list) {
list.push(e)
} else {
nestedByIteration.set(iterNum, [e])
}
}

const iterationNodes: EntryNode[] = iterationGroups
.map((iterGroup): EntryNode | null => {
const matchingNestedEntries = nestedForThisSubflow.filter((e) => {
const parent = e.parentIterations?.[0]
return parent?.iterationCurrent === iterGroup.iterationCurrent
})
const matchingNestedEntries = nestedByIteration.get(iterGroup.iterationCurrent) ?? []

const strippedNestedEntries: ConsoleEntry[] = matchingNestedEntries.map((e) => ({
...e,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ export function useBlockState(
const isDeletedBlock = !isShowingDiff && diffAnalysis?.deleted_blocks?.includes(blockId)

// Execution state
const isActiveBlock = useIsBlockActive(blockId)
const isActive = data.isActive || isActiveBlock
const isActive = useIsBlockActive(blockId)

return {
isEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export interface WorkflowBlockProps {
type: string
config: BlockConfig
name: string
isActive?: boolean
isPending?: boolean
isPreview?: boolean
/** Whether this block is selected in preview mode */
isPreviewSelected?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export function shouldSkipBlockRender(
prevProps.id === nextProps.id &&
prevProps.data.type === nextProps.data.type &&
prevProps.data.name === nextProps.data.name &&
prevProps.data.isActive === nextProps.data.isActive &&
prevProps.data.isPending === nextProps.data.isPending &&
prevProps.data.isPreview === nextProps.data.isPreview &&
prevProps.data.isPreviewSelected === nextProps.data.isPreviewSelected &&
prevProps.data.config === nextProps.data.config &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedule
import { useSkills } from '@/hooks/queries/skills'
import { useTablesList } from '@/hooks/queries/tables'
import { useSelectorDisplayName } from '@/hooks/use-selector-display-name'
import { useIsBlockPending } from '@/stores/execution'
import { useVariablesStore } from '@/stores/panel'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
Expand Down Expand Up @@ -855,7 +856,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
data,
selected,
}: NodeProps<WorkflowBlockProps>) {
const { type, config, name, isPending, isSandbox } = data
const { type, config, name, isSandbox } = data

const contentRef = useRef<HTMLDivElement>(null)

Expand All @@ -873,7 +874,9 @@ export const WorkflowBlock = memo(function WorkflowBlock({
hasRing,
ringStyles,
runPathStatus,
} = useBlockVisual({ blockId: id, data, isPending, isSelected: selected })
} = useBlockVisual({ blockId: id, data, isSelected: selected })

const isPending = useIsBlockPending(id)
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

const currentBlock = currentWorkflow.getBlockById(id)

Expand Down
Loading
Loading