Skip to content

Commit ceb0595

Browse files
committed
improvement(workflow): redesign editor and configuration
1 parent 04f0ffc commit ceb0595

133 files changed

Lines changed: 5525 additions & 4298 deletions

File tree

Some content is hidden

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

apps/realtime/src/database/operations.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,33 @@ async function handleBlockOperationTx(
628628
break
629629
}
630630

631+
case BLOCK_OPERATIONS.UPDATE_DESCRIPTION: {
632+
if (!payload.id || payload.description === undefined) {
633+
throw new Error('Missing required fields for update description operation')
634+
}
635+
636+
const updateResult = await tx
637+
.update(workflowBlocks)
638+
.set({
639+
data: sql`jsonb_set(
640+
coalesce(${workflowBlocks.data}, '{}'::jsonb),
641+
'{description}',
642+
${JSON.stringify(payload.description)}::jsonb,
643+
true
644+
)`,
645+
updatedAt: new Date(),
646+
})
647+
.where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId)))
648+
.returning({ id: workflowBlocks.id })
649+
650+
if (updateResult.length === 0) {
651+
throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`)
652+
}
653+
654+
logger.debug(`Updated block description: ${payload.id}`)
655+
break
656+
}
657+
631658
case BLOCK_OPERATIONS.TOGGLE_ENABLED: {
632659
if (!payload.id) {
633660
throw new Error('Missing block ID for toggle enabled operation')

apps/realtime/src/middleware/permissions.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ describe('checkRolePermission', () => {
263263
{ operation: 'update', adminAllowed: true, writeAllowed: true, readAllowed: false },
264264
{ operation: 'update-position', adminAllowed: true, writeAllowed: true, readAllowed: false },
265265
{ operation: 'update-name', adminAllowed: true, writeAllowed: true, readAllowed: false },
266+
{
267+
operation: 'update-description',
268+
adminAllowed: true,
269+
writeAllowed: true,
270+
readAllowed: false,
271+
},
266272
{ operation: 'toggle-enabled', adminAllowed: true, writeAllowed: true, readAllowed: false },
267273
{ operation: 'update-parent', adminAllowed: true, writeAllowed: true, readAllowed: false },
268274
{

apps/realtime/src/middleware/permissions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const WRITE_OPERATIONS: string[] = [
2626
// Block operations
2727
BLOCK_OPERATIONS.UPDATE_POSITION,
2828
BLOCK_OPERATIONS.UPDATE_NAME,
29+
BLOCK_OPERATIONS.UPDATE_DESCRIPTION,
2930
BLOCK_OPERATIONS.TOGGLE_ENABLED,
3031
BLOCK_OPERATIONS.UPDATE_PARENT,
3132
BLOCK_OPERATIONS.UPDATE_ADVANCED_MODE,

apps/sim/app/layout.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import { BrandedLayout } from '@/components/branded-layout'
66
import { PostHogProvider } from '@/app/_shell/providers/posthog-provider'
77
import { generateBrandedMetadata, generateThemeCSS } from '@/ee/whitelabeling'
88
import '@/app/_styles/globals.css'
9-
import {
10-
isChatEnabled,
11-
isHosted,
12-
isReactGrabEnabled,
13-
isReactScanEnabled,
14-
} from '@/lib/core/config/env-flags'
9+
import { isHosted, isReactGrabEnabled, isReactScanEnabled } from '@/lib/core/config/env-flags'
1510
import { DesktopUpdateGate } from '@/app/_shell/desktop-update-gate'
1611
import { HydrationErrorHandler } from '@/app/_shell/hydration-error-handler'
1712
import { QueryProvider } from '@/app/_shell/providers/query-provider'
@@ -155,10 +150,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
155150
}
156151
157152
var activeTab = panelState && panelState.activeTab;
158-
// A session that used the Chat tab before it was turned off still
159-
// has 'copilot' persisted; without this the CSS hides every tab
160-
// body and the panel paints empty.
161-
if (activeTab === 'copilot' && !${isChatEnabled}) {
153+
// Chat moved out of the right inspector. Migrate the legacy
154+
// persisted tab before first paint so the inspector opens on Blocks.
155+
if (activeTab === 'copilot') {
162156
activeTab = 'toolbar';
163157
}
164158
if (activeTab) {
@@ -260,7 +254,10 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
260254

261255
{isHosted ? <PublicEnvScript /> : <RuntimePublicEnvScript disableNextScript />}
262256
</head>
263-
<body className={`${season.variable} font-season`} suppressHydrationWarning>
257+
<body
258+
className={`${season.variable} font-season [--scrollbar-size:4px]`}
259+
suppressHydrationWarning
260+
>
264261
{/* Google Tag Manager (noscript) — hosted only */}
265262
{isHosted && (
266263
<noscript>

apps/sim/app/playground/page.tsx

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ import {
1313
ButtonGroupItem,
1414
Checkbox,
1515
ChevronDown,
16+
ChipCombobox,
17+
ChipCopyInput,
1618
ChipDatePicker,
19+
ChipInput,
20+
ChipSelect,
21+
ChipSwitch,
22+
ChipTag,
23+
ChipTextarea,
24+
ChipTimePicker,
1725
Code,
1826
Combobox,
1927
Connections,
@@ -83,7 +91,7 @@ import {
8391
ZoomIn,
8492
ZoomOut,
8593
} from '@sim/emcn'
86-
import { ArrowLeft, Folder, Moon, Sun } from '@sim/emcn/icons'
94+
import { ArrowLeft, Folder, Moon, Search, Sun } from '@sim/emcn/icons'
8795
import { notFound, useRouter } from 'next/navigation'
8896
import { env, isTruthy } from '@/lib/core/config/env'
8997

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

118+
interface WorkflowFieldPreviewProps {
119+
title: string
120+
hint?: string
121+
required?: boolean
122+
children: React.ReactNode
123+
}
124+
125+
function WorkflowFieldPreview({
126+
title,
127+
hint,
128+
required = false,
129+
children,
130+
}: WorkflowFieldPreviewProps) {
131+
return (
132+
<div className='flex flex-col gap-[9px] rounded-xl border border-[var(--border)] bg-[var(--surface-2)] p-4'>
133+
<Label className='text-[var(--text-muted)] text-small'>
134+
{title}
135+
{required ? <span className='text-[var(--text-error)]'> *</span> : null}
136+
</Label>
137+
{children}
138+
{hint ? <p className='text-[var(--text-muted)] text-caption'>{hint}</p> : null}
139+
</div>
140+
)
141+
}
142+
110143
const SAMPLE_CODE = `function greet(name) {
111144
console.log("Hello, " + name);
112145
return { success: true };
@@ -122,6 +155,11 @@ const COMBOBOX_OPTIONS = [
122155
{ label: 'Option 3', value: 'opt3' },
123156
]
124157

158+
const WORKFLOW_BOOLEAN_OPTIONS = [
159+
{ value: 'off', label: 'Off' },
160+
{ value: 'on', label: 'On' },
161+
] as const
162+
125163
const DARK_MODE_EVENT = 'playground:dark-mode-change'
126164

127165
const subscribeToDarkMode = (onStoreChange: () => void) => {
@@ -135,6 +173,15 @@ const getServerDarkModeSnapshot = () => false
135173
export default function PlaygroundPage() {
136174
const router = useRouter()
137175
const [comboboxValue, setComboboxValue] = useState('')
176+
const [workflowTextValue, setWorkflowTextValue] = useState('claude-sonnet-5')
177+
const [workflowDescription, setWorkflowDescription] = useState(
178+
'Classify each support request and return the urgency, owner, and next action.'
179+
)
180+
const [workflowChoice, setWorkflowChoice] = useState('opt1')
181+
const [workflowSearchChoice, setWorkflowSearchChoice] = useState('opt2')
182+
const [workflowMultiChoices, setWorkflowMultiChoices] = useState<string[]>(['opt1', 'opt3'])
183+
const [workflowToggle, setWorkflowToggle] = useState<'off' | 'on'>('off')
184+
const [workflowTime, setWorkflowTime] = useState('09:30')
138185
const [switchValue, setSwitchValue] = useState(false)
139186
const [checkboxValue, setCheckboxValue] = useState(false)
140187
const [sliderValue, setSliderValue] = useState([50])
@@ -194,6 +241,148 @@ export default function PlaygroundPage() {
194241
</p>
195242
</div>
196243

244+
<Section title='Workflow editor field lab'>
245+
<p className='max-w-2xl text-[var(--text-secondary)] text-sm'>
246+
The canonical workflow field language. These examples exercise shared EMCN chrome
247+
before workflow-specific adapters add variables, references, and generated content.
248+
</p>
249+
<div className='grid gap-4 md:grid-cols-2'>
250+
<WorkflowFieldPreview title='Single-line input' required>
251+
<ChipInput
252+
value={workflowTextValue}
253+
onChange={(event) => setWorkflowTextValue(event.target.value)}
254+
placeholder='Enter a value'
255+
/>
256+
</WorkflowFieldPreview>
257+
<WorkflowFieldPreview
258+
title='Validation state'
259+
hint='Required values use the component error state instead of custom borders.'
260+
required
261+
>
262+
<ChipInput error placeholder='Enter your API key' />
263+
</WorkflowFieldPreview>
264+
<WorkflowFieldPreview
265+
title='Adorned input'
266+
hint='Leading and trailing content is supplied through component props.'
267+
>
268+
<ChipInput
269+
icon={Search}
270+
placeholder='Search available data'
271+
endAdornment={<ChipTag variant='field'>⌘K</ChipTag>}
272+
/>
273+
</WorkflowFieldPreview>
274+
<WorkflowFieldPreview title='Multi-line input'>
275+
<ChipTextarea
276+
rows={4}
277+
value={workflowDescription}
278+
onChange={(event) => setWorkflowDescription(event.target.value)}
279+
placeholder='Describe what this block should do'
280+
/>
281+
</WorkflowFieldPreview>
282+
<WorkflowFieldPreview title='Disabled input' hint='Preview and unavailable states.'>
283+
<ChipInput value='Inherited from deployment settings' disabled />
284+
</WorkflowFieldPreview>
285+
<WorkflowFieldPreview
286+
title='View-only values'
287+
hint='Read-only records stay legible instead of looking unavailable.'
288+
>
289+
<div className='flex flex-col gap-2'>
290+
<ChipCopyInput value='https://api.sim.ai/webhooks/example' />
291+
<ChipTextarea
292+
value='This content is generated by the selected deployment.'
293+
rows={2}
294+
viewOnly
295+
/>
296+
</div>
297+
</WorkflowFieldPreview>
298+
<WorkflowFieldPreview title='Select'>
299+
<ChipSelect
300+
options={COMBOBOX_OPTIONS}
301+
value={workflowChoice}
302+
onChange={setWorkflowChoice}
303+
placeholder='Select an option'
304+
align='start'
305+
fullWidth
306+
dropdownWidth='trigger'
307+
/>
308+
</WorkflowFieldPreview>
309+
<WorkflowFieldPreview
310+
title='Multi-select'
311+
hint='Selected values collapse into a stable trigger summary.'
312+
>
313+
<ChipSelect
314+
options={COMBOBOX_OPTIONS}
315+
multiSelect
316+
multiSelectValues={workflowMultiChoices}
317+
onMultiSelectChange={setWorkflowMultiChoices}
318+
placeholder='Select options'
319+
searchable
320+
align='start'
321+
fullWidth
322+
dropdownWidth='trigger'
323+
/>
324+
</WorkflowFieldPreview>
325+
<WorkflowFieldPreview title='Searchable select'>
326+
<ChipCombobox
327+
options={COMBOBOX_OPTIONS}
328+
value={workflowSearchChoice}
329+
onChange={setWorkflowSearchChoice}
330+
placeholder='Search options'
331+
searchable
332+
/>
333+
</WorkflowFieldPreview>
334+
<WorkflowFieldPreview
335+
title='Loading select'
336+
hint='Async state is rendered by the picker rather than its consumer.'
337+
>
338+
<ChipSelect
339+
options={[]}
340+
isLoading
341+
placeholder='Load options'
342+
align='start'
343+
fullWidth
344+
dropdownWidth='trigger'
345+
/>
346+
</WorkflowFieldPreview>
347+
<WorkflowFieldPreview title='Empty select'>
348+
<ChipSelect
349+
options={[]}
350+
emptyMessage='No matching options'
351+
placeholder='Choose a resource'
352+
align='start'
353+
fullWidth
354+
dropdownWidth='trigger'
355+
/>
356+
</WorkflowFieldPreview>
357+
<WorkflowFieldPreview title='Truncated value'>
358+
<ChipSelect
359+
options={[
360+
{
361+
value: 'long',
362+
label:
363+
'A deliberately long resource name that demonstrates trigger truncation',
364+
},
365+
]}
366+
value='long'
367+
align='start'
368+
fullWidth
369+
dropdownWidth='trigger'
370+
/>
371+
</WorkflowFieldPreview>
372+
<WorkflowFieldPreview title='Boolean setting'>
373+
<ChipSwitch
374+
options={WORKFLOW_BOOLEAN_OPTIONS}
375+
value={workflowToggle}
376+
onChange={setWorkflowToggle}
377+
aria-label='Boolean setting'
378+
/>
379+
</WorkflowFieldPreview>
380+
<WorkflowFieldPreview title='Time input'>
381+
<ChipTimePicker value={workflowTime} onChange={setWorkflowTime} fullWidth />
382+
</WorkflowFieldPreview>
383+
</div>
384+
</Section>
385+
197386
{/* Toast */}
198387
<Section title='Toast'>
199388
<VariantRow label='default'>

0 commit comments

Comments
 (0)