From 5a5dfdd0fdf26be25f9e82e21bfa2ff81c8c4fdd Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Thu, 24 Sep 2026 11:41:37 -0700 Subject: [PATCH 1/2] fix(ui): keep preview values readable and restore version tint --- .../general/components/versions.tsx | 2 +- .../condition-input/condition-input.tsx | 1 + .../components/eval-input/eval-input.tsx | 1 + .../components/file-upload/file-upload.tsx | 4 +- .../grouped-checkbox-list.tsx | 2 +- .../components/long-input/long-input.tsx | 1 + .../messages-input/messages-input.tsx | 1 + .../selector-combobox/selector-combobox.tsx | 5 +- .../components/short-input/short-input.tsx | 1 + .../components/slider-input/slider-input.tsx | 5 +- .../components/starter/input-format.tsx | 1 + .../components/tool-input/tool-input.tsx | 1 + .../editor/components/sub-block/sub-block.tsx | 1 + .../preview-editor/preview-editor.tsx | 31 +------ .../preview-readonly-styles.test.tsx | 82 +++++++++++++++++++ .../preview-editor/preview-readonly-styles.ts | 24 ++++++ 16 files changed, 129 insertions(+), 34 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.test.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx index 00c7e36830a..8c2f754da9f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/versions.tsx @@ -231,7 +231,7 @@ export function Versions({ className={cn( 'flex h-[36px] cursor-pointer items-center px-4 transition-colors duration-100', isSelected - ? 'bg-[color-mix(in_srgb,var(--accent)_10%,transparent)] hover-hover:bg-[color-mix(in_srgb,var(--accent)_15%,transparent)]' + ? 'bg-[color-mix(in_srgb,hsl(var(--accent))_10%,transparent)] hover-hover:bg-[color-mix(in_srgb,hsl(var(--accent))_15%,transparent)]' : 'hover-hover:bg-[var(--surface-6)] dark:hover-hover:bg-[var(--border)]' )} onClick={() => handleRowClick(v.version)} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx index 5169f4697fd..c71ae2bc46f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx @@ -1114,6 +1114,7 @@ export function ConditionInput({ overlayRefs.current.delete(block.id) } }} + data-preview-full-opacity={isPreview || undefined} className={cn( 'pointer-events-none absolute inset-0 box-border overflow-auto whitespace-pre-wrap break-words border border-transparent bg-transparent px-3 py-2 font-sans text-sm', (isPreview || disabled) && 'opacity-50' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx index 6c659aff3dc..5fd799386d0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx @@ -197,6 +197,7 @@ export function EvalInput({ className='text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50' />
) : ( - + )}
@@ -775,7 +775,7 @@ export function FileUpload({ {isDeleting ? (
) : ( - + )}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx index d385a77ab71..b503433c4af 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx @@ -123,7 +123,7 @@ export function GroupedCheckboxList({ )} > - + Configure PII Types
(
- + )}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx index f74ffd665ae..666dad6182b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx @@ -393,6 +393,7 @@ export const ShortInput = memo(function ShortInput({ />
+
{ if (el) descriptionOverlayRefs.current[field.id] = el }} + data-preview-full-opacity={isPreview || undefined} style={{ scrollbarWidth: 'none' }} className={cn( 'pointer-events-none absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm', diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 54b7d751123..adecacf7d6a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -1634,6 +1634,7 @@ export const ToolInput = memo(function ToolInput({ onDrop={(e) => handleDrop(e, toolIndex)} >
{renderLabel( diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx index 8b027c4c74a..2ff99fef801 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx @@ -43,6 +43,7 @@ import { import { DELETED_WORKFLOW_LABEL } from '@/lib/workflows/workflow-labels' import { SubBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components' import { PreviewContextMenu } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-context-menu' +import { READONLY_PREVIEW_STYLES } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles' import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow' import { getBlock } from '@/blocks' import { BlockTile } from '@/blocks/block-tile' @@ -54,31 +55,6 @@ import { useCodeViewerFeatures } from '@/hooks/use-code-viewer' import { useContextMenu } from '@/hooks/use-context-menu' import type { BlockState, Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types' -/** - * CSS override to show full opacity and prevent interaction in readonly preview mode. - * Extracted to avoid duplicating the style block in multiple places. - */ -const READONLY_PREVIEW_STYLES = ` - .readonly-preview, - .readonly-preview * { - cursor: default !important; - } - .readonly-preview [disabled], - .readonly-preview [data-disabled], - .readonly-preview input, - .readonly-preview textarea, - .readonly-preview [role="combobox"], - .readonly-preview [role="slider"], - .readonly-preview [role="switch"], - .readonly-preview [role="checkbox"] { - opacity: 1 !important; - pointer-events: none; - } - .readonly-preview .opacity-50 { - opacity: 1 !important; - } -` - /** * Format a value for display as JSON string */ @@ -606,7 +582,7 @@ function SubflowConfigDisplay({ block, loop, parallel }: SubflowConfigDisplayPro return (
{/* Type Selection - matches SubflowEditor */} -
+
@@ -629,7 +605,7 @@ function SubflowConfigDisplay({ block, loop, parallel }: SubflowConfigDisplayPro {isCountMode ? ( -
+
{index < visibleSubBlocks.length - 1 && ( ({ useParams: () => ({ workspaceId: 'workspace-1' }) })) +vi.mock('@/hooks/use-webhook-management', () => ({ + useWebhookManagement: () => ({ webhookUrl: null }), +})) +vi.mock( + '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value', + () => ({ useSubBlockValue: () => [undefined, vi.fn()] }) +) + +import { SubBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block' +import { READONLY_PREVIEW_STYLES } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles' +import type { SubBlockConfig } from '@/blocks/types' + +const config: SubBlockConfig = { id: 'enabled', type: 'switch', title: 'Enabled' } + +let container: HTMLDivElement +let root: Root + +beforeEach(() => { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) +}) + +afterEach(() => { + act(() => root.unmount()) + container.remove() +}) + +describe('workflow preview read-only appearance', () => { + it('shows a preview value at full opacity while retaining the disabled interaction state', () => { + act(() => + root.render( + <> + +
+
+ +
+
+ +
+
+ + ) + ) + + const preview = container.querySelector('[data-testid="preview"]')! + const previewSwitch = preview.querySelector('[role="switch"]') as HTMLButtonElement + const disabled = container.querySelector('[data-testid="disabled"]')! + const disabledSwitch = disabled.querySelector('[role="switch"]') as HTMLButtonElement + + expect(preview.querySelector('[data-preview-readonly]')).not.toBeNull() + expect(previewSwitch.hasAttribute('disabled')).toBe(true) + expect(previewSwitch.getAttribute('aria-checked')).toBe('true') + expect(getComputedStyle(previewSwitch).opacity).toBe('1') + expect(getComputedStyle(previewSwitch).pointerEvents).toBe('none') + + act(() => { + previewSwitch.click() + previewSwitch.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true })) + }) + expect(previewSwitch.getAttribute('aria-checked')).toBe('true') + + expect(disabled.querySelector('[data-preview-readonly]')).toBeNull() + expect(disabledSwitch.hasAttribute('disabled')).toBe(true) + expect(getComputedStyle(disabledSwitch).opacity).toBe('0.5') + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts new file mode 100644 index 00000000000..940ad8c9dd3 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts @@ -0,0 +1,24 @@ +/** + * Keep preview fields noninteractive while showing intentionally read-only controls + * and their marked value overlays at full opacity in both preview sections. + */ +export const READONLY_PREVIEW_STYLES = ` + .readonly-preview, + .readonly-preview * { + cursor: default !important; + } + .readonly-preview [data-preview-readonly] :is( + input, + textarea, + [role="combobox"], + [role="slider"], + [role="switch"], + [role="checkbox"] + ) { + opacity: 1 !important; + pointer-events: none; + } + .readonly-preview [data-preview-readonly] [data-preview-full-opacity] { + opacity: 1 !important; + } +` From 6674af0be88772a47a4527e4ad1516d449c3dfa1 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Thu, 24 Sep 2026 12:31:12 -0700 Subject: [PATCH 2/2] fix(ui): keep preview file actions inert --- .../sub-block/components/file-upload/file-upload.tsx | 8 +++++--- .../components/selector-combobox/selector-combobox.tsx | 5 +---- .../preview-editor/preview-readonly-styles.test.tsx | 8 ++++++++ .../components/preview-editor/preview-readonly-styles.ts | 3 +++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx index cc200063fc8..ebef131255b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx @@ -215,7 +215,8 @@ function SingleFileSelector({ iconSize='compact' className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10' onClick={onClear} - disabled={isDeleting} + disabled={disabled || isDeleting} + data-preview-full-opacity={disabled || undefined} > {isDeleting ? (
@@ -770,7 +771,8 @@ export function FileUpload({ iconSize='compact' className='-translate-y-1/2 absolute top-1/2 right-[4px]' onClick={(e) => handleRemoveFile(file, e)} - disabled={isDeleting} + disabled={disabled || isPreview || isDeleting} + data-preview-full-opacity={isPreview || undefined} > {isDeleting ? (
@@ -972,7 +974,7 @@ export function FileUpload({ onOpenChange={(open) => { if (open) void refetchWorkspaceFiles() }} - disabled={disabled} + disabled={disabled || isPreview} isLoading={loadingWorkspaceFiles} formatFileSize={formatFileSize} truncateMiddle={truncateMiddle} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx index 2a9443cba8b..720a511c18c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx @@ -344,10 +344,7 @@ export function SelectorCombobox({ className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10' onClick={handleClear} > - + )}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.test.tsx index d47b55f7465..92ae2d8f16d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.test.tsx @@ -49,6 +49,11 @@ describe('workflow preview read-only appearance', () => { isPreview subBlockValues={{ enabled: { value: true } }} /> +
+ +
@@ -68,6 +73,9 @@ describe('workflow preview read-only appearance', () => { expect(previewSwitch.getAttribute('aria-checked')).toBe('true') expect(getComputedStyle(previewSwitch).opacity).toBe('1') expect(getComputedStyle(previewSwitch).pointerEvents).toBe('none') + const removeButton = preview.querySelector('button:not([role="switch"])') as HTMLButtonElement + expect(getComputedStyle(removeButton).pointerEvents).toBe('none') + expect(getComputedStyle(removeButton).opacity).toBe('0.5') act(() => { previewSwitch.click() diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts index 940ad8c9dd3..3fe8107450f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-readonly-styles.ts @@ -18,6 +18,9 @@ export const READONLY_PREVIEW_STYLES = ` opacity: 1 !important; pointer-events: none; } + .readonly-preview [data-preview-readonly] :is(button, [role="button"]) { + pointer-events: none; + } .readonly-preview [data-preview-readonly] [data-preview-full-opacity] { opacity: 1 !important; }