Skip to content

Commit a8bb0db

Browse files
v0.5.62: webhook bug fixes, seeding default subblock values, block selection fixes
2 parents af82820 + 5de7228 commit a8bb0db

File tree

11 files changed

+85
-133
lines changed

11 files changed

+85
-133
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
<p align="center">
1010
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-6F3DFA" alt="Sim.ai"></a>
1111
<a href="https://discord.gg/Hr4UWYEcTT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
12-
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simstudioai?style=social" alt="Twitter"></a>
13-
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a> <a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/DeepWiki-1E90FF.svg" alt="DeepWiki"></a>
12+
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simdotai?style=social" alt="Twitter"></a>
13+
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
1414
</p>
1515

1616
<p align="center">
17-
<a href="https://cursor.com/link/prompt?text=Help%20me%20set%20up%20Sim%20Studio%20locally.%20Follow%20these%20steps%3A%0A%0A1.%20First%2C%20verify%20Docker%20is%20installed%20and%20running%3A%0A%20%20%20docker%20--version%0A%20%20%20docker%20info%0A%0A2.%20Clone%20the%20repository%3A%0A%20%20%20git%20clone%20https%3A%2F%2Fgithub.com%2Fsimstudioai%2Fsim.git%0A%20%20%20cd%20sim%0A%0A3.%20Start%20the%20services%20with%20Docker%20Compose%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20up%20-d%0A%0A4.%20Wait%20for%20all%20containers%20to%20be%20healthy%20(this%20may%20take%201-2%20minutes)%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20ps%0A%0A5.%20Verify%20the%20app%20is%20accessible%20at%20http%3A%2F%2Flocalhost%3A3000%0A%0AIf%20there%20are%20any%20errors%2C%20help%20me%20troubleshoot%20them.%20Common%20issues%3A%0A-%20Port%203000%2C%203002%2C%20or%205432%20already%20in%20use%0A-%20Docker%20not%20running%0A-%20Insufficient%20memory%20(needs%2012GB%2B%20RAM)%0A%0AFor%20local%20AI%20models%20with%20Ollama%2C%20use%20this%20instead%20of%20step%203%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.ollama.yml%20--profile%20setup%20up%20-d"><img src="https://img.shields.io/badge/Set%20Up%20with-Cursor-000000?logo=cursor&logoColor=white" alt="Set Up with Cursor"></a>
17+
<a href="https://deepwiki.com/simstudioai/sim" target="_blank" rel="noopener noreferrer"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a> <a href="https://cursor.com/link/prompt?text=Help%20me%20set%20up%20Sim%20Studio%20locally.%20Follow%20these%20steps%3A%0A%0A1.%20First%2C%20verify%20Docker%20is%20installed%20and%20running%3A%0A%20%20%20docker%20--version%0A%20%20%20docker%20info%0A%0A2.%20Clone%20the%20repository%3A%0A%20%20%20git%20clone%20https%3A%2F%2Fgithub.com%2Fsimstudioai%2Fsim.git%0A%20%20%20cd%20sim%0A%0A3.%20Start%20the%20services%20with%20Docker%20Compose%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20up%20-d%0A%0A4.%20Wait%20for%20all%20containers%20to%20be%20healthy%20(this%20may%20take%201-2%20minutes)%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.prod.yml%20ps%0A%0A5.%20Verify%20the%20app%20is%20accessible%20at%20http%3A%2F%2Flocalhost%3A3000%0A%0AIf%20there%20are%20any%20errors%2C%20help%20me%20troubleshoot%20them.%20Common%20issues%3A%0A-%20Port%203000%2C%203002%2C%20or%205432%20already%20in%20use%0A-%20Docker%20not%20running%0A-%20Insufficient%20memory%20(needs%2012GB%2B%20RAM)%0A%0AFor%20local%20AI%20models%20with%20Ollama%2C%20use%20this%20instead%20of%20step%203%3A%0A%20%20%20docker%20compose%20-f%20docker-compose.ollama.yml%20--profile%20setup%20up%20-d"><img src="https://img.shields.io/badge/Set%20Up%20with-Cursor-000000?logo=cursor&logoColor=white" alt="Set Up with Cursor"></a>
1818
</p>
1919

2020
### Build Workflows with Ease

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,17 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }
168168
)
169169
})
170170

171-
export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlockNodeData>) {
171+
export const NoteBlock = memo(function NoteBlock({
172+
id,
173+
data,
174+
selected,
175+
}: NodeProps<NoteBlockNodeData>) {
172176
const { type, config, name } = data
173177

174178
const { activeWorkflowId, isEnabled, handleClick, hasRing, ringStyles } = useBlockVisual({
175179
blockId: id,
176180
data,
181+
isSelected: selected,
177182
})
178183
const storedValues = useSubBlockStore(
179184
useCallback(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export interface SubflowNodeData {
6666
* @param props - Node properties containing data and id
6767
* @returns Rendered subflow node component
6868
*/
69-
export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeData>) => {
69+
export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<SubflowNodeData>) => {
7070
const { getNodes } = useReactFlow()
7171
const blockRef = useRef<HTMLDivElement>(null)
7272
const userPermissions = useUserPermissionsContext()
@@ -134,13 +134,15 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
134134

135135
/**
136136
* Determine the ring styling based on subflow state priority:
137-
* 1. Focused (selected in editor) or preview selected - blue ring
137+
* 1. Focused (selected in editor), selected (shift-click/box), or preview selected - blue ring
138138
* 2. Diff status (version comparison) - green/orange ring
139139
*/
140-
const hasRing = isFocused || isPreviewSelected || diffStatus === 'new' || diffStatus === 'edited'
140+
const isSelected = !isPreview && selected
141+
const hasRing =
142+
isFocused || isSelected || isPreviewSelected || diffStatus === 'new' || diffStatus === 'edited'
141143
const ringStyles = cn(
142144
hasRing && 'ring-[1.75px]',
143-
(isFocused || isPreviewSelected) && 'ring-[var(--brand-secondary)]',
145+
(isFocused || isSelected || isPreviewSelected) && 'ring-[var(--brand-secondary)]',
144146
diffStatus === 'new' && 'ring-[var(--brand-tertiary-2)]',
145147
diffStatus === 'edited' && 'ring-[var(--warning)]'
146148
)
@@ -167,7 +169,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
167169
data-node-id={id}
168170
data-type='subflowNode'
169171
data-nesting-level={nestingLevel}
170-
data-subflow-selected={isFocused || isPreviewSelected}
172+
data-subflow-selected={isFocused || isSelected || isPreviewSelected}
171173
>
172174
{!isPreview && (
173175
<ActionBar blockId={id} blockType={data.kind} disabled={!userPermissions.canEdit} />

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ const tryParseJson = (value: unknown): unknown => {
208208
export const getDisplayValue = (value: unknown): string => {
209209
if (value == null || value === '') return '-'
210210

211-
// Try parsing JSON strings first
212211
const parsedValue = tryParseJson(value)
213212

214213
if (isMessagesArray(parsedValue)) {
@@ -557,6 +556,7 @@ const SubBlockRow = ({
557556
export const WorkflowBlock = memo(function WorkflowBlock({
558557
id,
559558
data,
559+
selected,
560560
}: NodeProps<WorkflowBlockProps>) {
561561
const { type, config, name, isPending } = data
562562

@@ -574,7 +574,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
574574
hasRing,
575575
ringStyles,
576576
runPathStatus,
577-
} = useBlockVisual({ blockId: id, data, isPending })
577+
} = useBlockVisual({ blockId: id, data, isPending, isSelected: selected })
578578

579579
const currentBlock = currentWorkflow.getBlockById(id)
580580

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ interface UseBlockVisualProps {
1717
data: WorkflowBlockProps
1818
/** Whether the block is pending execution */
1919
isPending?: boolean
20+
/** Whether the block is selected (via shift-click or selection box) */
21+
isSelected?: boolean
2022
}
2123

2224
/**
@@ -28,7 +30,12 @@ interface UseBlockVisualProps {
2830
* @param props - The hook properties
2931
* @returns Visual state, click handler, and ring styling for the block
3032
*/
31-
export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVisualProps) {
33+
export function useBlockVisual({
34+
blockId,
35+
data,
36+
isPending = false,
37+
isSelected = false,
38+
}: UseBlockVisualProps) {
3239
const isPreview = data.isPreview ?? false
3340
const isPreviewSelected = data.isPreviewSelected ?? false
3441

@@ -42,7 +49,6 @@ export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVis
4249
isDeletedBlock,
4350
} = useBlockState(blockId, currentWorkflow, data)
4451

45-
// Check if the editor panel is open for this block
4652
const currentBlockId = usePanelEditorStore((state) => state.currentBlockId)
4753
const activeTab = usePanelStore((state) => state.activeTab)
4854
const isEditorOpen = !isPreview && currentBlockId === blockId && activeTab === 'editor'
@@ -68,6 +74,7 @@ export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVis
6874
diffStatus: isPreview ? undefined : diffStatus,
6975
runPathStatus,
7076
isPreviewSelection: isPreview && isPreviewSelected,
77+
isSelected: isPreview ? false : isSelected,
7178
}),
7279
[
7380
isExecuting,
@@ -78,6 +85,7 @@ export function useBlockVisual({ blockId, data, isPending = false }: UseBlockVis
7885
runPathStatus,
7986
isPreview,
8087
isPreviewSelected,
88+
isSelected,
8189
]
8290
)
8391

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-ring-utils.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface BlockRingOptions {
1414
diffStatus: BlockDiffStatus
1515
runPathStatus: BlockRunPathStatus
1616
isPreviewSelection?: boolean
17+
/** Whether the block is selected via shift-click or selection box (shows blue ring) */
18+
isSelected?: boolean
1719
}
1820

1921
/**
@@ -32,11 +34,13 @@ export function getBlockRingStyles(options: BlockRingOptions): {
3234
diffStatus,
3335
runPathStatus,
3436
isPreviewSelection,
37+
isSelected,
3538
} = options
3639

3740
const hasRing =
3841
isExecuting ||
3942
isEditorOpen ||
43+
isSelected ||
4044
isPending ||
4145
diffStatus === 'new' ||
4246
diffStatus === 'edited' ||
@@ -46,39 +50,53 @@ export function getBlockRingStyles(options: BlockRingOptions): {
4650
const ringClassName = cn(
4751
// Executing block: pulsing success ring with prominent thickness (highest priority)
4852
isExecuting && 'ring-[3.5px] ring-[var(--border-success)] animate-ring-pulse',
49-
// Editor open or preview selection: static blue ring
53+
// Editor open, selected, or preview selection: static blue ring
5054
!isExecuting &&
51-
(isEditorOpen || isPreviewSelection) &&
55+
(isEditorOpen || isSelected || isPreviewSelection) &&
5256
'ring-[1.75px] ring-[var(--brand-secondary)]',
5357
// Non-active states use standard ring utilities
54-
!isExecuting && !isEditorOpen && !isPreviewSelection && hasRing && 'ring-[1.75px]',
58+
!isExecuting &&
59+
!isEditorOpen &&
60+
!isSelected &&
61+
!isPreviewSelection &&
62+
hasRing &&
63+
'ring-[1.75px]',
5564
// Pending state: warning ring
56-
!isExecuting && !isEditorOpen && isPending && 'ring-[var(--warning)]',
65+
!isExecuting && !isEditorOpen && !isSelected && isPending && 'ring-[var(--warning)]',
5766
// Deleted state (highest priority after active/pending)
58-
!isExecuting && !isEditorOpen && !isPending && isDeletedBlock && 'ring-[var(--text-error)]',
67+
!isExecuting &&
68+
!isEditorOpen &&
69+
!isSelected &&
70+
!isPending &&
71+
isDeletedBlock &&
72+
'ring-[var(--text-error)]',
5973
// Diff states
6074
!isExecuting &&
6175
!isEditorOpen &&
76+
!isSelected &&
6277
!isPending &&
6378
!isDeletedBlock &&
6479
diffStatus === 'new' &&
6580
'ring-[var(--brand-tertiary-2)]',
6681
!isExecuting &&
6782
!isEditorOpen &&
83+
!isSelected &&
6884
!isPending &&
6985
!isDeletedBlock &&
7086
diffStatus === 'edited' &&
7187
'ring-[var(--warning)]',
7288
// Run path states (lowest priority - only show if no other states active)
7389
!isExecuting &&
7490
!isEditorOpen &&
91+
!isSelected &&
7592
!isPending &&
7693
!isDeletedBlock &&
7794
!diffStatus &&
7895
runPathStatus === 'success' &&
7996
'ring-[var(--border-success)]',
8097
!isExecuting &&
8198
!isEditorOpen &&
99+
!isSelected &&
82100
!isPending &&
83101
!isDeletedBlock &&
84102
!diffStatus &&

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,23 @@ const WorkflowContent = React.memo(() => {
700700
triggerMode,
701701
})
702702

703-
collaborativeBatchAddBlocks([block], autoConnectEdge ? [autoConnectEdge] : [], {}, {}, {})
703+
const subBlockValues: Record<string, Record<string, unknown>> = {}
704+
if (block.subBlocks && Object.keys(block.subBlocks).length > 0) {
705+
subBlockValues[id] = {}
706+
for (const [subBlockId, subBlock] of Object.entries(block.subBlocks)) {
707+
if (subBlock.value !== null && subBlock.value !== undefined) {
708+
subBlockValues[id][subBlockId] = subBlock.value
709+
}
710+
}
711+
}
712+
713+
collaborativeBatchAddBlocks(
714+
[block],
715+
autoConnectEdge ? [autoConnectEdge] : [],
716+
{},
717+
{},
718+
subBlockValues
719+
)
704720
usePanelEditorStore.getState().setCurrentBlockId(id)
705721
},
706722
[collaborativeBatchAddBlocks, setSelectedEdges]

apps/sim/app/workspace/providers/socket-provider.tsx

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -406,43 +406,29 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
406406
socketInstance.on('cursor-update', (data) => {
407407
setPresenceUsers((prev) => {
408408
const existingIndex = prev.findIndex((user) => user.socketId === data.socketId)
409-
if (existingIndex !== -1) {
410-
return prev.map((user) =>
411-
user.socketId === data.socketId ? { ...user, cursor: data.cursor } : user
412-
)
409+
if (existingIndex === -1) {
410+
logger.debug('Received cursor-update for unknown user', { socketId: data.socketId })
411+
return prev
413412
}
414-
return [
415-
...prev,
416-
{
417-
socketId: data.socketId,
418-
userId: data.userId,
419-
userName: data.userName,
420-
avatarUrl: data.avatarUrl,
421-
cursor: data.cursor,
422-
},
423-
]
413+
return prev.map((user) =>
414+
user.socketId === data.socketId ? { ...user, cursor: data.cursor } : user
415+
)
424416
})
425417
eventHandlers.current.cursorUpdate?.(data)
426418
})
427419

428420
socketInstance.on('selection-update', (data) => {
429421
setPresenceUsers((prev) => {
430422
const existingIndex = prev.findIndex((user) => user.socketId === data.socketId)
431-
if (existingIndex !== -1) {
432-
return prev.map((user) =>
433-
user.socketId === data.socketId ? { ...user, selection: data.selection } : user
434-
)
435-
}
436-
return [
437-
...prev,
438-
{
423+
if (existingIndex === -1) {
424+
logger.debug('Received selection-update for unknown user', {
439425
socketId: data.socketId,
440-
userId: data.userId,
441-
userName: data.userName,
442-
avatarUrl: data.avatarUrl,
443-
selection: data.selection,
444-
},
445-
]
426+
})
427+
return prev
428+
}
429+
return prev.map((user) =>
430+
user.socketId === data.socketId ? { ...user, selection: data.selection } : user
431+
)
446432
})
447433
eventHandlers.current.selectionUpdate?.(data)
448434
})

apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2499,7 +2499,9 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, any> = {
24992499
async execute(params: EditWorkflowParams, context?: { userId: string }): Promise<any> {
25002500
const logger = createLogger('EditWorkflowServerTool')
25012501
const { operations, workflowId, currentUserWorkflow } = params
2502-
if (!operations || operations.length === 0) throw new Error('operations are required')
2502+
if (!Array.isArray(operations) || operations.length === 0) {
2503+
throw new Error('operations are required and must be an array')
2504+
}
25032505
if (!workflowId) throw new Error('workflowId is required')
25042506

25052507
logger.info('Executing edit_workflow', {

apps/sim/lib/workflows/persistence/utils.ts

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import crypto from 'crypto'
22
import {
33
db,
4-
webhook,
54
workflow,
65
workflowBlocks,
76
workflowDeploymentVersion,
@@ -22,7 +21,6 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w
2221
const logger = createLogger('WorkflowDBHelpers')
2322

2423
export type WorkflowDeploymentVersion = InferSelectModel<typeof workflowDeploymentVersion>
25-
type WebhookRecord = InferSelectModel<typeof webhook>
2624
type SubflowInsert = InferInsertModel<typeof workflowSubflows>
2725

2826
export interface WorkflowDeploymentVersionResponse {
@@ -337,18 +335,6 @@ export async function saveWorkflowToNormalizedTables(
337335

338336
// Start a transaction
339337
await db.transaction(async (tx) => {
340-
// Snapshot existing webhooks before deletion to preserve them through the cycle
341-
let existingWebhooks: WebhookRecord[] = []
342-
try {
343-
existingWebhooks = await tx.select().from(webhook).where(eq(webhook.workflowId, workflowId))
344-
} catch (webhookError) {
345-
// Webhook table might not be available in test environments
346-
logger.debug('Could not load webhooks before save, skipping preservation', {
347-
error: webhookError instanceof Error ? webhookError.message : String(webhookError),
348-
})
349-
}
350-
351-
// Clear existing data for this workflow
352338
await Promise.all([
353339
tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId)),
354340
tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, workflowId)),
@@ -419,42 +405,6 @@ export async function saveWorkflowToNormalizedTables(
419405
if (subflowInserts.length > 0) {
420406
await tx.insert(workflowSubflows).values(subflowInserts)
421407
}
422-
423-
// Re-insert preserved webhooks if any exist and their blocks still exist
424-
if (existingWebhooks.length > 0) {
425-
try {
426-
const webhookInserts = existingWebhooks
427-
.filter((wh) => !!state.blocks?.[wh.blockId ?? ''])
428-
.map((wh) => ({
429-
id: wh.id,
430-
workflowId: wh.workflowId,
431-
blockId: wh.blockId,
432-
path: wh.path,
433-
provider: wh.provider,
434-
providerConfig: wh.providerConfig,
435-
credentialSetId: wh.credentialSetId,
436-
isActive: wh.isActive,
437-
createdAt: wh.createdAt,
438-
updatedAt: new Date(),
439-
}))
440-
441-
if (webhookInserts.length > 0) {
442-
await tx.insert(webhook).values(webhookInserts)
443-
logger.debug(`Preserved ${webhookInserts.length} webhook(s) through workflow save`, {
444-
workflowId,
445-
})
446-
}
447-
} catch (webhookInsertError) {
448-
// Webhook preservation is optional - don't fail the entire save if it errors
449-
logger.warn('Could not preserve webhooks during save', {
450-
error:
451-
webhookInsertError instanceof Error
452-
? webhookInsertError.message
453-
: String(webhookInsertError),
454-
workflowId,
455-
})
456-
}
457-
}
458408
})
459409

460410
return { success: true }

0 commit comments

Comments
 (0)