fix(editor): block rename applies to correct block when selection changes (#3129)

This commit is contained in:
Waleed
2026-02-03 20:06:03 -08:00
committed by GitHub
parent 5b0c2156e0
commit 7977ac88ca
7 changed files with 95 additions and 45 deletions

View File

@@ -78,21 +78,15 @@ const IconComponent = ({ icon: Icon, className }: { icon: any; className?: strin
* @returns Editor panel content
*/
export function Editor() {
const {
currentBlockId,
connectionsHeight,
toggleConnectionsCollapsed,
shouldFocusRename,
setShouldFocusRename,
} = usePanelEditorStore(
useShallow((state) => ({
currentBlockId: state.currentBlockId,
connectionsHeight: state.connectionsHeight,
toggleConnectionsCollapsed: state.toggleConnectionsCollapsed,
shouldFocusRename: state.shouldFocusRename,
setShouldFocusRename: state.setShouldFocusRename,
}))
)
const { currentBlockId, connectionsHeight, toggleConnectionsCollapsed, registerRenameCallback } =
usePanelEditorStore(
useShallow((state) => ({
currentBlockId: state.currentBlockId,
connectionsHeight: state.connectionsHeight,
toggleConnectionsCollapsed: state.toggleConnectionsCollapsed,
registerRenameCallback: state.registerRenameCallback,
}))
)
const currentWorkflow = useCurrentWorkflow()
const currentBlock = currentBlockId ? currentWorkflow.getBlockById(currentBlockId) : null
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
@@ -229,6 +223,7 @@ export function Editor() {
const [isRenaming, setIsRenaming] = useState(false)
const [editedName, setEditedName] = useState('')
const renamingBlockIdRef = useRef<string | null>(null)
/**
* Ref callback that auto-selects the input text when mounted.
@@ -240,44 +235,62 @@ export function Editor() {
}, [])
/**
* Handles starting the rename process.
* Starts the rename process for the current block.
* Reads from stores directly to avoid stale closures when called via registered callback.
* Captures the block ID in a ref to ensure the correct block is renamed even if selection changes.
*/
const handleStartRename = useCallback(() => {
if (!canEditBlock || !currentBlock) return
setEditedName(currentBlock.name || '')
const blockId = usePanelEditorStore.getState().currentBlockId
if (!blockId) return
const blocks = useWorkflowStore.getState().blocks
const block = blocks[blockId]
if (!block) return
const parentId = block.data?.parentId as string | undefined
const isParentLocked = parentId ? (blocks[parentId]?.locked ?? false) : false
const isLocked = (block.locked ?? false) || isParentLocked
if (!userPermissions.canEdit || isLocked) return
renamingBlockIdRef.current = blockId
setEditedName(block.name || '')
setIsRenaming(true)
}, [canEditBlock, currentBlock])
}, [userPermissions.canEdit])
/**
* Handles saving the renamed block.
* Saves the renamed block using the captured block ID from when rename started.
*/
const handleSaveRename = useCallback(() => {
if (!currentBlockId || !isRenaming) return
const blockIdToRename = renamingBlockIdRef.current
if (!blockIdToRename || !isRenaming) return
const blocks = useWorkflowStore.getState().blocks
const blockToRename = blocks[blockIdToRename]
const trimmedName = editedName.trim()
if (trimmedName && trimmedName !== currentBlock?.name) {
const result = collaborativeUpdateBlockName(currentBlockId, trimmedName)
if (trimmedName && blockToRename && trimmedName !== blockToRename.name) {
const result = collaborativeUpdateBlockName(blockIdToRename, trimmedName)
if (!result.success) {
return
}
}
renamingBlockIdRef.current = null
setIsRenaming(false)
}, [currentBlockId, isRenaming, editedName, currentBlock?.name, collaborativeUpdateBlockName])
}, [isRenaming, editedName, collaborativeUpdateBlockName])
/**
* Handles canceling the rename process.
*/
const handleCancelRename = useCallback(() => {
renamingBlockIdRef.current = null
setIsRenaming(false)
setEditedName('')
}, [])
useEffect(() => {
if (shouldFocusRename && currentBlock) {
handleStartRename()
setShouldFocusRename(false)
}
}, [shouldFocusRename, currentBlock, handleStartRename, setShouldFocusRename])
registerRenameCallback(handleStartRename)
return () => registerRenameCallback(null)
}, [registerRenameCallback, handleStartRename])
/**
* Handles opening documentation link in a new secure tab.

View File

@@ -1185,6 +1185,12 @@ export function useWorkflowExecution() {
})
}
},
onExecutionCancelled: () => {
if (activeWorkflowId) {
cancelRunningEntries(activeWorkflowId)
}
},
},
})
@@ -1738,6 +1744,10 @@ export function useWorkflowExecution() {
})
}
},
onExecutionCancelled: () => {
cancelRunningEntries(workflowId)
},
},
})
} catch (error) {
@@ -1759,6 +1769,7 @@ export function useWorkflowExecution() {
setEdgeRunStatus,
addNotification,
addConsole,
cancelRunningEntries,
executionStream,
]
)

View File

@@ -1132,7 +1132,7 @@ const WorkflowContent = React.memo(() => {
const handleContextRename = useCallback(() => {
if (contextMenuBlocks.length === 1) {
usePanelEditorStore.getState().setCurrentBlockId(contextMenuBlocks[0].id)
usePanelEditorStore.getState().setShouldFocusRename(true)
usePanelEditorStore.getState().triggerRename()
}
}, [contextMenuBlocks])

View File

@@ -130,6 +130,7 @@ export class ExecutionEngine {
this.context.metadata.duration = endTime - startTime
if (this.cancelledFlag) {
this.finalizeIncompleteLogs()
return {
success: false,
output: this.finalOutput,
@@ -151,6 +152,7 @@ export class ExecutionEngine {
this.context.metadata.duration = endTime - startTime
if (this.cancelledFlag) {
this.finalizeIncompleteLogs()
return {
success: false,
output: this.finalOutput,
@@ -474,4 +476,20 @@ export class ExecutionEngine {
pauseCount: responses.length,
}
}
/**
* Finalizes any block logs that were still running when execution was cancelled.
* Sets their endedAt to now and calculates the actual elapsed duration.
*/
private finalizeIncompleteLogs(): void {
const now = new Date()
const nowIso = now.toISOString()
for (const log of this.context.blockLogs) {
if (!log.endedAt) {
log.endedAt = nowIso
log.durationMs = now.getTime() - new Date(log.startedAt).getTime()
}
}
}
}

View File

@@ -176,6 +176,10 @@ export function formatDuration(
}
} else {
ms = duration
// Handle NaN/Infinity (e.g., cancelled blocks with no end time)
if (!Number.isFinite(ms)) {
return '—'
}
}
const precision = options?.precision ?? 0

View File

@@ -5,6 +5,8 @@ import { persist } from 'zustand/middleware'
import { EDITOR_CONNECTIONS_HEIGHT } from '@/stores/constants'
import { usePanelStore } from '../store'
let renameCallback: (() => void) | null = null
/**
* State for the Editor panel.
* Tracks the currently selected block to edit its subblocks/values and connections panel height.
@@ -22,10 +24,10 @@ interface PanelEditorState {
setConnectionsHeight: (height: number) => void
/** Toggle connections between collapsed (min height) and expanded (default height) */
toggleConnectionsCollapsed: () => void
/** Flag to signal the editor to focus the rename input */
shouldFocusRename: boolean
/** Sets the shouldFocusRename flag */
setShouldFocusRename: (value: boolean) => void
/** Register the rename callback (called by Editor on mount) */
registerRenameCallback: (callback: (() => void) | null) => void
/** Trigger rename mode by invoking the registered callback */
triggerRename: () => void
}
/**
@@ -37,15 +39,16 @@ export const usePanelEditorStore = create<PanelEditorState>()(
(set, get) => ({
currentBlockId: null,
connectionsHeight: EDITOR_CONNECTIONS_HEIGHT.DEFAULT,
shouldFocusRename: false,
setShouldFocusRename: (value) => set({ shouldFocusRename: value }),
registerRenameCallback: (callback) => {
renameCallback = callback
},
triggerRename: () => {
renameCallback?.()
},
setCurrentBlockId: (blockId) => {
set({ currentBlockId: blockId })
// When a block is selected, always switch to the editor tab
if (blockId !== null) {
const panelState = usePanelStore.getState()
panelState.setActiveTab('editor')
usePanelStore.getState().setActiveTab('editor')
}
},
clearCurrentBlock: () => {
@@ -57,7 +60,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
Math.min(EDITOR_CONNECTIONS_HEIGHT.MAX, height)
)
set({ connectionsHeight: clampedHeight })
// Update CSS variable for immediate visual feedback
if (typeof window !== 'undefined') {
document.documentElement.style.setProperty(
'--editor-connections-height',
@@ -73,8 +75,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
: EDITOR_CONNECTIONS_HEIGHT.MIN
set({ connectionsHeight: newHeight })
// Update CSS variable
if (typeof window !== 'undefined') {
document.documentElement.style.setProperty(
'--editor-connections-height',
@@ -90,7 +90,6 @@ export const usePanelEditorStore = create<PanelEditorState>()(
connectionsHeight: state.connectionsHeight,
}),
onRehydrateStorage: () => (state) => {
// Sync CSS variables with stored state after rehydration
if (state && typeof window !== 'undefined') {
document.documentElement.style.setProperty(
'--editor-connections-height',

View File

@@ -380,13 +380,18 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
cancelRunningEntries: (workflowId: string) => {
set((state) => {
const now = new Date()
const updatedEntries = state.entries.map((entry) => {
if (entry.workflowId === workflowId && entry.isRunning) {
const durationMs = entry.startedAt
? now.getTime() - new Date(entry.startedAt).getTime()
: entry.durationMs
return {
...entry,
isRunning: false,
isCanceled: true,
endedAt: new Date().toISOString(),
endedAt: now.toISOString(),
durationMs,
}
}
return entry