From 9ed4fbc80b9f1dcba90f0ed96a8430210b01286f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Sun, 22 Mar 2026 02:01:38 -0700 Subject: [PATCH 1/2] fix(mothership): abort fix --- apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts | 2 -- apps/sim/lib/copilot/chat-streaming.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index f7a4e7daf08..45b541ad8a2 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -420,7 +420,6 @@ export function useChat( chatIdRef.current = undefined setResolvedChatId(undefined) appliedChatIdRef.current = undefined - abortControllerRef.current?.abort() abortControllerRef.current = null sendingRef.current = false setMessages([]) @@ -1467,7 +1466,6 @@ export function useChat( return () => { streamReaderRef.current?.cancel().catch(() => {}) streamReaderRef.current = null - abortControllerRef.current?.abort() abortControllerRef.current = null streamGenRef.current++ sendingRef.current = false diff --git a/apps/sim/lib/copilot/chat-streaming.ts b/apps/sim/lib/copilot/chat-streaming.ts index 79b59539e89..20d787eed58 100644 --- a/apps/sim/lib/copilot/chat-streaming.ts +++ b/apps/sim/lib/copilot/chat-streaming.ts @@ -353,7 +353,6 @@ export function createSSEStream(params: StreamingOrchestrationParams): ReadableS }, cancel() { clientDisconnected = true - abortController.abort() if (eventWriter) { eventWriter.flush().catch(() => {}) } From a3537e9050c04349def81f2fa3af0e1074d7c02c Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Sun, 22 Mar 2026 02:19:55 -0700 Subject: [PATCH 2/2] diff engine fix --- .../[workspaceId]/home/hooks/use-chat.ts | 21 ++++++++++++++++++- apps/sim/lib/workflows/diff/diff-engine.ts | 19 ++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 45b541ad8a2..048634f080a 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -444,7 +444,26 @@ export function useChat( appliedChatIdRef.current = chatHistory.id const mappedMessages = chatHistory.messages.map(mapStoredMessage) - setMessages(mappedMessages) + const shouldPreserveActiveStreamingMessage = + sendingRef.current && Boolean(activeStreamId) && activeStreamId === streamIdRef.current + + if (shouldPreserveActiveStreamingMessage) { + setMessages((prev) => { + const localStreamingAssistant = prev[prev.length - 1] + if (localStreamingAssistant?.role !== 'assistant') { + return mappedMessages + } + + const nextMessages = + mappedMessages[mappedMessages.length - 1]?.role === 'assistant' + ? mappedMessages.slice(0, -1) + : mappedMessages + + return [...nextMessages, localStreamingAssistant] + }) + } else { + setMessages(mappedMessages) + } if (chatHistory.resources.some((r) => r.id === 'streaming-file')) { fetch('/api/copilot/chat/resources', { diff --git a/apps/sim/lib/workflows/diff/diff-engine.ts b/apps/sim/lib/workflows/diff/diff-engine.ts index 84aa068673d..9b23a2f4eb3 100644 --- a/apps/sim/lib/workflows/diff/diff-engine.ts +++ b/apps/sim/lib/workflows/diff/diff-engine.ts @@ -505,12 +505,21 @@ export class WorkflowDiffEngine { try { const baselineBlockIds = new Set(Object.keys(mergedBaseline.blocks)) - // Identify blocks that need positioning: genuinely new blocks AND - // blocks inserted into subflows (position reset to 0,0). Extracted - // blocks are excluded — the server computes valid absolute positions - // from the container offset, so they don't need repositioning. + // Identify blocks that need positioning: genuinely new blocks that + // don't have valid positions yet. Blocks already positioned by a + // previous server-side layout (non-origin position) are skipped to + // avoid redundant client-side re-layout that can shift blocks when + // block metrics change between edits (e.g. condition handle offsets). const blocksNeedingLayout = Object.keys(finalBlocks).filter((id) => { - if (!baselineBlockIds.has(id)) return true + if (!baselineBlockIds.has(id)) { + const pos = finalBlocks[id]?.position + const hasValidPosition = + pos && + Number.isFinite(pos.x) && + Number.isFinite(pos.y) && + !(pos.x === 0 && pos.y === 0) + return !hasValidPosition + } const baselineParent = mergedBaseline.blocks[id]?.data?.parentId ?? null const proposedParent = finalBlocks[id]?.data?.parentId ?? null if (baselineParent === proposedParent) return false