Skip to content

Commit 4ebbf7a

Browse files
committed
refactor(ui): lazy-init useRef containers the nested-generic pattern missed
Allocate Map/Set ref containers once instead of on every render, and drop the redundant processedRemovalIds alias in the toast provider.
1 parent c9c629a commit 4ebbf7a

21 files changed

Lines changed: 223 additions & 190 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ afterEach(() => {
4141

4242
describe('claimPermissionResponse', () => {
4343
it('allows one response per request id across effect recreation', () => {
44-
const handledRequestIds = { current: new Set<string>() }
44+
const handledRequestIds = new Set<string>()
4545

4646
expect(claimPermissionResponse(handledRequestIds, 'request-1')).toBe(true)
4747
expect(claimPermissionResponse(handledRequestIds, 'request-1')).toBe(false)
@@ -63,7 +63,7 @@ describe('browser permission prompt', () => {
6363
}
6464

6565
it('blocks replaced and unmounted requests once without overriding an explicit answer', () => {
66-
const handledRequestIds = { current: new Set<string>() }
66+
const handledRequestIds = new Set<string>()
6767
const responses = vi.fn()
6868
const onDecision = (
6969
requestId: string,

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,15 @@ const MAX_HANDLED_PERMISSION_REQUESTS = 256
193193

194194
/** Claims the one renderer response allowed for a native browser permission request. */
195195
export function claimPermissionResponse(
196-
handledRequestIds: { current: Set<string> },
196+
handledRequestIds: Set<string>,
197197
requestId: string
198198
): boolean {
199-
if (handledRequestIds.current.has(requestId)) return false
200-
handledRequestIds.current.add(requestId)
201-
while (handledRequestIds.current.size > MAX_HANDLED_PERMISSION_REQUESTS) {
202-
const oldest = handledRequestIds.current.values().next().value
199+
if (handledRequestIds.has(requestId)) return false
200+
handledRequestIds.add(requestId)
201+
while (handledRequestIds.size > MAX_HANDLED_PERMISSION_REQUESTS) {
202+
const oldest = handledRequestIds.values().next().value
203203
if (typeof oldest !== 'string') break
204-
handledRequestIds.current.delete(oldest)
204+
handledRequestIds.delete(oldest)
205205
}
206206
return true
207207
}
@@ -449,7 +449,8 @@ export function BrowserSession({
449449
const toolbarMenuButtonRef = useRef<HTMLButtonElement>(null)
450450
const omniboxFocusRafRef = useRef<number | null>(null)
451451
const omniboxPointerSelectionRef = useRef<OmniboxPointerSelection | null>(null)
452-
const handledPermissionRequestIdsRef = useRef<Set<string>>(new Set())
452+
const handledPermissionRequestIdsRef = useRef<Set<string> | null>(null)
453+
const handledPermissionRequestIds = (handledPermissionRequestIdsRef.current ??= new Set())
453454
const [answeredPermissionRequestId, setAnsweredPermissionRequestId] = useState<string | null>(
454455
null
455456
)
@@ -505,7 +506,7 @@ export function BrowserSession({
505506
action: ReturnType<typeof browserPermissionResponseAction>,
506507
allowed: boolean
507508
) => {
508-
if (!claimPermissionResponse(handledPermissionRequestIdsRef, requestId)) {
509+
if (!claimPermissionResponse(handledPermissionRequestIds, requestId)) {
509510
return
510511
}
511512
setAnsweredPermissionRequestId(requestId)

‎apps/sim/app/workspace/[workspaceId]/home/hooks/preview/use-file-preview-controller.ts‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,21 @@ export function useFilePreviewController({
5959
}: FilePreviewControllerDeps) {
6060
const queryClient = useQueryClient()
6161

62-
const previewActivationOwnerRef = useRef<Map<string, string | null>>(new Map())
63-
const completedPreviewResourceHandoffRef = useRef<
64-
Map<string, { sessionId: string; suppressActivation: boolean }>
65-
>(new Map())
62+
const previewActivationOwnerRef = useRef<Map<string, string | null> | null>(null)
63+
const previewActivationOwners = (previewActivationOwnerRef.current ??= new Map())
64+
const completedPreviewResourceHandoffRef = useRef<Map<
65+
string,
66+
{ sessionId: string; suppressActivation: boolean }
67+
> | null>(null)
68+
const completedPreviewResourceHandoffs = (completedPreviewResourceHandoffRef.current ??=
69+
new Map())
6670

6771
const rememberPreviewActivationOwner = useCallback(
6872
(session: FilePreviewSession) => {
69-
if (!session.fileId || previewActivationOwnerRef.current.has(session.id)) {
73+
if (!session.fileId || previewActivationOwners.has(session.id)) {
7074
return
7175
}
72-
previewActivationOwnerRef.current.set(session.id, activeResourceIdRef.current)
76+
previewActivationOwners.set(session.id, activeResourceIdRef.current)
7377
},
7478
[activeResourceIdRef]
7579
)
@@ -81,7 +85,7 @@ export function useFilePreviewController({
8185
}
8286
if (onResourceEventRef.current) return true
8387
const currentActiveResourceId = activeResourceIdRef.current
84-
const activationOwnerId = previewActivationOwnerRef.current.get(session.id)
88+
const activationOwnerId = previewActivationOwners.get(session.id)
8589
return (
8690
currentActiveResourceId === null ||
8791
currentActiveResourceId ===
@@ -219,8 +223,8 @@ export function useFilePreviewController({
219223

220224
const resetEphemeralPreviewState = useCallback(
221225
(options?: { removeStreamingResource?: boolean }) => {
222-
previewActivationOwnerRef.current.clear()
223-
completedPreviewResourceHandoffRef.current.clear()
226+
previewActivationOwners.clear()
227+
completedPreviewResourceHandoffs.clear()
224228
latestPreviewTargetToolCallIdRef.current = null
225229
syncPreviewSessionRefs(INITIAL_FILE_PREVIEW_SESSIONS_STATE)
226230
resetPreviewSessions()
@@ -380,7 +384,7 @@ export function useFilePreviewController({
380384
if (shouldActivateOnComplete) {
381385
requestResourceAttention(fileId)
382386
}
383-
completedPreviewResourceHandoffRef.current.set(fileId, {
387+
completedPreviewResourceHandoffs.set(fileId, {
384388
sessionId: nextSession.id,
385389
suppressActivation: !shouldActivateOnComplete,
386390
})

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/handle-resource-event.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export function handleResourceEvent(ctx: StreamLoopContext, parsed: ResourceEven
224224

225225
const completedPreviewHandoff =
226226
resource.type === 'file'
227-
? completedPreviewResourceHandoffRef.current.get(resource.id)
227+
? completedPreviewResourceHandoffRef.current?.get(resource.id)
228228
: undefined
229229
const matchingPreviewSessions =
230230
resource.type === 'file'
@@ -249,8 +249,8 @@ export function handleResourceEvent(ctx: StreamLoopContext, parsed: ResourceEven
249249
(!latestActivePreviewForResource ||
250250
latestActivePreviewForResource.id === completedPreviewHandoff.sessionId)
251251
if (completedPreviewHandoff && !isCompletedPreviewHandoffCurrent) {
252-
completedPreviewResourceHandoffRef.current.delete(resource.id)
253-
previewActivationOwnerRef.current.delete(completedPreviewHandoff.sessionId)
252+
completedPreviewResourceHandoffRef.current?.delete(resource.id)
253+
previewActivationOwnerRef.current?.delete(completedPreviewHandoff.sessionId)
254254
}
255255
const shouldSuppressFileResourceActivation =
256256
(isCompletedPreviewHandoffCurrent && completedPreviewHandoff?.suppressActivation === true) ||
@@ -284,8 +284,8 @@ export function handleResourceEvent(ctx: StreamLoopContext, parsed: ResourceEven
284284
)
285285
}
286286
if (completedPreviewHandoff && isCompletedPreviewHandoffCurrent) {
287-
completedPreviewResourceHandoffRef.current.delete(resource.id)
288-
previewActivationOwnerRef.current.delete(completedPreviewHandoff.sessionId)
287+
completedPreviewResourceHandoffRef.current?.delete(resource.id)
288+
previewActivationOwnerRef.current?.delete(completedPreviewHandoff.sessionId)
289289
}
290290
if (pinnedViewId) {
291291
// Carry the newest pin on an existing tab so a remount adopts it. Not gated

‎apps/sim/app/workspace/[workspaceId]/home/hooks/stream/stream-context.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,11 @@ export interface StreamLoopDeps {
182182
previewSessionsRef: MutableRefObject<Record<string, FilePreviewSession>>
183183
latestPreviewTargetToolCallIdRef: MutableRefObject<string | null>
184184
activePreviewSessionIdRef: MutableRefObject<string | null>
185-
completedPreviewResourceHandoffRef: MutableRefObject<
186-
Map<string, { sessionId: string; suppressActivation: boolean }>
187-
>
188-
previewActivationOwnerRef: MutableRefObject<Map<string, string | null>>
185+
completedPreviewResourceHandoffRef: MutableRefObject<Map<
186+
string,
187+
{ sessionId: string; suppressActivation: boolean }
188+
> | null>
189+
previewActivationOwnerRef: MutableRefObject<Map<string, string | null> | null>
189190
}
190191

191192
export interface StreamLoopOps {

‎apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts‎

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,8 @@ export function useChat(
721721
const clearQueueDispatchState = useCallback(() => {
722722
queueDispatchEpochRef.current++
723723
queueDispatchActionsRef.current = []
724-
queuedMessageDispatchIdsRef.current.clear()
725-
userRemovedDuringDispatchRef.current.clear()
724+
queuedMessageDispatchIds.clear()
725+
userRemovedDuringDispatch.clear()
726726
queueDispatchTaskRef.current = null
727727
setDispatchingHeadId(null)
728728
}, [])
@@ -868,11 +868,13 @@ export function useChat(
868868
)
869869
const editingQueuedId = useMothershipQueueStore((state) => state.editing[chatKey] ?? null)
870870
const [dispatchingHeadId, setDispatchingHeadId] = useState<string | null>(null)
871-
const queuedMessageDispatchIdsRef = useRef<Set<string>>(new Set())
871+
const queuedMessageDispatchIdsRef = useRef<Set<string> | null>(null)
872+
const queuedMessageDispatchIds = (queuedMessageDispatchIdsRef.current ??= new Set())
872873
// Ids the user explicitly removed while a dispatch was in flight — used to
873874
// suppress the dispatch's failure-restore path, which would otherwise undo
874875
// the user's removal silently.
875-
const userRemovedDuringDispatchRef = useRef<Set<string>>(new Set())
876+
const userRemovedDuringDispatchRef = useRef<Set<string> | null>(null)
877+
const userRemovedDuringDispatch = (userRemovedDuringDispatchRef.current ??= new Set())
876878
const queueDispatchActionsRef = useRef<QueueDispatchAction[]>([])
877879
const queueDispatchTaskRef = useRef<Promise<void> | null>(null)
878880
const queueDispatchEpochRef = useRef(0)
@@ -921,7 +923,9 @@ export function useChat(
921923
const reconnectExhaustedRecheckTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
922924

923925
const abortControllerRef = useRef<AbortController | null>(null)
924-
const detachedChatResolutionControllersRef = useRef<Set<AbortController>>(new Set())
926+
const detachedChatResolutionControllersRef = useRef<Set<AbortController> | null>(null)
927+
const detachedChatResolutionControllers = (detachedChatResolutionControllersRef.current ??=
928+
new Set())
925929
const streamReaderRef = useRef<ReadableStreamDefaultReader<Uint8Array> | null>(null)
926930
const chatIdRef = useRef<string | undefined>(initialChatId)
927931
const tableViewContextsRef = useRef({
@@ -974,9 +978,13 @@ export function useChat(
974978
const resourceActivityTrackerRef = useRef<ResourceActivityTracker | null>(null)
975979
const streamingContentRef = useRef('')
976980
const streamingBlocksRef = useRef<ContentBlock[]>([])
977-
const handledClientWorkflowToolIdsRef = useRef<Set<string>>(new Set())
978-
const handledClientLocalFilesystemToolIdsRef = useRef<Set<string>>(new Set())
979-
const recoveringClientWorkflowToolIdsRef = useRef<Set<string>>(new Set())
981+
const handledClientWorkflowToolIdsRef = useRef<Set<string> | null>(null)
982+
const handledClientWorkflowToolIds = (handledClientWorkflowToolIdsRef.current ??= new Set())
983+
const handledClientLocalFilesystemToolIdsRef = useRef<Set<string> | null>(null)
984+
const handledClientLocalFilesystemToolIds = (handledClientLocalFilesystemToolIdsRef.current ??=
985+
new Set())
986+
const recoveringClientWorkflowToolIdsRef = useRef<Set<string> | null>(null)
987+
const recoveringClientWorkflowToolIds = (recoveringClientWorkflowToolIdsRef.current ??= new Set())
980988
const isHomePage = pathname.endsWith('/home')
981989

982990
const setTransportIdle = useCallback(() => {
@@ -1532,13 +1540,13 @@ export function useChat(
15321540
if (!isWorkflowToolName(toolName)) {
15331541
return
15341542
}
1535-
if (handledClientWorkflowToolIdsRef.current.has(toolCallId)) {
1543+
if (handledClientWorkflowToolIds.has(toolCallId)) {
15361544
return
15371545
}
1538-
if (recoveringClientWorkflowToolIdsRef.current.has(toolCallId)) {
1546+
if (recoveringClientWorkflowToolIds.has(toolCallId)) {
15391547
return
15401548
}
1541-
handledClientWorkflowToolIdsRef.current.add(toolCallId)
1549+
handledClientWorkflowToolIds.add(toolCallId)
15421550

15431551
ensureWorkflowToolResource(toolArgs)
15441552
executeRunToolOnClient(toolCallId, toolName, toolArgs)
@@ -1554,10 +1562,10 @@ export function useChat(
15541562
) {
15551563
return
15561564
}
1557-
if (handledClientLocalFilesystemToolIdsRef.current.has(toolCallId)) {
1565+
if (handledClientLocalFilesystemToolIds.has(toolCallId)) {
15581566
return
15591567
}
1560-
handledClientLocalFilesystemToolIdsRef.current.add(toolCallId)
1568+
handledClientLocalFilesystemToolIds.add(toolCallId)
15611569
const options = {
15621570
workspaceId,
15631571
chatId: chatIdRef.current ?? selectedChatIdRef.current,
@@ -1677,12 +1685,12 @@ export function useChat(
16771685
if (!toolCall || !isWorkflowToolName(toolCall.name)) continue
16781686
if (toolCall.status !== 'executing') continue
16791687
if (
1680-
handledClientWorkflowToolIdsRef.current.has(toolCall.id) ||
1681-
recoveringClientWorkflowToolIdsRef.current.has(toolCall.id)
1688+
handledClientWorkflowToolIds.has(toolCall.id) ||
1689+
recoveringClientWorkflowToolIds.has(toolCall.id)
16821690
) {
16831691
continue
16841692
}
1685-
recoveringClientWorkflowToolIdsRef.current.add(toolCall.id)
1693+
recoveringClientWorkflowToolIds.add(toolCall.id)
16861694
pending.push(toolCall)
16871695
}
16881696
}
@@ -1695,15 +1703,15 @@ export function useChat(
16951703
if (targetWorkflowId) {
16961704
const rebound = await bindRunToolToExecution(toolCall.id, targetWorkflowId)
16971705
if (rebound) {
1698-
handledClientWorkflowToolIdsRef.current.add(toolCall.id)
1706+
handledClientWorkflowToolIds.add(toolCall.id)
16991707
continue
17001708
}
17011709
}
17021710

1703-
recoveringClientWorkflowToolIdsRef.current.delete(toolCall.id)
1711+
recoveringClientWorkflowToolIds.delete(toolCall.id)
17041712
startClientWorkflowTool(toolCall.id, toolCall.name, toolArgs)
17051713
} finally {
1706-
recoveringClientWorkflowToolIdsRef.current.delete(toolCall.id)
1714+
recoveringClientWorkflowToolIds.delete(toolCall.id)
17071715
}
17081716
}
17091717
},
@@ -1736,7 +1744,7 @@ export function useChat(
17361744
// background so its native resources are re-keyed onto the server
17371745
// chat even though this reader is intentionally being cancelled.
17381746
const detachedResolutionController = new AbortController()
1739-
detachedChatResolutionControllersRef.current.add(detachedResolutionController)
1747+
detachedChatResolutionControllers.add(detachedResolutionController)
17401748
void (async () => {
17411749
const resolution = await waitForDetachedChatResolution(
17421750
() =>
@@ -1781,7 +1789,7 @@ export function useChat(
17811789
})
17821790
})
17831791
.finally(() => {
1784-
detachedChatResolutionControllersRef.current.delete(detachedResolutionController)
1792+
detachedChatResolutionControllers.delete(detachedResolutionController)
17851793
})
17861794
}
17871795
// Detach the current UI from the old stream without cancelling it on the server.
@@ -4591,17 +4599,17 @@ export function useChat(
45914599
queuedSendHandoff?: QueuedSendHandoffSeed
45924600
}
45934601
) => {
4594-
if (queuedMessageDispatchIdsRef.current.has(msg.id)) {
4602+
if (queuedMessageDispatchIds.has(msg.id)) {
45954603
return
45964604
}
4597-
queuedMessageDispatchIdsRef.current.add(msg.id)
4605+
queuedMessageDispatchIds.add(msg.id)
45984606

45994607
const dispatchChatKey = chatKeyRef.current
46004608
const queueAtStart =
46014609
useMothershipQueueStore.getState().queues[dispatchChatKey] ?? EMPTY_MESSAGE_QUEUE
46024610
let originalIndex = queueAtStart.findIndex((queued) => queued.id === msg.id)
46034611
if (originalIndex === -1) {
4604-
queuedMessageDispatchIdsRef.current.delete(msg.id)
4612+
queuedMessageDispatchIds.delete(msg.id)
46054613
return
46064614
}
46074615

@@ -4645,7 +4653,7 @@ export function useChat(
46454653
}
46464654
// If the user explicitly removed this message during dispatch, honor
46474655
// that and don't re-insert on failure.
4648-
if (userRemovedDuringDispatchRef.current.delete(msg.id)) {
4656+
if (userRemovedDuringDispatch.delete(msg.id)) {
46494657
clearQueuedSendHandoffState(msg.id)
46504658
return
46514659
}
@@ -4724,8 +4732,8 @@ export function useChat(
47244732
restoreQueuedMessage(activeQueuedSendHandoff)
47254733
} finally {
47264734
setDispatchingHeadId((current) => (current === msg.id ? null : current))
4727-
queuedMessageDispatchIdsRef.current.delete(msg.id)
4728-
userRemovedDuringDispatchRef.current.delete(msg.id)
4735+
queuedMessageDispatchIds.delete(msg.id)
4736+
userRemovedDuringDispatch.delete(msg.id)
47294737
}
47304738
},
47314739
[startSendMessage, handOffWithdrawnSend]
@@ -4781,8 +4789,8 @@ export function useChat(
47814789
const removeFromQueue = useCallback((id: string) => {
47824790
// If the message is mid-dispatch, mark it so the dispatch's failure-restore
47834791
// path won't silently undo the user's removal.
4784-
if (queuedMessageDispatchIdsRef.current.has(id)) {
4785-
userRemovedDuringDispatchRef.current.add(id)
4792+
if (queuedMessageDispatchIds.has(id)) {
4793+
userRemovedDuringDispatch.add(id)
47864794
}
47874795
clearQueuedSendHandoffState(id)
47884796
clearQueuedSendHandoffClaim(id)
@@ -4796,7 +4804,7 @@ export function useChat(
47964804
const queue = queueState.queues[chatKey]
47974805
const msg = id === undefined ? queue?.[0] : queue?.find((queued) => queued.id === id)
47984806
if (!msg || queueState.editing[chatKey] === msg.id) return
4799-
if (queuedMessageDispatchIdsRef.current.has(msg.id)) return
4807+
if (queuedMessageDispatchIds.has(msg.id)) return
48004808
const admissionPending = hasPendingChatAdmission()
48014809

48024810
// Explicit queue sends should supersede any older auto-drain work scheduled by finalize().
@@ -4861,7 +4869,7 @@ export function useChat(
48614869
const editQueuedMessage = useCallback((id: string): QueuedMessage | undefined => {
48624870
// Reject edits on a message already mid-dispatch; the slot is about to be
48634871
// dropped. UI also disables this via `dispatchingHeadId`.
4864-
if (queuedMessageDispatchIdsRef.current.has(id)) return undefined
4872+
if (queuedMessageDispatchIds.has(id)) return undefined
48654873
const activeChatKey = chatKeyRef.current
48664874
const queue = useMothershipQueueStore.getState().queues[activeChatKey] ?? EMPTY_MESSAGE_QUEUE
48674875
const msg = queue.find((m) => m.id === id)
@@ -4890,7 +4898,7 @@ export function useChat(
48904898
)
48914899
if (chatHistory.activeStreamId) acceptedMessageIds.add(chatHistory.activeStreamId)
48924900
for (const queued of messageQueue) {
4893-
if (queuedMessageDispatchIdsRef.current.has(queued.id)) continue
4901+
if (queuedMessageDispatchIds.has(queued.id)) continue
48944902
const requestId = queued.queuedSendHandoff?.userMessageId ?? queued.resumeUserMessageId
48954903
if (!requestId || !acceptedMessageIds.has(requestId)) continue
48964904
clearQueuedSendHandoffState(queued.id)
@@ -4931,10 +4939,10 @@ export function useChat(
49314939
cancelActiveStreamReader()
49324940
abortControllerRef.current?.abort('unmount:client_cleanup')
49334941
abortControllerRef.current = null
4934-
for (const controller of detachedChatResolutionControllersRef.current) {
4942+
for (const controller of detachedChatResolutionControllers) {
49354943
controller.abort('unmount:detached_chat_resolution')
49364944
}
4937-
detachedChatResolutionControllersRef.current.clear()
4945+
detachedChatResolutionControllers.clear()
49384946
clearActiveTurn()
49394947
sendingRef.current = false
49404948
// Release the editing slot — the composer it binds to is unmounting.

0 commit comments

Comments
 (0)