Skip to content

v0.5.90: workflow duplication improvements, model allowlist, logs performance#3226

Merged
waleedlatif1 merged 4 commits intomainfrom
staging
Feb 16, 2026
Merged

v0.5.90: workflow duplication improvements, model allowlist, logs performance#3226
waleedlatif1 merged 4 commits intomainfrom
staging

Conversation

@waleedlatif1
Copy link
Collaborator

@waleedlatif1 waleedlatif1 commented Feb 16, 2026

waleedlatif1 and others added 4 commits February 13, 2026 15:16
@vercel
Copy link

vercel bot commented Feb 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Feb 16, 2026 7:30am

Request Review

@waleedlatif1 waleedlatif1 marked this pull request as ready for review February 16, 2026 07:38
@waleedlatif1 waleedlatif1 merged commit e204628 into main Feb 16, 2026
26 of 27 checks passed
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 16, 2026

Greptile Summary

Release PR bundling four areas of improvement: (1) logs performance — stabilizes callbacks with refs, wraps components in memo, adds useReducer for log selection, prefetches log details on hover, and uses structural comparison to prevent cascading re-renders from poll data; (2) workflow duplication — remaps variable IDs in variables-input subblocks during duplication and fixes the variables type from Array to Record in export/import and sanitization; (3) model allowlist — validates combobox default values against available options so disabled providers don't force an invalid default, and consolidates duplicated getModelOptions() into a shared utility; (4) misc fixes — Vertex AI now prioritizes user-provided project/location over env vars, Slack thread_ts renamed to threadTs for camelCase consistency, cron-expression wand generation added, and STRUCTURAL_SUBBLOCK_IDS narrowed to show authMethod/destinationType in tool-input.

  • The logs performance refactor is thorough and well-structured, using refs to break unnecessary dependency chains in useCallback hooks and adding memo wrappers with custom equality functions where appropriate.
  • Variable ID remapping during duplication is a targeted fix that correctly handles variables-input subblocks.
  • The getModelOptions consolidation in blocks/utils.ts is a clean DRY improvement. Note that TranslateBlock now includes vLLM models where it previously did not — verify this is intentional.
  • The sub-block-renderer refactor from bidirectional useEffect sync to zustand subscribe is a meaningful architectural improvement that should eliminate the ping-pong update issue.

Confidence Score: 4/5

  • This PR is safe to merge — the changes are well-structured with no critical bugs, and the performance improvements follow sound React patterns.
  • Score reflects that the changes are largely refactors and performance improvements with correct logic. The two style comments are about React best practices (side-effects in state updaters, ref assignment during render) that won't cause bugs in current usage but could behave unexpectedly under React strict/concurrent mode. No breaking API changes or data corruption risks.
  • apps/sim/app/workspace/[workspaceId]/logs/logs.tsx (large refactor with side-effects in state updater), apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx (complex OAuth rendering order logic)

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/logs/logs.tsx Major refactor: replaces scattered useState with useReducer for log selection, stabilizes callbacks with refs to eliminate re-render cascades. Adds hover-based prefetching and cleanup of timers. Side-effects inside setIsLive updater is a minor anti-pattern but functionally correct.
apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/dashboard.tsx Wraps Dashboard in memo, stabilizes execution objects with structural comparison to prevent cascading re-renders on poll. Uses ref for lastAnchorIndices to remove callback dependency.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx Refactors OAuth account rendering to respect block subblock ordering. Extracts renderSubBlock helper and uses a RenderItem union for consistent ordering of OAuth and sub-block fields.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/tools/sub-block-renderer.tsx Replaces bidirectional useEffect sync with zustand subscribe for tool param syncing. Eliminates ping-pong update issue by subscribing directly to store changes.
apps/sim/lib/workflows/persistence/duplicate.ts Adds variable ID remapping during workflow duplication so that variables-input subblocks point to new variable IDs. Clean implementation with proper null checking.
apps/sim/lib/workflows/operations/import-export.ts Fixes variables type check from Array.isArray to object type check, aligning with the Record-based variable storage format used elsewhere.
apps/sim/blocks/utils.ts Extracts shared getModelOptions() function to eliminate duplicated model option logic across 6+ block definition files.
apps/sim/hooks/queries/logs.ts Adds initialData from list cache for log detail queries, hover-based prefetch function, and conditional refetchInterval callback to avoid polling for completed logs.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx Validates that defaultValue exists in available options before using it. Falls through to other defaults if the model's provider is disabled.
apps/sim/providers/vertex/index.ts Swaps priority order so user-provided vertexProject/vertexLocation takes precedence over env vars. Correct fix for user override support.
apps/sim/tools/params.ts Reduces STRUCTURAL_SUBBLOCK_IDS to only 'operation', removing 'authMethod' and 'destinationType' so they appear as user-facing parameters in tool-input.

Flowchart

flowchart TD
    subgraph LogsPerformance["Logs Performance Optimizations"]
        A["logs.tsx: useReducer + refs"] --> B["Stable callbacks (no dep changes)"]
        B --> C["memo-wrapped children skip re-render"]
        C --> D["LogsToolbar (memo)"]
        C --> E["Dashboard (memo + structural cmp)"]
        C --> F["LogsList + LogRow"]
        C --> G["LogDetails (memo)"]
        E --> H["StatusBar (custom equality)"]
        E --> I["WorkflowsList (memo)"]
    end

    subgraph HoverPrefetch["Hover Prefetch Flow"]
        J["LogRow onMouseEnter"] --> K["prefetchLogDetail(queryClient, logId)"]
        K --> L["React Query cache populated"]
        L --> M["Click → useLogDetail reads initialData from cache"]
    end

    subgraph Duplication["Workflow Duplication Fix"]
        N["Source workflow variables"] --> O["Generate new UUIDs + build varIdMapping"]
        O --> P["remapVariableIdsInSubBlocks"]
        P --> Q["variables-input subblock assignments updated"]
    end

    subgraph ModelAllowlist["Model Allowlist"]
        R["ComboBox defaultValue"] --> S{"defaultValue in evaluatedOptions?"}
        S -- Yes --> T["Use defaultValue"]
        S -- No --> U["Fall through to claude-sonnet-4-5 or first option"]
    end
Loading

Last reviewed commit: 92b4f77

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

37 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 363 to +379
const handleToggleLive = useCallback(() => {
const newIsLive = !isLive
setIsLive(newIsLive)

if (newIsLive) {
setIsVisuallyRefreshing(true)
setTimeout(() => setIsVisuallyRefreshing(false), REFRESH_SPINNER_DURATION_MS)
logsQuery.refetch()
if (selectedLogId) {
activeLogQuery.refetch()
setIsLive((prev) => {
if (!prev) {
setIsVisuallyRefreshing(true)
const timerId = window.setTimeout(() => {
setIsVisuallyRefreshing(false)
refreshTimersRef.current.delete(timerId)
}, REFRESH_SPINNER_DURATION_MS)
refreshTimersRef.current.add(timerId)
logsRefetchRef.current()
if (selectedLogIdRef.current) {
activeLogRefetchRef.current()
}
}
}
}, [isLive, logsQuery, activeLogQuery, selectedLogId])
return !prev
})
}, [])
Copy link
Contributor

Choose a reason for hiding this comment

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

Side-effects inside state updater

Calling setIsVisuallyRefreshing, window.setTimeout, and logsRefetchRef.current() inside the setIsLive updater is a side-effect within a React state updater function. While this works in practice because React batches the updates, state updaters are expected to be pure functions — React may call them more than once in strict mode or concurrent features, which would trigger duplicate timers and refetches.

Consider extracting the side-effects outside the updater:

Suggested change
const handleToggleLive = useCallback(() => {
const newIsLive = !isLive
setIsLive(newIsLive)
if (newIsLive) {
setIsVisuallyRefreshing(true)
setTimeout(() => setIsVisuallyRefreshing(false), REFRESH_SPINNER_DURATION_MS)
logsQuery.refetch()
if (selectedLogId) {
activeLogQuery.refetch()
setIsLive((prev) => {
if (!prev) {
setIsVisuallyRefreshing(true)
const timerId = window.setTimeout(() => {
setIsVisuallyRefreshing(false)
refreshTimersRef.current.delete(timerId)
}, REFRESH_SPINNER_DURATION_MS)
refreshTimersRef.current.add(timerId)
logsRefetchRef.current()
if (selectedLogIdRef.current) {
activeLogRefetchRef.current()
}
}
}
}, [isLive, logsQuery, activeLogQuery, selectedLogId])
return !prev
})
}, [])
const handleToggleLive = useCallback(() => {
setIsLive((prev) => !prev)
}, [])
const isLiveRef = useRef(isLive)
useEffect(() => {
const wasLive = isLiveRef.current
isLiveRef.current = isLive
if (isLive && !wasLive) {
setIsVisuallyRefreshing(true)
const timerId = window.setTimeout(() => {
setIsVisuallyRefreshing(false)
refreshTimersRef.current.delete(timerId)
}, REFRESH_SPINNER_DURATION_MS)
refreshTimersRef.current.add(timerId)
logsRefetchRef.current()
if (selectedLogIdRef.current) {
activeLogRefetchRef.current()
}
}
}, [isLive])

const [storeValue, setStoreValue] = useSubBlockValue(blockId, syntheticId)

const toolParamValue = toolParams?.[effectiveParamId] ?? ''
const isObjectType = OBJECT_SUBBLOCK_TYPES.has(subBlock.type)
Copy link
Contributor

Choose a reason for hiding this comment

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

Mutable ref assigned during render

Assigning onParamChangeRef.current = onParamChange during render (outside a useEffect) is a side-effect during render. In React concurrent mode or strict mode, render functions may be called multiple times before commit, which can lead to stale or inconsistent ref values.

Suggested change
const isObjectType = OBJECT_SUBBLOCK_TYPES.has(subBlock.type)
const onParamChangeRef = useRef(onParamChange)
useEffect(() => {
onParamChangeRef.current = onParamChange
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants