* Get sidebar state for a workspace (subset of full state). * Returns cached reference if values haven't changed. * This is critical for useSyncExternalStore - must return stable references.
(workspaceId: string)
| 2086 | * This is critical for useSyncExternalStore - must return stable references. |
| 2087 | */ |
| 2088 | getWorkspaceSidebarState(workspaceId: string): WorkspaceSidebarState { |
| 2089 | const fullState = this.getWorkspaceState(workspaceId); |
| 2090 | const isStarting = fullState.isStreamStarting; |
| 2091 | const terminalActivity = this.workspaceTerminalActivity.get(workspaceId); |
| 2092 | const terminalActiveCount = terminalActivity?.activeCount ?? 0; |
| 2093 | const terminalSessionCount = terminalActivity?.totalSessions ?? 0; |
| 2094 | |
| 2095 | const cached = this.sidebarStateCache.get(workspaceId); |
| 2096 | if (cached && this.sidebarStateSourceState.get(workspaceId) === fullState) { |
| 2097 | return cached; |
| 2098 | } |
| 2099 | |
| 2100 | // Return cached if values match. |
| 2101 | // Note: timingStats/sessionStats are intentionally excluded - they change on every |
| 2102 | // streaming token and sidebar items don't use them. Components needing timing should |
| 2103 | // use useWorkspaceStatsSnapshot() which has its own subscription. |
| 2104 | if ( |
| 2105 | cached?.canInterrupt === fullState.canInterrupt && |
| 2106 | cached.isStarting === isStarting && |
| 2107 | cached.awaitingUserQuestion === fullState.awaitingUserQuestion && |
| 2108 | cached.lastAbortReason === fullState.lastAbortReason && |
| 2109 | cached.currentModel === fullState.currentModel && |
| 2110 | cached.pendingStreamModel === fullState.pendingStreamModel && |
| 2111 | cached.recencyTimestamp === fullState.recencyTimestamp && |
| 2112 | cached.loadedSkills === fullState.loadedSkills && |
| 2113 | cached.skillLoadErrors === fullState.skillLoadErrors && |
| 2114 | cached.agentStatus === fullState.agentStatus && |
| 2115 | cached.activeWorkflowRunCount === fullState.activeWorkflowRunCount && |
| 2116 | cached.terminalActiveCount === terminalActiveCount && |
| 2117 | cached.terminalSessionCount === terminalSessionCount && |
| 2118 | cached.goal === fullState.goal |
| 2119 | ) { |
| 2120 | // Even if we re-use the cached object, mark it as derived from the current |
| 2121 | // WorkspaceState so repeated getSnapshot() reads during this render are stable. |
| 2122 | this.sidebarStateSourceState.set(workspaceId, fullState); |
| 2123 | return cached; |
| 2124 | } |
| 2125 | |
| 2126 | // Create and cache new state |
| 2127 | const newState: WorkspaceSidebarState = { |
| 2128 | canInterrupt: fullState.canInterrupt, |
| 2129 | isStarting, |
| 2130 | awaitingUserQuestion: fullState.awaitingUserQuestion, |
| 2131 | lastAbortReason: fullState.lastAbortReason, |
| 2132 | currentModel: fullState.currentModel, |
| 2133 | pendingStreamModel: fullState.pendingStreamModel, |
| 2134 | recencyTimestamp: fullState.recencyTimestamp, |
| 2135 | loadedSkills: fullState.loadedSkills, |
| 2136 | skillLoadErrors: fullState.skillLoadErrors, |
| 2137 | agentStatus: fullState.agentStatus, |
| 2138 | activeWorkflowRunCount: fullState.activeWorkflowRunCount, |
| 2139 | terminalActiveCount, |
| 2140 | terminalSessionCount, |
| 2141 | goal: fullState.goal, |
| 2142 | }; |
| 2143 | this.sidebarStateCache.set(workspaceId, newState); |
| 2144 | this.sidebarStateSourceState.set(workspaceId, fullState); |
| 2145 | return newState; |