@@ -9,7 +9,7 @@ import { getUserDisplayName } from "@hooks/useUsers";
99import { filtersMatch } from "@lib/filters" ;
1010import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore" ;
1111import type { RegisteredFolder , Task , Workspace } from "@shared/types" ;
12- import { useEffect } from "react" ;
12+ import { useEffect , useMemo } from "react" ;
1313import { useWorkspaceStore } from "@/renderer/features/workspace/stores/workspaceStore" ;
1414import {
1515 getTaskRepository ,
@@ -339,94 +339,138 @@ export function useSidebarData({
339339 syncFolderOrder ( folderIds ) ;
340340 } , [ syncFolderOrder , folderIds ] ) ;
341341
342- // Sort folders by persisted order
343- const sortedFolders = sortFoldersByOrder ( folders , folderOrder ) ;
344- const tasksByFolder = groupTasksByFolder ( allTasks , folders , workspaces ) ;
345-
346342 const activeTaskId =
347343 activeView . type === "task-detail" && activeView . data
348344 ? activeView . data . id
349345 : null ;
350346
351- const getSessionForTask = ( taskId : string ) : AgentSession | undefined => {
352- return Object . values ( sessions ) . find ( ( s ) => s . taskId === taskId ) ;
353- } ;
354-
355- const folderData : FolderData [ ] = sortedFolders . map ( ( folder ) => {
356- const folderTasks = tasksByFolder . get ( folder . id ) || [ ] ;
357-
358- const tasksWithActivity = folderTasks . map ( ( task ) => {
359- const session = getSessionForTask ( task . id ) ;
360- // Use max of task.updated_at and local activity timestamp for accurate ordering
361- const apiUpdatedAt = new Date ( task . updated_at ) . getTime ( ) ;
362- const localActivity = localActivityAt [ task . id ] ;
363- const lastActivityAt = localActivity
364- ? Math . max ( apiUpdatedAt , localActivity )
365- : apiUpdatedAt ;
366- const isPinned = pinnedTaskIds . has ( task . id ) ;
367- return {
368- task,
369- lastActivityAt,
370- isGenerating : session ?. isPromptPending ?? false ,
371- isPinned,
372- } ;
373- } ) ;
347+ // Memoize sorted folders to maintain stable reference
348+ const sortedFolders = useMemo (
349+ ( ) => sortFoldersByOrder ( folders , folderOrder ) ,
350+ [ folders , folderOrder ] ,
351+ ) ;
374352
375- // Sort by pinned first, then by most recent activity
376- tasksWithActivity . sort ( ( a , b ) => {
377- // Pinned tasks come first
378- if ( a . isPinned && ! b . isPinned ) return - 1 ;
379- if ( ! a . isPinned && b . isPinned ) return 1 ;
380- // Then sort by most recent activity
381- return b . lastActivityAt - a . lastActivityAt ;
382- } ) ;
353+ // Memoize tasks grouped by folder to maintain stable reference
354+ const tasksByFolder = useMemo (
355+ ( ) => groupTasksByFolder ( allTasks , folders , workspaces ) ,
356+ [ allTasks , folders , workspaces ] ,
357+ ) ;
383358
384- return {
385- id : folder . id ,
386- name : folder . name ,
387- path : folder . path ,
388- tasks : tasksWithActivity . map (
389- ( { task, lastActivityAt, isGenerating, isPinned } ) => {
390- const taskLastViewedAt = lastViewedAt [ task . id ] ;
391- const isCurrentlyViewing = activeTaskId === task . id ;
392- // Only show unread if: user has viewed it before AND there's new activity since
393- const isUnread =
394- ! isCurrentlyViewing &&
395- taskLastViewedAt !== undefined &&
396- lastActivityAt > taskLastViewedAt ;
397-
398- return {
399- id : task . id ,
400- title : task . title ,
401- lastActivityAt,
402- isGenerating,
403- isUnread,
404- isPinned,
405- } ;
406- } ,
407- ) ,
359+ // Memoize folder data to prevent unnecessary re-renders in consumers
360+ const folderData : FolderData [ ] = useMemo ( ( ) => {
361+ const getSessionForTask = ( taskId : string ) : AgentSession | undefined => {
362+ return Object . values ( sessions ) . find ( ( s ) => s . taskId === taskId ) ;
408363 } ;
409- } ) ;
410364
411- const historyData = buildHistoryData (
412- allTasks ,
413- workspaces ,
414- folders ,
365+ return sortedFolders . map ( ( folder ) => {
366+ const folderTasks = tasksByFolder . get ( folder . id ) || [ ] ;
367+
368+ const tasksWithActivity = folderTasks . map ( ( task ) => {
369+ const session = getSessionForTask ( task . id ) ;
370+ // Use max of task.updated_at and local activity timestamp for accurate ordering
371+ const apiUpdatedAt = new Date ( task . updated_at ) . getTime ( ) ;
372+ const localActivity = localActivityAt [ task . id ] ;
373+ const lastActivityAt = localActivity
374+ ? Math . max ( apiUpdatedAt , localActivity )
375+ : apiUpdatedAt ;
376+ const isPinned = pinnedTaskIds . has ( task . id ) ;
377+ return {
378+ task,
379+ lastActivityAt,
380+ isGenerating : session ?. isPromptPending ?? false ,
381+ isPinned,
382+ } ;
383+ } ) ;
384+
385+ // Sort by pinned first, then by most recent activity
386+ tasksWithActivity . sort ( ( a , b ) => {
387+ // Pinned tasks come first
388+ if ( a . isPinned && ! b . isPinned ) return - 1 ;
389+ if ( ! a . isPinned && b . isPinned ) return 1 ;
390+ // Then sort by most recent activity
391+ return b . lastActivityAt - a . lastActivityAt ;
392+ } ) ;
393+
394+ return {
395+ id : folder . id ,
396+ name : folder . name ,
397+ path : folder . path ,
398+ tasks : tasksWithActivity . map (
399+ ( { task, lastActivityAt, isGenerating, isPinned } ) => {
400+ const taskLastViewedAt = lastViewedAt [ task . id ] ;
401+ const isCurrentlyViewing = activeTaskId === task . id ;
402+ // Only show unread if: user has viewed it before AND there's new activity since
403+ const isUnread =
404+ ! isCurrentlyViewing &&
405+ taskLastViewedAt !== undefined &&
406+ lastActivityAt > taskLastViewedAt ;
407+
408+ return {
409+ id : task . id ,
410+ title : task . title ,
411+ lastActivityAt,
412+ isGenerating,
413+ isUnread,
414+ isPinned,
415+ } ;
416+ } ,
417+ ) ,
418+ } ;
419+ } ) ;
420+ } , [
421+ sortedFolders ,
422+ tasksByFolder ,
415423 sessions ,
416- lastViewedAt ,
417424 localActivityAt ,
418425 pinnedTaskIds ,
426+ lastViewedAt ,
419427 activeTaskId ,
420- historyVisibleCount ,
428+ ] ) ;
429+
430+ const historyData = useMemo (
431+ ( ) =>
432+ buildHistoryData (
433+ allTasks ,
434+ workspaces ,
435+ folders ,
436+ sessions ,
437+ lastViewedAt ,
438+ localActivityAt ,
439+ pinnedTaskIds ,
440+ activeTaskId ,
441+ historyVisibleCount ,
442+ ) ,
443+ [
444+ allTasks ,
445+ workspaces ,
446+ folders ,
447+ sessions ,
448+ lastViewedAt ,
449+ localActivityAt ,
450+ pinnedTaskIds ,
451+ activeTaskId ,
452+ historyVisibleCount ,
453+ ] ,
421454 ) ;
422455
423- const pinnedData = buildPinnedData (
424- allTasks ,
425- sessions ,
426- lastViewedAt ,
427- localActivityAt ,
428- pinnedTaskIds ,
429- activeTaskId ,
456+ const pinnedData = useMemo (
457+ ( ) =>
458+ buildPinnedData (
459+ allTasks ,
460+ sessions ,
461+ lastViewedAt ,
462+ localActivityAt ,
463+ pinnedTaskIds ,
464+ activeTaskId ,
465+ ) ,
466+ [
467+ allTasks ,
468+ sessions ,
469+ lastViewedAt ,
470+ localActivityAt ,
471+ pinnedTaskIds ,
472+ activeTaskId ,
473+ ] ,
430474 ) ;
431475
432476 return {
0 commit comments