Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useCallback, useEffect, useMemo } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import clsx from 'clsx'
import { useParams, usePathname } from 'next/navigation'
import { FolderItem } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item'
Expand Down Expand Up @@ -144,25 +144,45 @@ export function WorkflowList({
[pathname, workspaceId]
)

// Track last scrolled workflow to avoid redundant scroll checks
const lastScrolledWorkflowRef = useRef<string | null>(null)

/**
* Auto-expand folders and select the active workflow
* Auto-expand folders, select active workflow, and scroll into view if needed.
*/
useEffect(() => {
if (!workflowId || isLoading || foldersLoading) return

// Expand folder path
// Expand folder path to reveal workflow
if (activeWorkflowFolderId) {
const folderPath = getFolderPath(activeWorkflowFolderId)
for (const folder of folderPath) {
setExpanded(folder.id, true)
}
folderPath.forEach((folder) => setExpanded(folder.id, true))
}

// Auto-select active workflow if not already selected
// Select workflow if not already selected
const { selectedWorkflows, selectOnly } = useFolderStore.getState()
if (!selectedWorkflows.has(workflowId)) {
selectOnly(workflowId)
}

// Skip scroll check if already handled for this workflow
if (lastScrolledWorkflowRef.current === workflowId) return
lastScrolledWorkflowRef.current = workflowId

// Scroll after render only if element is completely off-screen
requestAnimationFrame(() => {
const element = document.querySelector(`[data-item-id="${workflowId}"]`)
const container = scrollContainerRef.current
if (!element || !container) return

const { top: elTop, bottom: elBottom } = element.getBoundingClientRect()
const { top: ctTop, bottom: ctBottom } = container.getBoundingClientRect()

// Only scroll if completely above or below the visible area
if (elBottom <= ctTop || elTop >= ctBottom) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
})
}, [workflowId, activeWorkflowFolderId, isLoading, foldersLoading, getFolderPath, setExpanded])
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: Missing scrollContainerRef in dependency array

Suggested change
}, [workflowId, activeWorkflowFolderId, isLoading, foldersLoading, getFolderPath, setExpanded])
}, [workflowId, activeWorkflowFolderId, isLoading, foldersLoading, getFolderPath, setExpanded, scrollContainerRef])
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx
Line: 185:185

Comment:
**syntax:** Missing `scrollContainerRef` in dependency array

```suggestion
  }, [workflowId, activeWorkflowFolderId, isLoading, foldersLoading, getFolderPath, setExpanded, scrollContainerRef])
```

How can I resolve this? If you propose a fix, please make it concise.


const renderWorkflowItem = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,64 +212,24 @@ export function Sidebar() {
// Combined loading state
const isLoading = workflowsLoading || sessionLoading

// Ref to track active timeout IDs for cleanup
const scrollTimeoutRef = useRef<number | null>(null)

/**
* Scrolls an element into view if it's not already visible in the scroll container.
* Uses a retry mechanism with cleanup to wait for the element to be rendered in the DOM.
*
* @param elementId - The ID of the element to scroll to
* @param maxRetries - Maximum number of retry attempts (default: 10)
* Scrolls a newly created element into view if completely off-screen.
* Uses requestAnimationFrame to sync with render, then scrolls.
*/
const scrollToElement = useCallback(
(elementId: string, maxRetries = 10) => {
// Clear any existing timeout
if (scrollTimeoutRef.current !== null) {
clearTimeout(scrollTimeoutRef.current)
scrollTimeoutRef.current = null
}

let attempts = 0

const tryScroll = () => {
attempts++
const element = document.querySelector(`[data-item-id="${elementId}"]`)
const container = scrollContainerRef.current

if (element && container) {
const elementRect = element.getBoundingClientRect()
const containerRect = container.getBoundingClientRect()

// Check if element is not fully visible in the container
const isAboveView = elementRect.top < containerRect.top
const isBelowView = elementRect.bottom > containerRect.bottom

if (isAboveView || isBelowView) {
element.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
}
scrollTimeoutRef.current = null
} else if (attempts < maxRetries) {
// Element not in DOM yet, retry after a short delay
scrollTimeoutRef.current = window.setTimeout(tryScroll, 50)
} else {
scrollTimeoutRef.current = null
}
}

// Start the scroll attempt after a small delay to ensure rendering.
scrollTimeoutRef.current = window.setTimeout(tryScroll, 50)
},
[scrollContainerRef]
)

// Cleanup timeouts on unmount
useEffect(() => {
return () => {
if (scrollTimeoutRef.current !== null) {
clearTimeout(scrollTimeoutRef.current)
const scrollToElement = useCallback((elementId: string) => {
requestAnimationFrame(() => {
const element = document.querySelector(`[data-item-id="${elementId}"]`)
const container = scrollContainerRef.current
if (!element || !container) return

const { top: elTop, bottom: elBottom } = element.getBoundingClientRect()
const { top: ctTop, bottom: ctBottom } = container.getBoundingClientRect()

// Only scroll if element is completely off-screen
if (elBottom <= ctTop || elTop >= ctBottom) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
})
}, [])

/**
Expand Down