Skip to content

Commit 7977ac8

Browse files
authored
fix(editor): block rename applies to correct block when selection changes (#3129)
1 parent 5b0c215 commit 7977ac8

File tree

7 files changed

+95
-45
lines changed

7 files changed

+95
-45
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,15 @@ const IconComponent = ({ icon: Icon, className }: { icon: any; className?: strin
7878
* @returns Editor panel content
7979
*/
8080
export function Editor() {
81-
const {
82-
currentBlockId,
83-
connectionsHeight,
84-
toggleConnectionsCollapsed,
85-
shouldFocusRename,
86-
setShouldFocusRename,
87-
} = usePanelEditorStore(
88-
useShallow((state) => ({
89-
currentBlockId: state.currentBlockId,
90-
connectionsHeight: state.connectionsHeight,
91-
toggleConnectionsCollapsed: state.toggleConnectionsCollapsed,
92-
shouldFocusRename: state.shouldFocusRename,
93-
setShouldFocusRename: state.setShouldFocusRename,
94-
}))
95-
)
81+
const { currentBlockId, connectionsHeight, toggleConnectionsCollapsed, registerRenameCallback } =
82+
usePanelEditorStore(
83+
useShallow((state) => ({
84+
currentBlockId: state.currentBlockId,
85+
connectionsHeight: state.connectionsHeight,
86+
toggleConnectionsCollapsed: state.toggleConnectionsCollapsed,
87+
registerRenameCallback: state.registerRenameCallback,
88+
}))
89+
)
9690
const currentWorkflow = useCurrentWorkflow()
9791
const currentBlock = currentBlockId ? currentWorkflow.getBlockById(currentBlockId) : null
9892
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
@@ -229,6 +223,7 @@ export function Editor() {
229223

230224
const [isRenaming, setIsRenaming] = useState(false)
231225
const [editedName, setEditedName] = useState('')
226+
const renamingBlockIdRef = useRef<string | null>(null)
232227

233228
/**
234229
* Ref callback that auto-selects the input text when mounted.
@@ -240,44 +235,62 @@ export function Editor() {
240235
}, [])
241236

242237
/**
243-
* Handles starting the rename process.
238+
* Starts the rename process for the current block.
239+
* Reads from stores directly to avoid stale closures when called via registered callback.
240+
* Captures the block ID in a ref to ensure the correct block is renamed even if selection changes.
244241
*/
245242
const handleStartRename = useCallback(() => {
246-
if (!canEditBlock || !currentBlock) return
247-
setEditedName(currentBlock.name || '')
243+
const blockId = usePanelEditorStore.getState().currentBlockId
244+
if (!blockId) return
245+
246+
const blocks = useWorkflowStore.getState().blocks
247+
const block = blocks[blockId]
248+
if (!block) return
249+
250+
const parentId = block.data?.parentId as string | undefined
251+
const isParentLocked = parentId ? (blocks[parentId]?.locked ?? false) : false
252+
const isLocked = (block.locked ?? false) || isParentLocked
253+
if (!userPermissions.canEdit || isLocked) return
254+
255+
renamingBlockIdRef.current = blockId
256+
setEditedName(block.name || '')
248257
setIsRenaming(true)
249-
}, [canEditBlock, currentBlock])
258+
}, [userPermissions.canEdit])
250259

251260
/**
252-
* Handles saving the renamed block.
261+
* Saves the renamed block using the captured block ID from when rename started.
253262
*/
254263
const handleSaveRename = useCallback(() => {
255-
if (!currentBlockId || !isRenaming) return
264+
const blockIdToRename = renamingBlockIdRef.current
265+
if (!blockIdToRename || !isRenaming) return
266+
267+
const blocks = useWorkflowStore.getState().blocks
268+
const blockToRename = blocks[blockIdToRename]
256269

257270
const trimmedName = editedName.trim()
258-
if (trimmedName && trimmedName !== currentBlock?.name) {
259-
const result = collaborativeUpdateBlockName(currentBlockId, trimmedName)
271+
if (trimmedName && blockToRename && trimmedName !== blockToRename.name) {
272+
const result = collaborativeUpdateBlockName(blockIdToRename, trimmedName)
260273
if (!result.success) {
261274
return
262275
}
263276
}
277+
renamingBlockIdRef.current = null
264278
setIsRenaming(false)
265-
}, [currentBlockId, isRenaming, editedName, currentBlock?.name, collaborativeUpdateBlockName])
279+
}, [isRenaming, editedName, collaborativeUpdateBlockName])
266280

267281
/**
268282
* Handles canceling the rename process.
269283
*/
270284
const handleCancelRename = useCallback(() => {
285+
renamingBlockIdRef.current = null
271286
setIsRenaming(false)
272287
setEditedName('')
273288
}, [])
274289

275290
useEffect(() => {
276-
if (shouldFocusRename && currentBlock) {
277-
handleStartRename()
278-
setShouldFocusRename(false)
279-
}
280-
}, [shouldFocusRename, currentBlock, handleStartRename, setShouldFocusRename])
291+
registerRenameCallback(handleStartRename)
292+
return () => registerRenameCallback(null)
293+
}, [registerRenameCallback, handleStartRename])
281294

282295
/**
283296
* Handles opening documentation link in a new secure tab.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,12 @@ export function useWorkflowExecution() {
11851185
})
11861186
}
11871187
},
1188+
1189+
onExecutionCancelled: () => {
1190+
if (activeWorkflowId) {
1191+
cancelRunningEntries(activeWorkflowId)
1192+
}
1193+
},
11881194
},
11891195
})
11901196

@@ -1738,6 +1744,10 @@ export function useWorkflowExecution() {
17381744
})
17391745
}
17401746
},
1747+
1748+
onExecutionCancelled: () => {
1749+
cancelRunningEntries(workflowId)
1750+
},
17411751
},
17421752
})
17431753
} catch (error) {
@@ -1759,6 +1769,7 @@ export function useWorkflowExecution() {
17591769
setEdgeRunStatus,
17601770
addNotification,
17611771
addConsole,
1772+
cancelRunningEntries,
17621773
executionStream,
17631774
]
17641775
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ const WorkflowContent = React.memo(() => {
11321132
const handleContextRename = useCallback(() => {
11331133
if (contextMenuBlocks.length === 1) {
11341134
usePanelEditorStore.getState().setCurrentBlockId(contextMenuBlocks[0].id)
1135-
usePanelEditorStore.getState().setShouldFocusRename(true)
1135+
usePanelEditorStore.getState().triggerRename()
11361136
}
11371137
}, [contextMenuBlocks])
11381138

apps/sim/executor/execution/engine.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export class ExecutionEngine {
130130
this.context.metadata.duration = endTime - startTime
131131

132132
if (this.cancelledFlag) {
133+
this.finalizeIncompleteLogs()
133134
return {
134135
success: false,
135136
output: this.finalOutput,
@@ -151,6 +152,7 @@ export class ExecutionEngine {
151152
this.context.metadata.duration = endTime - startTime
152153

153154
if (this.cancelledFlag) {
155+
this.finalizeIncompleteLogs()
154156
return {
155157
success: false,
156158
output: this.finalOutput,
@@ -474,4 +476,20 @@ export class ExecutionEngine {
474476
pauseCount: responses.length,
475477
}
476478
}
479+
480+
/**
481+
* Finalizes any block logs that were still running when execution was cancelled.
482+
* Sets their endedAt to now and calculates the actual elapsed duration.
483+
*/
484+
private finalizeIncompleteLogs(): void {
485+
const now = new Date()
486+
const nowIso = now.toISOString()
487+
488+
for (const log of this.context.blockLogs) {
489+
if (!log.endedAt) {
490+
log.endedAt = nowIso
491+
log.durationMs = now.getTime() - new Date(log.startedAt).getTime()
492+
}
493+
}
494+
}
477495
}

apps/sim/lib/core/utils/formatting.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ export function formatDuration(
176176
}
177177
} else {
178178
ms = duration
179+
// Handle NaN/Infinity (e.g., cancelled blocks with no end time)
180+
if (!Number.isFinite(ms)) {
181+
return '—'
182+
}
179183
}
180184

181185
const precision = options?.precision ?? 0

apps/sim/stores/panel/editor/store.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { persist } from 'zustand/middleware'
55
import { EDITOR_CONNECTIONS_HEIGHT } from '@/stores/constants'
66
import { usePanelStore } from '../store'
77

8+
let renameCallback: (() => void) | null = null
9+
810
/**
911
* State for the Editor panel.
1012
* Tracks the currently selected block to edit its subblocks/values and connections panel height.
@@ -22,10 +24,10 @@ interface PanelEditorState {
2224
setConnectionsHeight: (height: number) => void
2325
/** Toggle connections between collapsed (min height) and expanded (default height) */
2426
toggleConnectionsCollapsed: () => void
25-
/** Flag to signal the editor to focus the rename input */
26-
shouldFocusRename: boolean
27-
/** Sets the shouldFocusRename flag */
28-
setShouldFocusRename: (value: boolean) => void
27+
/** Register the rename callback (called by Editor on mount) */
28+
registerRenameCallback: (callback: (() => void) | null) => void
29+
/** Trigger rename mode by invoking the registered callback */
30+
triggerRename: () => void
2931
}
3032

3133
/**
@@ -37,15 +39,16 @@ export const usePanelEditorStore = create<PanelEditorState>()(
3739
(set, get) => ({
3840
currentBlockId: null,
3941
connectionsHeight: EDITOR_CONNECTIONS_HEIGHT.DEFAULT,
40-
shouldFocusRename: false,
41-
setShouldFocusRename: (value) => set({ shouldFocusRename: value }),
42+
registerRenameCallback: (callback) => {
43+
renameCallback = callback
44+
},
45+
triggerRename: () => {
46+
renameCallback?.()
47+
},
4248
setCurrentBlockId: (blockId) => {
4349
set({ currentBlockId: blockId })
44-
45-
// When a block is selected, always switch to the editor tab
4650
if (blockId !== null) {
47-
const panelState = usePanelStore.getState()
48-
panelState.setActiveTab('editor')
51+
usePanelStore.getState().setActiveTab('editor')
4952
}
5053
},
5154
clearCurrentBlock: () => {
@@ -57,7 +60,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
5760
Math.min(EDITOR_CONNECTIONS_HEIGHT.MAX, height)
5861
)
5962
set({ connectionsHeight: clampedHeight })
60-
// Update CSS variable for immediate visual feedback
6163
if (typeof window !== 'undefined') {
6264
document.documentElement.style.setProperty(
6365
'--editor-connections-height',
@@ -73,8 +75,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
7375
: EDITOR_CONNECTIONS_HEIGHT.MIN
7476

7577
set({ connectionsHeight: newHeight })
76-
77-
// Update CSS variable
7878
if (typeof window !== 'undefined') {
7979
document.documentElement.style.setProperty(
8080
'--editor-connections-height',
@@ -90,7 +90,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
9090
connectionsHeight: state.connectionsHeight,
9191
}),
9292
onRehydrateStorage: () => (state) => {
93-
// Sync CSS variables with stored state after rehydration
9493
if (state && typeof window !== 'undefined') {
9594
document.documentElement.style.setProperty(
9695
'--editor-connections-height',

apps/sim/stores/terminal/console/store.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,18 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
380380

381381
cancelRunningEntries: (workflowId: string) => {
382382
set((state) => {
383+
const now = new Date()
383384
const updatedEntries = state.entries.map((entry) => {
384385
if (entry.workflowId === workflowId && entry.isRunning) {
386+
const durationMs = entry.startedAt
387+
? now.getTime() - new Date(entry.startedAt).getTime()
388+
: entry.durationMs
385389
return {
386390
...entry,
387391
isRunning: false,
388392
isCanceled: true,
389-
endedAt: new Date().toISOString(),
393+
endedAt: now.toISOString(),
394+
durationMs,
390395
}
391396
}
392397
return entry

0 commit comments

Comments
 (0)