@@ -45,35 +45,22 @@ const ACTION_BUTTON_STYLES = [
4545] . join ( ' ' )
4646
4747/**
48- * The hatch a filled sweep slot paints: narrow uprights leaning right, so a
49- * filling run reads as one continuous `////` band.
48+ * The running hatch: narrow uprights leaning right, painted ONCE across the row.
5049 *
51- * Repeating, not one bar per slot. A single bar left the rest of its 24px button
52- * empty, so the marks inherited the button grid's rhythm and sat ~19px apart —
53- * the row read as isolated ticks rather than a loader. The pitch instead divides
54- * the row's own rhythm: slot + `gap-[2px]` is 26px, so a 13px horizontal pitch
55- * puts exactly two bars in every slot and stays in phase across the gaps,
56- * including the 40px end slots. Bars land every 13px with a uniform 6px between
57- * them, whatever the run's length.
50+ * Per-slot backgrounds cannot do this. Each button starts its own gradient at
51+ * its own origin, so the phase resets at every slot — and the end slots are 40px
52+ * against the others' 24px, so the resets are not even uniform. The row came out
53+ * as bars bunched in some places and spread in others. One element spanning the
54+ * row has one gradient, and therefore one phase.
5855 *
59- * Stops are measured along the 105° axis, not horizontally, so they carry the
60- * `sin(105°)` factor: a 7px bar on a 13px pitch is 6.76px on a 12.56px period.
61- * `bg-clip-content` with symmetric padding sets the height without touching the
62- * slot's size, so the swell measured around it does not move. `--surface-2` is
63- * untouched.
64- *
65- * Each variant is spelled out: Tailwind's JIT reads literal class strings.
56+ * `--surface-2` is the same fill the slots used; only where it is painted moved.
6657 */
67- const RUNNING_SWEEP_FILL = [
68- '!bg-[repeating-linear-gradient(105deg,var(--surface-2)_0_6.76px,transparent_6.76px_12.56px)]' ,
69- 'hover-hover:!bg-[repeating-linear-gradient(105deg,var(--surface-2)_0_6.76px,transparent_6.76px_12.56px)]' ,
70- '!bg-clip-content !py-[4px] !rounded-none' ,
71- ] . join ( ' ' )
58+ const RUNNING_HATCH =
59+ 'bg-[repeating-linear-gradient(105deg,var(--surface-2)_0_5.8px,transparent_5.8px_11.6px)]'
7260
73- const RUNNING_SWEEP_FILL_STATIC = [
74- 'motion-reduce:!bg-[repeating-linear-gradient(105deg,var(--surface-2)_0_6.76px,transparent_6.76px_12.56px)]' ,
75- 'motion-reduce:!bg-clip-content motion-reduce:!py-[4px] motion-reduce:!rounded-none' ,
76- ] . join ( ' ' )
61+ /** Left edge of the hatch: clears the run/stop button, which stays live mid-run. */
62+ const RUNNING_HATCH_INSET_SWELL = 'left-[42px]'
63+ const RUNNING_HATCH_INSET_PLAIN = 'left-[26px]'
7764
7865const ICON_SIZE = 'size-[14px]'
7966
@@ -291,7 +278,6 @@ export const ActionBar = memo(
291278 const getActionButtonStyles = ( actionId : ActionId ) => {
292279 const runningSweepIndex = runningSweepActionIds . indexOf ( actionId )
293280 const isRunningSweepSlot = isSweeping && runningSweepIndex >= 0
294- const isRunningSweepFilled = isRunningSweepSlot && runningSweepIndex < runningSweepFilledCount
295281
296282 return cn (
297283 actionButtonStyles ,
@@ -328,11 +314,11 @@ export const ActionBar = memo(
328314 ]
329315 : 'hover-hover:!bg-transparent dark:hover-hover:!bg-transparent' ,
330316 ] ,
317+ /* The hatch is painted across the row, not per slot — a slot only clears
318+ itself out of its way. */
331319 isRunningSweepSlot && [
332320 '!opacity-100 [&_svg]:!opacity-0' ,
333- isRunningSweepFilled ? RUNNING_SWEEP_FILL : '!bg-transparent hover-hover:!bg-transparent' ,
334- RUNNING_SWEEP_FILL_STATIC ,
335- 'motion-reduce:transition-none' ,
321+ '!bg-transparent hover-hover:!bg-transparent' ,
336322 ] ,
337323 /* `!` is required: these buttons are also `disabled` when locked, and
338324 the emcn Button base carries `disabled:opacity-70`, which outranks a
@@ -379,13 +365,38 @@ export const ActionBar = memo(
379365 >
380366 < div
381367 className = { cn (
382- 'flex flex-row items-center gap-[2px]' ,
368+ 'relative flex flex-row items-center gap-[2px]' ,
383369 isSwell && [
384370 'pointer-events-none h-full opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]' ,
385371 'group-data-[action-menu-ready]:pointer-events-auto group-data-[action-menu-ready]:opacity-100 group-data-[action-menu-ready]:duration-100' ,
386372 ]
387373 ) }
388374 >
375+ { /*
376+ Painted behind the row rather than into each slot, so the hatch has a
377+ single gradient and a single phase. The run/stop button keeps an
378+ opaque fill while running, which masks the hatch growing underneath
379+ it; every other slot is transparent mid-sweep, so it shows through.
380+ */ }
381+ { isSweeping && (
382+ < span
383+ aria-hidden = 'true'
384+ className = { cn (
385+ 'pointer-events-none absolute inset-y-[4px] right-0 overflow-hidden' ,
386+ isSwell ? RUNNING_HATCH_INSET_SWELL : RUNNING_HATCH_INSET_PLAIN
387+ ) }
388+ >
389+ < span
390+ className = { cn (
391+ 'block h-full transition-[width] duration-150 ease-linear motion-reduce:transition-none' ,
392+ RUNNING_HATCH
393+ ) }
394+ style = { {
395+ width : `${ ( runningSweepFilledCount / runningSweepActionIds . length ) * 100 } %` ,
396+ } }
397+ />
398+ </ span >
399+ ) }
389400 { ! isNoteBlock && ( ! isInsideSubflow || isWorkflowRunning ) && (
390401 < Tooltip . Root preferAbove >
391402 < Tooltip . Trigger asChild >
0 commit comments