Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f8e47e0
improvement(workflow): refine canvas interactions and rendering
andresdjasso Jul 24, 2026
68491eb
fix(workflow): keep outputs on the right, focus newly created blocks
andresdjasso Jul 27, 2026
6f20689
fix(workflow): floor header-only card height, adopt brand tag palette
andresdjasso Jul 28, 2026
f543974
improvement(workflow): polish workflow canvas interactions
andresdjasso Aug 1, 2026
c930c05
fix(workflow): restyle loop drop target outline
andresdjasso Aug 1, 2026
2ab461f
fix(workflow): shorten human block catalog label
andresdjasso Aug 1, 2026
7f50240
fix(workflow): canonicalize realtime edge handles
andresdjasso Aug 1, 2026
256c97b
improvement(notes): add focused canvas editing
andresdjasso Aug 3, 2026
434adba
improvement(workflow): refine live execution feedback
andresdjasso Aug 4, 2026
3783ff1
fix(workflow): align running action loader
andresdjasso Aug 4, 2026
081a359
fix(workflow): preserve running control artwork
andresdjasso Aug 4, 2026
4b1fab6
feat(workflow): unify core block colors
andresdjasso Aug 5, 2026
76fb897
fix(workflow): neutralize content block color
andresdjasso Aug 5, 2026
5f3c385
fix(workflow): lighten content block tone
andresdjasso Aug 5, 2026
7a42b6f
fix(workflow): align content block ink
andresdjasso Aug 5, 2026
78f6fa8
fix(workflow): update content block teal
andresdjasso Aug 5, 2026
33d7609
fix(workflow): brighten content block teal
andresdjasso Aug 5, 2026
034a107
fix(workflow): replace legacy deployments icon
andresdjasso Aug 5, 2026
1916b8e
fix(workflows): apply semantic colors to native triggers
andresdjasso Aug 5, 2026
773daf8
fix(workflows): theme running stop hover in dark mode
andresdjasso Aug 5, 2026
b8bc0c6
fix(workflows): suppress hidden action tooltips while running
andresdjasso Aug 5, 2026
2ddd441
fix(workflows): blend running loader into execution swell
andresdjasso Aug 5, 2026
04f0ffc
fix(sidebar): show route workspace identity fallback
andresdjasso Aug 5, 2026
ceb0595
improvement(workflow): redesign editor and configuration
andresdjasso Aug 10, 2026
445b351
improvement(workflow): move canvas controls into header
andresdjasso Aug 11, 2026
2aa64c0
improvement(workflow): refine canvas mode controls
andresdjasso Aug 11, 2026
420739a
fix(workflow): restore compact run button
andresdjasso Aug 11, 2026
2bd2692
improvement(editor): separate block header from settings
andresdjasso Aug 11, 2026
de94429
improvement(emcn): add compact chip switch size
andresdjasso Aug 11, 2026
a018173
improvement(workflow): organize toolbar and editor views
andresdjasso Aug 11, 2026
ac9be72
improvement(workflow): refine editor toolbar and empty states
andresdjasso Aug 11, 2026
f43d6d5
improvement(workflow): refine toolbar search morph
andresdjasso Aug 13, 2026
3be95ed
improvement(deploy): redesign workflow deployment experience
andresdjasso Aug 14, 2026
24affd7
chore(workflow): sync editor branch with staging
andresdjasso Aug 14, 2026
d6c5d7e
improvement(workflow): redesign editor and configuration
andresdjasso Aug 10, 2026
4dcdd5a
improvement(workflow): move canvas controls into header
andresdjasso Aug 11, 2026
a6a55d2
improvement(workflow): refine canvas mode controls
andresdjasso Aug 11, 2026
660f967
fix(workflow): restore compact run button
andresdjasso Aug 11, 2026
17b3330
improvement(editor): separate block header from settings
andresdjasso Aug 11, 2026
3457bb0
improvement(emcn): add compact chip switch size
andresdjasso Aug 11, 2026
51d5397
improvement(workflow): organize toolbar and editor views
andresdjasso Aug 11, 2026
1fc49a8
improvement(workflow): refine editor toolbar and empty states
andresdjasso Aug 11, 2026
375186c
improvement(workflow): refine toolbar search morph
andresdjasso Aug 13, 2026
7fe6145
improvement(deploy): redesign workflow deployment experience
andresdjasso Aug 14, 2026
087a21d
fix(workflow): reconcile editor and deploy experience with staging
andresdjasso Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions apps/realtime/src/database/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,33 @@ async function handleBlockOperationTx(
break
}

case BLOCK_OPERATIONS.UPDATE_DESCRIPTION: {
if (!payload.id || payload.description === undefined) {
throw new Error('Missing required fields for update description operation')
}

const updateResult = await tx
.update(workflowBlocks)
.set({
data: sql`jsonb_set(
coalesce(${workflowBlocks.data}, '{}'::jsonb),
'{description}',
${JSON.stringify(payload.description)}::jsonb,
true
)`,
updatedAt: new Date(),
})
.where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId)))
.returning({ id: workflowBlocks.id })

if (updateResult.length === 0) {
throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`)
}

logger.debug(`Updated block description: ${payload.id}`)
break
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description update skips lock checks

Medium Severity

The new UPDATE_DESCRIPTION handler writes workflowBlocks.data.description with no locked-block or locked-parent guard. Sibling ops such as rename reject edits when the block or its parent is locked, so description changes can still persist for protected blocks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 087a21d. Configure here.


case BLOCK_OPERATIONS.TOGGLE_ENABLED: {
if (!payload.id) {
throw new Error('Missing block ID for toggle enabled operation')
Expand Down
6 changes: 6 additions & 0 deletions apps/realtime/src/middleware/permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ describe('checkRolePermission', () => {
{ operation: 'update', adminAllowed: true, writeAllowed: true, readAllowed: false },
{ operation: 'update-position', adminAllowed: true, writeAllowed: true, readAllowed: false },
{ operation: 'update-name', adminAllowed: true, writeAllowed: true, readAllowed: false },
{
operation: 'update-description',
adminAllowed: true,
writeAllowed: true,
readAllowed: false,
},
{ operation: 'toggle-enabled', adminAllowed: true, writeAllowed: true, readAllowed: false },
{ operation: 'update-parent', adminAllowed: true, writeAllowed: true, readAllowed: false },
{
Expand Down
1 change: 1 addition & 0 deletions apps/realtime/src/middleware/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const WRITE_OPERATIONS: string[] = [
// Block operations
BLOCK_OPERATIONS.UPDATE_POSITION,
BLOCK_OPERATIONS.UPDATE_NAME,
BLOCK_OPERATIONS.UPDATE_DESCRIPTION,
BLOCK_OPERATIONS.TOGGLE_ENABLED,
BLOCK_OPERATIONS.UPDATE_PARENT,
BLOCK_OPERATIONS.UPDATE_ADVANCED_MODE,
Expand Down
19 changes: 8 additions & 11 deletions apps/sim/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import { BrandedLayout } from '@/components/branded-layout'
import { PostHogProvider } from '@/app/_shell/providers/posthog-provider'
import { generateBrandedMetadata, generateThemeCSS } from '@/ee/whitelabeling'
import '@/app/_styles/globals.css'
import {
isChatEnabled,
isHosted,
isReactGrabEnabled,
isReactScanEnabled,
} from '@/lib/core/config/env-flags'
import { isHosted, isReactGrabEnabled, isReactScanEnabled } from '@/lib/core/config/env-flags'
import { DesktopUpdateGate } from '@/app/_shell/desktop-update-gate'
import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler'
import { QueryProvider } from '@/app/_shell/providers/query-provider'
Expand Down Expand Up @@ -155,10 +150,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}

var activeTab = panelState && panelState.activeTab;
// A session that used the Chat tab before it was turned off still
// has 'copilot' persisted; without this the CSS hides every tab
// body and the panel paints empty.
if (activeTab === 'copilot' && !${isChatEnabled}) {
// Chat moved out of the right inspector. Migrate the legacy
// persisted tab before first paint so the inspector opens on Blocks.
if (activeTab === 'copilot') {
activeTab = 'toolbar';
}
if (activeTab) {
Expand Down Expand Up @@ -260,7 +254,10 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=

{isHosted ? <PublicEnvScript /> : <RuntimePublicEnvScript disableNextScript />}
</head>
<body className={`${season.variable} font-season`} suppressHydrationWarning>
<body
className={`${season.variable} font-season [--scrollbar-size:4px]`}
suppressHydrationWarning
>
{/* Google Tag Manager (noscript) — hosted only */}
{isHosted && (
<noscript>
Expand Down
191 changes: 190 additions & 1 deletion apps/sim/app/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ import {
ButtonGroupItem,
Checkbox,
ChevronDown,
ChipCombobox,
ChipCopyInput,
ChipDatePicker,
ChipInput,
ChipSelect,
ChipSwitch,
ChipTag,
ChipTextarea,
ChipTimePicker,
Code,
Combobox,
Connections,
Expand Down Expand Up @@ -83,7 +91,7 @@ import {
ZoomIn,
ZoomOut,
} from '@sim/emcn'
import { ArrowLeft, Folder, Moon, Sun } from '@sim/emcn/icons'
import { ArrowLeft, Folder, Moon, Search, Sun } from '@sim/emcn/icons'
import { notFound, useRouter } from 'next/navigation'
import { env, isTruthy } from '@/lib/core/config/env'

Expand All @@ -107,6 +115,31 @@ function VariantRow({ label, children }: { label: string; children: React.ReactN
)
}

interface WorkflowFieldPreviewProps {
title: string
hint?: string
required?: boolean
children: React.ReactNode
}

function WorkflowFieldPreview({
title,
hint,
required = false,
children,
}: WorkflowFieldPreviewProps) {
return (
<div className='flex flex-col gap-[9px] rounded-xl border border-[var(--border)] bg-[var(--surface-2)] p-4'>
<Label className='text-[var(--text-muted)] text-small'>
{title}
{required ? <span className='text-[var(--text-error)]'> *</span> : null}
</Label>
{children}
{hint ? <p className='text-[var(--text-muted)] text-caption'>{hint}</p> : null}
</div>
)
}

const SAMPLE_CODE = `function greet(name) {
console.log("Hello, " + name);
return { success: true };
Expand All @@ -122,6 +155,11 @@ const COMBOBOX_OPTIONS = [
{ label: 'Option 3', value: 'opt3' },
]

const WORKFLOW_BOOLEAN_OPTIONS = [
{ value: 'off', label: 'Off' },
{ value: 'on', label: 'On' },
] as const

const DARK_MODE_EVENT = 'playground:dark-mode-change'

const subscribeToDarkMode = (onStoreChange: () => void) => {
Expand All @@ -135,6 +173,15 @@ const getServerDarkModeSnapshot = () => false
export default function PlaygroundPage() {
const router = useRouter()
const [comboboxValue, setComboboxValue] = useState('')
const [workflowTextValue, setWorkflowTextValue] = useState('claude-sonnet-5')
const [workflowDescription, setWorkflowDescription] = useState(
'Classify each support request and return the urgency, owner, and next action.'
)
const [workflowChoice, setWorkflowChoice] = useState('opt1')
const [workflowSearchChoice, setWorkflowSearchChoice] = useState('opt2')
const [workflowMultiChoices, setWorkflowMultiChoices] = useState<string[]>(['opt1', 'opt3'])
const [workflowToggle, setWorkflowToggle] = useState<'off' | 'on'>('off')
const [workflowTime, setWorkflowTime] = useState('09:30')
const [switchValue, setSwitchValue] = useState(false)
const [checkboxValue, setCheckboxValue] = useState(false)
const [sliderValue, setSliderValue] = useState([50])
Expand Down Expand Up @@ -194,6 +241,148 @@ export default function PlaygroundPage() {
</p>
</div>

<Section title='Workflow editor field lab'>
<p className='max-w-2xl text-[var(--text-secondary)] text-sm'>
The canonical workflow field language. These examples exercise shared EMCN chrome
before workflow-specific adapters add variables, references, and generated content.
</p>
<div className='grid gap-4 md:grid-cols-2'>
<WorkflowFieldPreview title='Single-line input' required>
<ChipInput
value={workflowTextValue}
onChange={(event) => setWorkflowTextValue(event.target.value)}
placeholder='Enter a value'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview
title='Validation state'
hint='Required values use the component error state instead of custom borders.'
required
>
<ChipInput error placeholder='Enter your API key' />
</WorkflowFieldPreview>
<WorkflowFieldPreview
title='Adorned input'
hint='Leading and trailing content is supplied through component props.'
>
<ChipInput
icon={Search}
placeholder='Search available data'
endAdornment={<ChipTag variant='field'>⌘K</ChipTag>}
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Multi-line input'>
<ChipTextarea
rows={4}
value={workflowDescription}
onChange={(event) => setWorkflowDescription(event.target.value)}
placeholder='Describe what this block should do'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Disabled input' hint='Preview and unavailable states.'>
<ChipInput value='Inherited from deployment settings' disabled />
</WorkflowFieldPreview>
<WorkflowFieldPreview
title='View-only values'
hint='Read-only records stay legible instead of looking unavailable.'
>
<div className='flex flex-col gap-2'>
<ChipCopyInput value='https://api.sim.ai/webhooks/example' />
<ChipTextarea
value='This content is generated by the selected deployment.'
rows={2}
viewOnly
/>
</div>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Select'>
<ChipSelect
options={COMBOBOX_OPTIONS}
value={workflowChoice}
onChange={setWorkflowChoice}
placeholder='Select an option'
align='start'
fullWidth
dropdownWidth='trigger'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview
title='Multi-select'
hint='Selected values collapse into a stable trigger summary.'
>
<ChipSelect
options={COMBOBOX_OPTIONS}
multiSelect
multiSelectValues={workflowMultiChoices}
onMultiSelectChange={setWorkflowMultiChoices}
placeholder='Select options'
searchable
align='start'
fullWidth
dropdownWidth='trigger'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Searchable select'>
<ChipCombobox
options={COMBOBOX_OPTIONS}
value={workflowSearchChoice}
onChange={setWorkflowSearchChoice}
placeholder='Search options'
searchable
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview
title='Loading select'
hint='Async state is rendered by the picker rather than its consumer.'
>
<ChipSelect
options={[]}
isLoading
placeholder='Load options'
align='start'
fullWidth
dropdownWidth='trigger'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Empty select'>
<ChipSelect
options={[]}
emptyMessage='No matching options'
placeholder='Choose a resource'
align='start'
fullWidth
dropdownWidth='trigger'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Truncated value'>
<ChipSelect
options={[
{
value: 'long',
label:
'A deliberately long resource name that demonstrates trigger truncation',
},
]}
value='long'
align='start'
fullWidth
dropdownWidth='trigger'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Boolean setting'>
<ChipSwitch
options={WORKFLOW_BOOLEAN_OPTIONS}
value={workflowToggle}
onChange={setWorkflowToggle}
aria-label='Boolean setting'
/>
</WorkflowFieldPreview>
<WorkflowFieldPreview title='Time input'>
<ChipTimePicker value={workflowTime} onChange={setWorkflowTime} fullWidth />
</WorkflowFieldPreview>
</div>
</Section>

{/* Toast */}
<Section title='Toast'>
<VariantRow label='default'>
Expand Down
Loading
Loading