Skip to content

Commit d4b3f87

Browse files
Vikhyath Mondreticlaude
andcommitted
improvement(canvas): retract bystander cards during a run, slant the sweep mark
Two things about a running canvas. A run pinned every card's action bar open and suspended every card's hover on top of it, so the canvas became a wall of open swells that could neither retract nor respond to a pointer — and the `isWorkflowRunning && !isRunning` hover treatment already written for those cards was unreachable. Only the card that is actually running is pinned now; the rest behave as they do at rest, which is what makes hovering one bring its bar up again. The sweep's filled slot painted a full 24px square. It now paints a slanted band across the slot, as a hard-stop gradient rather than a `clip-path` — the two end slots already carry one for the swell silhouette and a second would have to win a specificity race with it. The stops hold `--surface-2` exactly, so only the shape changes. Each variant is spelled out because Tailwind's JIT reads literal class strings and a composed `hover-hover:${FILL}` compiles to nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent f57b126 commit d4b3f87

4 files changed

Lines changed: 66 additions & 9 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ const ACTION_BUTTON_STYLES = [
4444
'transition-[background-color,color,opacity,transform] duration-150 active:scale-[0.96]',
4545
].join(' ')
4646

47+
/**
48+
* The mark a filled sweep slot paints: a slanted band across the slot rather
49+
* than the slot itself.
50+
*
51+
* Drawn as a hard-stop gradient rather than a `clip-path` because the two end
52+
* slots already carry one for the swell silhouette, and a second would have to
53+
* win a specificity race against it. The stops keep `--surface-2` exactly — only
54+
* the shape changes.
55+
*
56+
* Every variant is spelled out: Tailwind's JIT reads literal class strings, so a
57+
* `hover-hover:${FILL}` built at runtime compiles to no CSS at all. The hover
58+
* and motion-reduce entries also clear the base `hover-hover:bg-*` COLOR, which
59+
* a background-image cannot override and which would otherwise fill the slot
60+
* back in behind the band.
61+
*/
62+
const RUNNING_SWEEP_FILL = [
63+
'!bg-[linear-gradient(107deg,transparent_26%,var(--surface-2)_26%,var(--surface-2)_74%,transparent_74%)]',
64+
'hover-hover:!bg-[linear-gradient(107deg,transparent_26%,var(--surface-2)_26%,var(--surface-2)_74%,transparent_74%)]',
65+
].join(' ')
66+
67+
const RUNNING_SWEEP_FILL_STATIC =
68+
'motion-reduce:!bg-[linear-gradient(107deg,transparent_26%,var(--surface-2)_26%,var(--surface-2)_74%,transparent_74%)]'
69+
4770
const ICON_SIZE = 'size-[14px]'
4871

4972
type ActionId = 'run' | 'enabled' | 'lock' | 'duplicate' | 'remove' | 'delete' | 'color'
@@ -299,10 +322,9 @@ export const ActionBar = memo(
299322
],
300323
isRunningSweepSlot && [
301324
'!opacity-100 [&_svg]:!opacity-0',
302-
isRunningSweepFilled
303-
? '!bg-[var(--surface-2)] hover-hover:!bg-[var(--surface-2)]'
304-
: '!bg-transparent hover-hover:!bg-transparent',
305-
'motion-reduce:!bg-[var(--surface-2)] motion-reduce:transition-none',
325+
isRunningSweepFilled ? RUNNING_SWEEP_FILL : '!bg-transparent hover-hover:!bg-transparent',
326+
RUNNING_SWEEP_FILL_STATIC,
327+
'motion-reduce:transition-none',
306328
],
307329
/* `!` is required: these buttons are also `disabled` when locked, and
308330
the emcn Button base carries `disabled:opacity-70`, which outranks a

‎packages/workflow-renderer/src/subflow/subflow-node-view.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,15 @@ export function SubflowNodeView({
447447
onBlurCapture: handleActionMenuBlur,
448448
} = useActionMenuSwell({
449449
enabled: showActionMenu,
450-
forceOpen: isExecutionHighlighted || isRunning || isWorkflowRunning,
451-
suspendInteraction: isRunning || isWorkflowRunning,
450+
/*
451+
* A run pins the executing card's bar open — not every card's. Pinning them
452+
* all turned the canvas into a wall of open swells, and suspending their
453+
* hover on top of it made the "hover a non-running card" treatment below
454+
* unreachable, so those cards could neither retract nor respond. The block
455+
* that is actually running keeps both.
456+
*/
457+
forceOpen: isExecutionHighlighted || isRunning,
458+
suspendInteraction: isRunning,
452459
suppressNestedNodeHover: true,
453460
})
454461

‎packages/workflow-renderer/src/workflow-block/workflow-block-view-interaction.test.tsx‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ function createView(
2525
isRunning: boolean,
2626
isEnabled = true,
2727
isLocked = false,
28-
isExecutionHighlighted = false
28+
isExecutionHighlighted = false,
29+
isWorkflowRunning = false
2930
) {
3031
return (
3132
<ReactFlowProvider>
@@ -38,6 +39,7 @@ function createView(
3839
hasRing={false}
3940
ringStyles=''
4041
isRunning={isRunning}
42+
isWorkflowRunning={isWorkflowRunning}
4143
isExecutionHighlighted={isExecutionHighlighted}
4244
Icon={TestIcon}
4345
iconBgColor='var(--surface-2)'
@@ -116,6 +118,25 @@ describe('WorkflowBlockView action menu', () => {
116118
expect(actionMenuRoot).toHaveAttribute('data-action-menu-ready')
117119
})
118120

121+
/**
122+
* A run pins the executing card's bar open, not every card's. Pinning them all
123+
* turned the canvas into a wall of open swells, and suspending their hover on
124+
* top of it left them unable to retract or respond.
125+
*/
126+
it('leaves a bystander card retracted while another block runs', () => {
127+
const host = document.createElement('div')
128+
document.body.appendChild(host)
129+
const root = createRoot(host)
130+
mountedRoots.add(root)
131+
mountedHosts.add(host)
132+
133+
act(() => root.render(createView(false, true, false, false, true)))
134+
flushAnimationFrames()
135+
136+
const actionMenuRoot = host.querySelector<HTMLElement>('.group.relative')
137+
expect(actionMenuRoot).not.toHaveAttribute('data-action-menu-ready')
138+
})
139+
119140
it('uses the selected graphite treatment for a block in the active handoff', () => {
120141
const host = document.createElement('div')
121142
document.body.appendChild(host)

‎packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,9 +651,16 @@ export function WorkflowBlockView({
651651
onBlurCapture: handleActionMenuBlur,
652652
} = useActionMenuSwell({
653653
enabled: showActionMenu,
654-
forceOpen: Boolean(usesSelectedVisuals || isRunning || isWorkflowRunning),
654+
/*
655+
* A run pins the executing card's bar open — not every card's. Pinning them
656+
* all turned the canvas into a wall of open swells, and suspending their
657+
* hover on top of it made the "hover a non-running card" treatment below
658+
* unreachable, so those cards could neither retract nor respond. The block
659+
* that is actually running keeps both.
660+
*/
661+
forceOpen: Boolean(usesSelectedVisuals || isRunning),
655662
maxWidth: ACTION_MENU_MAX_WIDTH_PX,
656-
suspendInteraction: isRunning || isWorkflowRunning,
663+
suspendInteraction: isRunning,
657664
})
658665
/* Blocks that can emit an error always carry the row; `response` terminates
659666
the flow and has no error branch. */

0 commit comments

Comments
 (0)