@@ -679,6 +679,11 @@ export class WorkspaceStore {
679679
680680 // Cache sidebar state objects to return stable references
681681 private sidebarStateCache = new Map < string , WorkspaceSidebarState > ( ) ;
682+ // Map from workspaceId -> the WorkspaceState reference used to compute sidebarStateCache.
683+ // React's useSyncExternalStore may call getSnapshot() multiple times per render; this
684+ // ensures getWorkspaceSidebarState() returns a referentially stable snapshot for a given
685+ // MapStore version even when timingStats would otherwise change via Date.now().
686+ private sidebarStateSourceState = new Map < string , WorkspaceState > ( ) ;
682687
683688 /**
684689 * Get sidebar state for a workspace (subset of full state).
@@ -687,6 +692,12 @@ export class WorkspaceStore {
687692 */
688693 getWorkspaceSidebarState ( workspaceId : string ) : WorkspaceSidebarState {
689694 const fullState = this . getWorkspaceState ( workspaceId ) ;
695+
696+ const cached = this . sidebarStateCache . get ( workspaceId ) ;
697+ if ( cached && this . sidebarStateSourceState . get ( workspaceId ) === fullState ) {
698+ return cached ;
699+ }
700+
690701 const aggregator = this . aggregators . get ( workspaceId ) ;
691702
692703 // Get timing stats: prefer active stream, fall back to last completed
@@ -711,9 +722,7 @@ export class WorkspaceStore {
711722 // Get session-level aggregate stats
712723 const sessionStats = aggregator ?. getSessionTimingStats ( ) ?? null ;
713724
714- const cached = this . sidebarStateCache . get ( workspaceId ) ;
715-
716- // Return cached if values match (timing stats checked by reference since they change frequently)
725+ // Return cached if values match.
717726 if (
718727 cached &&
719728 cached . canInterrupt === fullState . canInterrupt &&
@@ -728,6 +737,9 @@ export class WorkspaceStore {
728737 ( cached . sessionStats ?. totalDurationMs === sessionStats ?. totalDurationMs &&
729738 cached . sessionStats ?. responseCount === sessionStats ?. responseCount ) )
730739 ) {
740+ // Even if we re-use the cached object, mark it as derived from the current
741+ // WorkspaceState so repeated getSnapshot() reads during this render are stable.
742+ this . sidebarStateSourceState . set ( workspaceId , fullState ) ;
731743 return cached ;
732744 }
733745
@@ -742,6 +754,7 @@ export class WorkspaceStore {
742754 sessionStats,
743755 } ;
744756 this . sidebarStateCache . set ( workspaceId , newState ) ;
757+ this . sidebarStateSourceState . set ( workspaceId , fullState ) ;
745758 return newState ;
746759 }
747760
@@ -1143,6 +1156,7 @@ export class WorkspaceStore {
11431156 this . recencyCache . delete ( workspaceId ) ;
11441157 this . previousSidebarValues . delete ( workspaceId ) ;
11451158 this . sidebarStateCache . delete ( workspaceId ) ;
1159+ this . sidebarStateSourceState . delete ( workspaceId ) ;
11461160 this . workspaceCreatedAt . delete ( workspaceId ) ;
11471161 this . workspaceStats . delete ( workspaceId ) ;
11481162 this . statsStore . delete ( workspaceId ) ;
0 commit comments