Skip to content

Commit 76503be

Browse files
committed
Merge branch 'staging' into feat/run-from-block-2
2 parents 828d74f + 72a2f79 commit 76503be

File tree

53 files changed

+1680
-1585
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1680
-1585
lines changed

.claude/commands/add-tools.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,21 @@ export const {serviceName}{Action}Tool: ToolConfig<
5555
},
5656

5757
params: {
58-
// Hidden params (system-injected)
58+
// Hidden params (system-injected, only use hidden for oauth accessToken)
5959
accessToken: {
6060
type: 'string',
6161
required: true,
6262
visibility: 'hidden',
6363
description: 'OAuth access token',
6464
},
65-
// User-only params (credentials, IDs user must provide)
65+
// User-only params (credentials, api key, IDs user must provide)
6666
someId: {
6767
type: 'string',
6868
required: true,
6969
visibility: 'user-only',
7070
description: 'The ID of the resource',
7171
},
72-
// User-or-LLM params (can be provided by user OR computed by LLM)
72+
// User-or-LLM params (everything else, can be provided by user OR computed by LLM)
7373
query: {
7474
type: 'string',
7575
required: false, // Use false for optional
@@ -114,8 +114,8 @@ export const {serviceName}{Action}Tool: ToolConfig<
114114

115115
### Visibility Options
116116
- `'hidden'` - System-injected (OAuth tokens, internal params). User never sees.
117-
- `'user-only'` - User must provide (credentials, account-specific IDs)
118-
- `'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters)
117+
- `'user-only'` - User must provide (credentials, api keys, account-specific IDs)
118+
- `'user-or-llm'` - User provides OR LLM can compute (search queries, content, filters, most fall into this category)
119119

120120
### Parameter Types
121121
- `'string'` - Text values

apps/docs/components/icons.tsx

Lines changed: 220 additions & 165 deletions
Large diffs are not rendered by default.

apps/sim/app/api/workflows/[id]/autolayout/route.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ const AutoLayoutRequestSchema = z.object({
3535
})
3636
.optional()
3737
.default({}),
38-
// Optional: if provided, use these blocks instead of loading from DB
39-
// This allows using blocks with live measurements from the UI
38+
gridSize: z.number().min(0).max(50).optional(),
4039
blocks: z.record(z.any()).optional(),
4140
edges: z.array(z.any()).optional(),
4241
loops: z.record(z.any()).optional(),
@@ -53,7 +52,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
5352
const { id: workflowId } = await params
5453

5554
try {
56-
// Get the session
5755
const session = await getSession()
5856
if (!session?.user?.id) {
5957
logger.warn(`[${requestId}] Unauthorized autolayout attempt for workflow ${workflowId}`)
@@ -62,15 +60,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
6260

6361
const userId = session.user.id
6462

65-
// Parse request body
6663
const body = await request.json()
6764
const layoutOptions = AutoLayoutRequestSchema.parse(body)
6865

6966
logger.info(`[${requestId}] Processing autolayout request for workflow ${workflowId}`, {
7067
userId,
7168
})
7269

73-
// Fetch the workflow to check ownership/access
7470
const accessContext = await getWorkflowAccessContext(workflowId, userId)
7571
const workflowData = accessContext?.workflow
7672

@@ -79,7 +75,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
7975
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
8076
}
8177

82-
// Check if user has permission to update this workflow
8378
const canUpdate =
8479
accessContext?.isOwner ||
8580
(workflowData.workspaceId
@@ -94,8 +89,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
9489
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
9590
}
9691

97-
// Use provided blocks/edges if available (with live measurements from UI),
98-
// otherwise load from database
9992
let currentWorkflowData: NormalizedWorkflowData | null
10093

10194
if (layoutOptions.blocks && layoutOptions.edges) {
@@ -125,6 +118,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
125118
y: layoutOptions.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
126119
},
127120
alignment: layoutOptions.alignment,
121+
gridSize: layoutOptions.gridSize,
128122
}
129123

130124
const layoutResult = applyAutoLayout(

apps/sim/app/api/yaml/autolayout/route.ts

Lines changed: 0 additions & 108 deletions
This file was deleted.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-auto-layout.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
33
import { useReactFlow } from 'reactflow'
44
import type { AutoLayoutOptions } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
55
import { applyAutoLayoutAndUpdateStore as applyAutoLayoutStandalone } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils'
6+
import { useSnapToGridSize } from '@/hooks/queries/general-settings'
67
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
78

89
export type { AutoLayoutOptions }
@@ -13,21 +14,28 @@ const logger = createLogger('useAutoLayout')
1314
* Hook providing auto-layout functionality for workflows.
1415
* Binds workflowId context and provides memoized callback for React components.
1516
* Includes automatic fitView animation after successful layout.
17+
* Automatically uses the user's snap-to-grid setting for grid-aligned layout.
1618
*
1719
* Note: This hook requires a ReactFlowProvider ancestor.
1820
*/
1921
export function useAutoLayout(workflowId: string | null) {
2022
const reactFlowInstance = useReactFlow()
2123
const { fitViewToBounds } = useCanvasViewport(reactFlowInstance)
24+
const snapToGridSize = useSnapToGridSize()
2225

2326
const applyAutoLayoutAndUpdateStore = useCallback(
2427
async (options: AutoLayoutOptions = {}) => {
2528
if (!workflowId) {
2629
return { success: false, error: 'No workflow ID provided' }
2730
}
28-
return applyAutoLayoutStandalone(workflowId, options)
31+
// Include gridSize from user's snap-to-grid setting
32+
const optionsWithGrid: AutoLayoutOptions = {
33+
...options,
34+
gridSize: options.gridSize ?? (snapToGridSize > 0 ? snapToGridSize : undefined),
35+
}
36+
return applyAutoLayoutStandalone(workflowId, optionsWithGrid)
2937
},
30-
[workflowId]
38+
[workflowId, snapToGridSize]
3139
)
3240

3341
/**

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface AutoLayoutOptions {
2121
x?: number
2222
y?: number
2323
}
24+
gridSize?: number
2425
}
2526

2627
/**
@@ -62,6 +63,7 @@ export async function applyAutoLayoutAndUpdateStore(
6263
x: options.padding?.x ?? DEFAULT_LAYOUT_PADDING.x,
6364
y: options.padding?.y ?? DEFAULT_LAYOUT_PADDING.y,
6465
},
66+
gridSize: options.gridSize,
6567
}
6668

6769
// Call the autolayout API route

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

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,41 +2352,20 @@ const WorkflowContent = React.memo(() => {
23522352
window.removeEventListener('remove-from-subflow', handleRemoveFromSubflow as EventListener)
23532353
}, [blocks, edgesForDisplay, getNodeAbsolutePosition, collaborativeBatchUpdateParent])
23542354

2355-
/** Handles node changes - applies changes and resolves parent-child selection conflicts. */
2356-
const onNodesChange = useCallback(
2357-
(changes: NodeChange[]) => {
2358-
selectedIdsRef.current = null
2359-
setDisplayNodes((nds) => {
2360-
const updated = applyNodeChanges(changes, nds)
2361-
const hasSelectionChange = changes.some((c) => c.type === 'select')
2362-
if (!hasSelectionChange) return updated
2363-
const resolved = resolveParentChildSelectionConflicts(updated, blocks)
2364-
selectedIdsRef.current = resolved.filter((node) => node.selected).map((node) => node.id)
2365-
return resolved
2366-
})
2367-
const selectedIds = selectedIdsRef.current as string[] | null
2368-
if (selectedIds !== null) {
2369-
syncPanelWithSelection(selectedIds)
2370-
}
2371-
},
2372-
[blocks]
2373-
)
2374-
23752355
/**
2376-
* Updates container dimensions in displayNodes during drag.
2377-
* This allows live resizing of containers as their children are dragged.
2356+
* Updates container dimensions in displayNodes during drag or keyboard movement.
23782357
*/
2379-
const updateContainerDimensionsDuringDrag = useCallback(
2380-
(draggedNodeId: string, draggedNodePosition: { x: number; y: number }) => {
2381-
const parentId = blocks[draggedNodeId]?.data?.parentId
2358+
const updateContainerDimensionsDuringMove = useCallback(
2359+
(movedNodeId: string, movedNodePosition: { x: number; y: number }) => {
2360+
const parentId = blocks[movedNodeId]?.data?.parentId
23822361
if (!parentId) return
23832362

23842363
setDisplayNodes((currentNodes) => {
23852364
const childNodes = currentNodes.filter((n) => n.parentId === parentId)
23862365
if (childNodes.length === 0) return currentNodes
23872366

23882367
const childPositions = childNodes.map((node) => {
2389-
const nodePosition = node.id === draggedNodeId ? draggedNodePosition : node.position
2368+
const nodePosition = node.id === movedNodeId ? movedNodePosition : node.position
23902369
const { width, height } = getBlockDimensions(node.id)
23912370
return { x: nodePosition.x, y: nodePosition.y, width, height }
23922371
})
@@ -2417,6 +2396,55 @@ const WorkflowContent = React.memo(() => {
24172396
[blocks, getBlockDimensions]
24182397
)
24192398

2399+
/** Handles node changes - applies changes and resolves parent-child selection conflicts. */
2400+
const onNodesChange = useCallback(
2401+
(changes: NodeChange[]) => {
2402+
selectedIdsRef.current = null
2403+
setDisplayNodes((nds) => {
2404+
const updated = applyNodeChanges(changes, nds)
2405+
const hasSelectionChange = changes.some((c) => c.type === 'select')
2406+
if (!hasSelectionChange) return updated
2407+
const resolved = resolveParentChildSelectionConflicts(updated, blocks)
2408+
selectedIdsRef.current = resolved.filter((node) => node.selected).map((node) => node.id)
2409+
return resolved
2410+
})
2411+
const selectedIds = selectedIdsRef.current as string[] | null
2412+
if (selectedIds !== null) {
2413+
syncPanelWithSelection(selectedIds)
2414+
}
2415+
2416+
// Handle position changes (e.g., from keyboard arrow key movement)
2417+
// Update container dimensions when child nodes are moved and persist to backend
2418+
// Only persist if not in a drag operation (drag-end is handled by onNodeDragStop)
2419+
const isInDragOperation =
2420+
getDragStartPosition() !== null || multiNodeDragStartRef.current.size > 0
2421+
const keyboardPositionUpdates: Array<{ id: string; position: { x: number; y: number } }> = []
2422+
for (const change of changes) {
2423+
if (
2424+
change.type === 'position' &&
2425+
!change.dragging &&
2426+
'position' in change &&
2427+
change.position
2428+
) {
2429+
updateContainerDimensionsDuringMove(change.id, change.position)
2430+
if (!isInDragOperation) {
2431+
keyboardPositionUpdates.push({ id: change.id, position: change.position })
2432+
}
2433+
}
2434+
}
2435+
// Persist keyboard movements to backend for collaboration sync
2436+
if (keyboardPositionUpdates.length > 0) {
2437+
collaborativeBatchUpdatePositions(keyboardPositionUpdates)
2438+
}
2439+
},
2440+
[
2441+
blocks,
2442+
updateContainerDimensionsDuringMove,
2443+
collaborativeBatchUpdatePositions,
2444+
getDragStartPosition,
2445+
]
2446+
)
2447+
24202448
/**
24212449
* Effect to resize loops when nodes change (add/remove/position change).
24222450
* Runs on structural changes only - not during drag (position-only changes).
@@ -2661,7 +2689,7 @@ const WorkflowContent = React.memo(() => {
26612689

26622690
// If the node is inside a container, update container dimensions during drag
26632691
if (currentParentId) {
2664-
updateContainerDimensionsDuringDrag(node.id, node.position)
2692+
updateContainerDimensionsDuringMove(node.id, node.position)
26652693
}
26662694

26672695
// Check if this is a starter block - starter blocks should never be in containers
@@ -2778,7 +2806,7 @@ const WorkflowContent = React.memo(() => {
27782806
blocks,
27792807
getNodeAbsolutePosition,
27802808
getNodeDepth,
2781-
updateContainerDimensionsDuringDrag,
2809+
updateContainerDimensionsDuringMove,
27822810
highlightContainerNode,
27832811
]
27842812
)

0 commit comments

Comments
 (0)