Skip to content

Commit 46f6383

Browse files
committed
Merge branch 'staging' into feat/workflow-as-mcp
2 parents 6856b26 + f21eaf1 commit 46f6383

File tree

42 files changed

+9053
-344
lines changed

Some content is hidden

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

42 files changed

+9053
-344
lines changed

apps/sim/app/api/users/me/settings/route.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ const SettingsSchema = z.object({
2626
showTrainingControls: z.boolean().optional(),
2727
superUserModeEnabled: z.boolean().optional(),
2828
errorNotificationsEnabled: z.boolean().optional(),
29+
snapToGridSize: z.number().min(0).max(50).optional(),
2930
})
3031

31-
// Default settings values
3232
const defaultSettings = {
3333
theme: 'system',
3434
autoConnect: true,
@@ -38,6 +38,7 @@ const defaultSettings = {
3838
showTrainingControls: false,
3939
superUserModeEnabled: false,
4040
errorNotificationsEnabled: true,
41+
snapToGridSize: 0,
4142
}
4243

4344
export async function GET() {
@@ -46,7 +47,6 @@ export async function GET() {
4647
try {
4748
const session = await getSession()
4849

49-
// Return default settings for unauthenticated users instead of 401 error
5050
if (!session?.user?.id) {
5151
logger.info(`[${requestId}] Returning default settings for unauthenticated user`)
5252
return NextResponse.json({ data: defaultSettings }, { status: 200 })
@@ -72,13 +72,13 @@ export async function GET() {
7272
showTrainingControls: userSettings.showTrainingControls ?? false,
7373
superUserModeEnabled: userSettings.superUserModeEnabled ?? true,
7474
errorNotificationsEnabled: userSettings.errorNotificationsEnabled ?? true,
75+
snapToGridSize: userSettings.snapToGridSize ?? 0,
7576
},
7677
},
7778
{ status: 200 }
7879
)
7980
} catch (error: any) {
8081
logger.error(`[${requestId}] Settings fetch error`, error)
81-
// Return default settings on error instead of error response
8282
return NextResponse.json({ data: defaultSettings }, { status: 200 })
8383
}
8484
}
@@ -89,7 +89,6 @@ export async function PATCH(request: Request) {
8989
try {
9090
const session = await getSession()
9191

92-
// Return success for unauthenticated users instead of error
9392
if (!session?.user?.id) {
9493
logger.info(
9594
`[${requestId}] Settings update attempted by unauthenticated user - acknowledged without saving`
@@ -103,7 +102,6 @@ export async function PATCH(request: Request) {
103102
try {
104103
const validatedData = SettingsSchema.parse(body)
105104

106-
// Store the settings
107105
await db
108106
.insert(settings)
109107
.values({
@@ -135,7 +133,6 @@ export async function PATCH(request: Request) {
135133
}
136134
} catch (error: any) {
137135
logger.error(`[${requestId}] Settings update error`, error)
138-
// Return success on error instead of error response
139136
return NextResponse.json({ success: true }, { status: 200 })
140137
}
141138
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/code/code.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useWand } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-
3737
import type { GenerationType } from '@/blocks/types'
3838
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
3939
import { useTagSelection } from '@/hooks/use-tag-selection'
40-
import { normalizeBlockName } from '@/stores/workflows/utils'
40+
import { normalizeName } from '@/stores/workflows/utils'
4141

4242
const logger = createLogger('Code')
4343

@@ -602,7 +602,7 @@ export function Code({
602602

603603
const inner = reference.slice(1, -1)
604604
const [prefix] = inner.split('.')
605-
const normalizedPrefix = normalizeBlockName(prefix)
605+
const normalizedPrefix = normalizeName(prefix)
606606

607607
if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {
608608
return true

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/c
3333
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
3434
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
3535
import { useTagSelection } from '@/hooks/use-tag-selection'
36-
import { normalizeBlockName } from '@/stores/workflows/utils'
36+
import { normalizeName } from '@/stores/workflows/utils'
3737
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3838

3939
const logger = createLogger('ConditionInput')
@@ -139,7 +139,7 @@ export function ConditionInput({
139139

140140
const inner = reference.slice(1, -1)
141141
const [prefix] = inner.split('.')
142-
const normalizedPrefix = normalizeBlockName(prefix)
142+
const normalizedPrefix = normalizeName(prefix)
143143

144144
if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {
145145
return true

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ReactNode } from 'react'
44
import { splitReferenceSegment } from '@/lib/workflows/sanitization/references'
55
import { REFERENCE } from '@/executor/constants'
66
import { createCombinedPattern } from '@/executor/utils/reference-validation'
7-
import { normalizeBlockName } from '@/stores/workflows/utils'
7+
import { normalizeName } from '@/stores/workflows/utils'
88

99
export interface HighlightContext {
1010
accessiblePrefixes?: Set<string>
@@ -31,7 +31,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
3131

3232
const inner = reference.slice(1, -1)
3333
const [prefix] = inner.split('.')
34-
const normalizedPrefix = normalizeBlockName(prefix)
34+
const normalizedPrefix = normalizeName(prefix)
3535

3636
if (SYSTEM_PREFIXES.has(normalizedPrefix)) {
3737
return true

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { useVariablesStore } from '@/stores/panel/variables/store'
3434
import type { Variable } from '@/stores/panel/variables/types'
3535
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
3636
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
37+
import { normalizeName } from '@/stores/workflows/utils'
3738
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3839
import type { BlockState } from '@/stores/workflows/workflow/types'
3940
import { getTool } from '@/tools/utils'
@@ -117,20 +118,6 @@ const TAG_PREFIXES = {
117118
VARIABLE: 'variable.',
118119
} as const
119120

120-
/**
121-
* Normalizes a block name by removing spaces and converting to lowercase
122-
*/
123-
const normalizeBlockName = (blockName: string): string => {
124-
return blockName.replace(/\s+/g, '').toLowerCase()
125-
}
126-
127-
/**
128-
* Normalizes a variable name by removing spaces
129-
*/
130-
const normalizeVariableName = (variableName: string): string => {
131-
return variableName.replace(/\s+/g, '')
132-
}
133-
134121
/**
135122
* Ensures the root tag is present in the tags array
136123
*/
@@ -521,7 +508,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
521508
if (sourceBlock.type === 'loop' || sourceBlock.type === 'parallel') {
522509
const mockConfig = { outputs: { results: 'array' } }
523510
const blockName = sourceBlock.name || sourceBlock.type
524-
const normalizedBlockName = normalizeBlockName(blockName)
511+
const normalizedBlockName = normalizeName(blockName)
525512

526513
const outputPaths = generateOutputPaths(mockConfig.outputs)
527514
const blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
@@ -542,7 +529,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
542529
}
543530

544531
const blockName = sourceBlock.name || sourceBlock.type
545-
const normalizedBlockName = normalizeBlockName(blockName)
532+
const normalizedBlockName = normalizeName(blockName)
546533

547534
const mergedSubBlocks = getMergedSubBlocks(activeSourceBlockId)
548535
const responseFormatValue = mergedSubBlocks?.responseFormat?.value
@@ -735,12 +722,12 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
735722
)
736723

737724
const variableTags = validVariables.map(
738-
(variable: Variable) => `${TAG_PREFIXES.VARIABLE}${normalizeVariableName(variable.name)}`
725+
(variable: Variable) => `${TAG_PREFIXES.VARIABLE}${normalizeName(variable.name)}`
739726
)
740727

741728
const variableInfoMap = validVariables.reduce(
742729
(acc, variable) => {
743-
const tagName = `${TAG_PREFIXES.VARIABLE}${normalizeVariableName(variable.name)}`
730+
const tagName = `${TAG_PREFIXES.VARIABLE}${normalizeName(variable.name)}`
744731
acc[tagName] = {
745732
type: variable.type,
746733
id: variable.id,
@@ -865,7 +852,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
865852

866853
const mockConfig = { outputs: { results: 'array' } }
867854
const blockName = accessibleBlock.name || accessibleBlock.type
868-
const normalizedBlockName = normalizeBlockName(blockName)
855+
const normalizedBlockName = normalizeName(blockName)
869856

870857
const outputPaths = generateOutputPaths(mockConfig.outputs)
871858
let blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
@@ -885,7 +872,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
885872
}
886873

887874
const blockName = accessibleBlock.name || accessibleBlock.type
888-
const normalizedBlockName = normalizeBlockName(blockName)
875+
const normalizedBlockName = normalizeName(blockName)
889876

890877
const mergedSubBlocks = getMergedSubBlocks(accessibleBlockId)
891878
const responseFormatValue = mergedSubBlocks?.responseFormat?.value

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-subflow-editor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { checkTagTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/co
99
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
1010
import { createEnvVarPattern, createReferencePattern } from '@/executor/utils/reference-validation'
1111
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
12-
import { normalizeBlockName } from '@/stores/workflows/utils'
12+
import { normalizeName } from '@/stores/workflows/utils'
1313
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1414
import type { BlockState } from '@/stores/workflows/workflow/types'
1515

@@ -110,7 +110,7 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
110110

111111
const inner = reference.slice(1, -1)
112112
const [prefix] = inner.split('.')
113-
const normalizedPrefix = normalizeBlockName(prefix)
113+
const normalizedPrefix = normalizeName(prefix)
114114

115115
if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {
116116
return true

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo } from 'react'
22
import { useShallow } from 'zustand/react/shallow'
33
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
44
import { SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/sanitization/references'
5-
import { normalizeBlockName } from '@/stores/workflows/utils'
5+
import { normalizeName } from '@/stores/workflows/utils'
66
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
77
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
88

@@ -53,10 +53,10 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set<str
5353

5454
const prefixes = new Set<string>()
5555
accessibleIds.forEach((id) => {
56-
prefixes.add(normalizeBlockName(id))
56+
prefixes.add(normalizeName(id))
5757
const block = blocks[id]
5858
if (block?.name) {
59-
prefixes.add(normalizeBlockName(block.name))
59+
prefixes.add(normalizeName(block.name))
6060
}
6161
})
6262

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ const edgeTypes: EdgeTypes = {
9292

9393
/** ReactFlow configuration constants. */
9494
const defaultEdgeOptions = { type: 'custom' }
95-
const snapGrid: [number, number] = [20, 20]
9695
const reactFlowFitViewOptions = { padding: 0.6 } as const
9796
const reactFlowProOptions = { hideAttribution: true } as const
9897

@@ -160,6 +159,14 @@ const WorkflowContent = React.memo(() => {
160159
// Training modal state
161160
const showTrainingModal = useCopilotTrainingStore((state) => state.showModal)
162161

162+
// Snap to grid settings
163+
const snapToGridSize = useGeneralStore((state) => state.snapToGridSize)
164+
const snapToGrid = snapToGridSize > 0
165+
const snapGrid: [number, number] = useMemo(
166+
() => [snapToGridSize, snapToGridSize],
167+
[snapToGridSize]
168+
)
169+
163170
// Handle copilot stream cleanup on page unload and component unmount
164171
useStreamCleanup(copilotCleanup)
165172

@@ -2311,7 +2318,7 @@ const WorkflowContent = React.memo(() => {
23112318
onNodeDrag={effectivePermissions.canEdit ? onNodeDrag : undefined}
23122319
onNodeDragStop={effectivePermissions.canEdit ? onNodeDragStop : undefined}
23132320
onNodeDragStart={effectivePermissions.canEdit ? onNodeDragStart : undefined}
2314-
snapToGrid={false}
2321+
snapToGrid={snapToGrid}
23152322
snapGrid={snapGrid}
23162323
elevateEdgesOnSelect={true}
23172324
onlyRenderVisibleElements={false}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/environment/environment.tsx

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ interface UIEnvironmentVariable {
5252
id?: number
5353
}
5454

55+
/**
56+
* Validates an environment variable key.
57+
* Returns an error message if invalid, undefined if valid.
58+
*/
59+
function validateEnvVarKey(key: string): string | undefined {
60+
if (!key) return undefined
61+
if (key.includes(' ')) return 'Spaces are not allowed'
62+
if (!ENV_VAR_PATTERN.test(key)) return 'Only letters, numbers, and underscores allowed'
63+
return undefined
64+
}
65+
5566
interface EnvironmentVariablesProps {
5667
registerBeforeLeaveHandler?: (handler: (onProceed: () => void) => void) => void
5768
}
@@ -222,6 +233,10 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
222233
return envVars.some((envVar) => !!envVar.key && Object.hasOwn(workspaceVars, envVar.key))
223234
}, [envVars, workspaceVars])
224235

236+
const hasInvalidKeys = useMemo(() => {
237+
return envVars.some((envVar) => !!envVar.key && validateEnvVarKey(envVar.key))
238+
}, [envVars])
239+
225240
useEffect(() => {
226241
hasChangesRef.current = hasChanges
227242
}, [hasChanges])
@@ -551,6 +566,7 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
551566
const renderEnvVarRow = useCallback(
552567
(envVar: UIEnvironmentVariable, originalIndex: number) => {
553568
const isConflict = !!envVar.key && Object.hasOwn(workspaceVars, envVar.key)
569+
const keyError = validateEnvVarKey(envVar.key)
554570
const maskedValueStyle =
555571
focusedValueIndex !== originalIndex && !isConflict
556572
? ({ WebkitTextSecurity: 'disc' } as React.CSSProperties)
@@ -571,7 +587,7 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
571587
spellCheck='false'
572588
readOnly
573589
onFocus={(e) => e.target.removeAttribute('readOnly')}
574-
className={`h-9 ${isConflict ? conflictClassName : ''}`}
590+
className={`h-9 ${isConflict ? conflictClassName : ''} ${keyError ? 'border-[var(--text-error)]' : ''}`}
575591
/>
576592
<div />
577593
<EmcnInput
@@ -627,7 +643,12 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
627643
</Tooltip.Root>
628644
</div>
629645
</div>
630-
{isConflict && (
646+
{keyError && (
647+
<div className='col-span-3 mt-[4px] text-[12px] text-[var(--text-error)] leading-tight'>
648+
{keyError}
649+
</div>
650+
)}
651+
{isConflict && !keyError && (
631652
<div className='col-span-3 mt-[4px] text-[12px] text-[var(--text-error)] leading-tight'>
632653
Workspace variable with the same name overrides this. Rename your personal key to use
633654
it.
@@ -707,14 +728,17 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
707728
<Tooltip.Trigger asChild>
708729
<Button
709730
onClick={handleSave}
710-
disabled={isLoading || !hasChanges || hasConflicts}
731+
disabled={isLoading || !hasChanges || hasConflicts || hasInvalidKeys}
711732
variant='primary'
712-
className={`${PRIMARY_BUTTON_STYLES} ${hasConflicts ? 'cursor-not-allowed opacity-50' : ''}`}
733+
className={`${PRIMARY_BUTTON_STYLES} ${hasConflicts || hasInvalidKeys ? 'cursor-not-allowed opacity-50' : ''}`}
713734
>
714735
Save
715736
</Button>
716737
</Tooltip.Trigger>
717738
{hasConflicts && <Tooltip.Content>Resolve all conflicts before saving</Tooltip.Content>}
739+
{hasInvalidKeys && !hasConflicts && (
740+
<Tooltip.Content>Fix invalid variable names before saving</Tooltip.Content>
741+
)}
718742
</Tooltip.Root>
719743
</div>
720744

@@ -808,27 +832,31 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment
808832
<ModalHeader>Unsaved Changes</ModalHeader>
809833
<ModalBody>
810834
<p className='text-[12px] text-[var(--text-tertiary)]'>
811-
{hasConflicts
812-
? 'You have unsaved changes, but conflicts must be resolved before saving. You can discard your changes to close the modal.'
835+
{hasConflicts || hasInvalidKeys
836+
? `You have unsaved changes, but ${hasConflicts ? 'conflicts must be resolved' : 'invalid variable names must be fixed'} before saving. You can discard your changes to close the modal.`
813837
: 'You have unsaved changes. Do you want to save them before closing?'}
814838
</p>
815839
</ModalBody>
816840
<ModalFooter>
817841
<Button variant='default' onClick={handleCancel}>
818842
Discard Changes
819843
</Button>
820-
{hasConflicts ? (
844+
{hasConflicts || hasInvalidKeys ? (
821845
<Tooltip.Root>
822846
<Tooltip.Trigger asChild>
823847
<Button
824848
disabled={true}
825849
variant='primary'
826-
className='cursor-not-allowed opacity-50'
850+
className={`${PRIMARY_BUTTON_STYLES} cursor-not-allowed opacity-50`}
827851
>
828852
Save Changes
829853
</Button>
830854
</Tooltip.Trigger>
831-
<Tooltip.Content>Resolve all conflicts before saving</Tooltip.Content>
855+
<Tooltip.Content>
856+
{hasConflicts
857+
? 'Resolve all conflicts before saving'
858+
: 'Fix invalid variable names before saving'}
859+
</Tooltip.Content>
832860
</Tooltip.Root>
833861
) : (
834862
<Button onClick={handleSave} variant='primary' className={PRIMARY_BUTTON_STYLES}>

0 commit comments

Comments
 (0)