11'use client'
22
3- import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
3+ import { memo , useCallback , useEffect , useMemo , useRef , useState } from 'react'
44import { Loader2 } from 'lucide-react'
55import { Skeleton } from '@/components/ui/skeleton'
66import { formatLatency } from '@/app/workspace/[workspaceId]/logs/utils'
@@ -141,31 +141,90 @@ function toWorkflowExecution(wf: WorkflowStats): WorkflowExecution {
141141 }
142142}
143143
144- export default function Dashboard ( { stats, isLoading, error } : DashboardProps ) {
144+ function DashboardInner ( { stats, isLoading, error } : DashboardProps ) {
145145 const [ selectedSegments , setSelectedSegments ] = useState < Record < string , number [ ] > > ( { } )
146146 const [ lastAnchorIndices , setLastAnchorIndices ] = useState < Record < string , number > > ( { } )
147- const barsAreaRef = useRef < HTMLDivElement | null > ( null )
147+ const lastAnchorIndicesRef = useRef < Record < string , number > > ( { } )
148148
149149 const { workflowIds, searchQuery, toggleWorkflowId, timeRange } = useFilterStore ( )
150150
151151 const allWorkflows = useWorkflowRegistry ( ( state ) => state . workflows )
152152
153153 const expandedWorkflowId = workflowIds . length === 1 ? workflowIds [ 0 ] : null
154154
155- const { executions , aggregateSegments, segmentMs } = useMemo ( ( ) => {
155+ const { rawExecutions , aggregateSegments, segmentMs } = useMemo ( ( ) => {
156156 if ( ! stats ) {
157- return { executions : [ ] , aggregateSegments : [ ] , segmentMs : 0 }
157+ return { rawExecutions : [ ] , aggregateSegments : [ ] , segmentMs : 0 }
158158 }
159159
160- const workflowExecutions = stats . workflows . map ( toWorkflowExecution )
161-
162160 return {
163- executions : workflowExecutions ,
161+ rawExecutions : stats . workflows . map ( toWorkflowExecution ) ,
164162 aggregateSegments : stats . aggregateSegments ,
165163 segmentMs : stats . segmentMs ,
166164 }
167165 } , [ stats ] )
168166
167+ /**
168+ * Stabilize execution objects: reuse previous references for workflows
169+ * whose segment data hasn't structurally changed between polls.
170+ * This prevents cascading re-renders through WorkflowsList → StatusBar.
171+ */
172+ const prevExecutionsRef = useRef < WorkflowExecution [ ] > ( [ ] )
173+
174+ const executions = useMemo ( ( ) => {
175+ const prevMap = new Map ( prevExecutionsRef . current . map ( ( e ) => [ e . workflowId , e ] ) )
176+ let anyChanged = false
177+
178+ const result = rawExecutions . map ( ( exec ) => {
179+ const prev = prevMap . get ( exec . workflowId )
180+ if ( ! prev ) {
181+ anyChanged = true
182+ return exec
183+ }
184+ if (
185+ prev . overallSuccessRate !== exec . overallSuccessRate ||
186+ prev . workflowName !== exec . workflowName ||
187+ prev . segments . length !== exec . segments . length
188+ ) {
189+ anyChanged = true
190+ return exec
191+ }
192+
193+ for ( let i = 0 ; i < prev . segments . length ; i ++ ) {
194+ const ps = prev . segments [ i ]
195+ const ns = exec . segments [ i ]
196+ if (
197+ ps . totalExecutions !== ns . totalExecutions ||
198+ ps . successfulExecutions !== ns . successfulExecutions ||
199+ ps . timestamp !== ns . timestamp ||
200+ ps . avgDurationMs !== ns . avgDurationMs ||
201+ ps . p50Ms !== ns . p50Ms ||
202+ ps . p90Ms !== ns . p90Ms ||
203+ ps . p99Ms !== ns . p99Ms
204+ ) {
205+ anyChanged = true
206+ return exec
207+ }
208+ }
209+
210+ return prev
211+ } )
212+
213+ if (
214+ ! anyChanged &&
215+ result . length === prevExecutionsRef . current . length &&
216+ result . every ( ( r , i ) => r === prevExecutionsRef . current [ i ] )
217+ ) {
218+ return prevExecutionsRef . current
219+ }
220+
221+ return result
222+ } , [ rawExecutions ] )
223+
224+ useEffect ( ( ) => {
225+ prevExecutionsRef . current = executions
226+ } , [ executions ] )
227+
169228 const lastExecutionByWorkflow = useMemo ( ( ) => {
170229 const map = new Map < string , number > ( )
171230 for ( const wf of executions ) {
@@ -312,6 +371,8 @@ export default function Dashboard({ stats, isLoading, error }: DashboardProps) {
312371 [ toggleWorkflowId ]
313372 )
314373
374+ lastAnchorIndicesRef . current = lastAnchorIndices
375+
315376 /**
316377 * Handles segment click for selecting time segments.
317378 * @param workflowId - The workflow containing the segment
@@ -361,7 +422,7 @@ export default function Dashboard({ stats, isLoading, error }: DashboardProps) {
361422 } else if ( mode === 'range' ) {
362423 setSelectedSegments ( ( prev ) => {
363424 const currentSegments = prev [ workflowId ] || [ ]
364- const anchor = lastAnchorIndices [ workflowId ] ?? segmentIndex
425+ const anchor = lastAnchorIndicesRef . current [ workflowId ] ?? segmentIndex
365426 const [ start , end ] =
366427 anchor < segmentIndex ? [ anchor , segmentIndex ] : [ segmentIndex , anchor ]
367428 const range = Array . from ( { length : end - start + 1 } , ( _ , i ) => start + i )
@@ -370,12 +431,12 @@ export default function Dashboard({ stats, isLoading, error }: DashboardProps) {
370431 } )
371432 }
372433 } ,
373- [ lastAnchorIndices ]
434+ [ ]
374435 )
375436
376437 useEffect ( ( ) => {
377- setSelectedSegments ( { } )
378- setLastAnchorIndices ( { } )
438+ setSelectedSegments ( ( prev ) => ( Object . keys ( prev ) . length > 0 ? { } : prev ) )
439+ setLastAnchorIndices ( ( prev ) => ( Object . keys ( prev ) . length > 0 ? { } : prev ) )
379440 } , [ stats , timeRange , workflowIds , searchQuery ] )
380441
381442 if ( isLoading ) {
@@ -493,7 +554,7 @@ export default function Dashboard({ stats, isLoading, error }: DashboardProps) {
493554 </ div >
494555 </ div >
495556
496- < div className = 'min-h-0 flex-1 overflow-hidden' ref = { barsAreaRef } >
557+ < div className = 'min-h-0 flex-1 overflow-hidden' >
497558 < WorkflowsList
498559 filteredExecutions = { filteredExecutions as WorkflowExecution [ ] }
499560 expandedWorkflowId = { expandedWorkflowId }
@@ -507,3 +568,5 @@ export default function Dashboard({ stats, isLoading, error }: DashboardProps) {
507568 </ div >
508569 )
509570}
571+
572+ export default memo ( DashboardInner )
0 commit comments