Two costs that scaled with the number of sub-executions rather than with
anything visible, both of which a parent loop over a few hundred items would
have multiplied.
The progress arc no longer trickles between steps. The trickle needed a
1s interval, and because the composable was called unconditionally, every
default node on the canvas ran one whether a sub-workflow was executing or
not. The arc's own reason for existing was weak once the point was granted
that the rotating running border already shows the node is alive, so this
drops the behaviour rather than gating the timer.
The reachable-node count is now computed on first emit instead of at hook
registration, and node events return early when no parent pushRef resolves.
Executions with no editor session — the overwhelming majority — no longer
walk the child workflow's graph at all. Nodes are still tallied before that
bail-out, so an editor attaching mid-run sees a truthful count.
Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the in-body progress bar with a determinate arc on the node's own
border, plus a step count in the status-icon slot that gives way to the success
checkmark on finish. The running child node name reuses the subtitle line, so
nothing shifts as the child advances.
The arc is drawn on its own layer rather than sharing the running halo's ring:
compositing the two there made the shimmer's bright head vanish behind the solid
arc and read as a flicker. It trickles between steps, bounded to half a step, so
a slow child node doesn't read as a stalled bar, and stops short of a full turn
so only completion closes the loop.
The step count carries no denominator. Nodes reached is knowable, the total is
only estimated, so the count is reported unclamped and the estimate is used just
to scale the arc. That total now counts only nodes reachable from the
sub-workflow's entry point (plus their AI/tool sub-nodes), which stops leftover
trigger branches and disconnected islands from scaling the arc down.
Co-Authored-By: Claude <noreply@anthropic.com>
The progress stream forwarded from a sub-workflow to the parent's canvas was
effectively unthrottled. The previous guard only suppressed consecutive
`nodeExecuteBefore` events for the same node with no intervening
`nodeExecuteAfter` — a sequence the engine does not produce — so steady state
was two messages per child node execution. Each of those is a pubsub broadcast
in scaling mode and a full canvas re-map in the editor, so a looping child
workflow could emit thousands of messages for one run.
Replace it with a trailing-edge throttle: the leading edge still emits
immediately so the overlay appears at once, subsequent events inside the window
coalesce into a single pending snapshot, and a trailing timer sends the latest
state. The overlay only renders the running node name, an "X / Y" counter and a
bar, so intermediate states are disposable. A 200-iteration child now emits two
messages instead of ~400. `workflowExecuteAfter` drops any pending snapshot,
since the finished event clears the overlay outright.
Also stop forwarding progress for nested sub-workflows. Only the workflow open
on the canvas renders the overlay, so a grandchild's parent node is not on
screen and the editor discarded those messages anyway. Resolving the direct
parent's pushRef instead of walking the whole ancestor chain removes that
wasted traffic and simplifies the lookup.
Tests now exercise real engine before/after sequences rather than the synthetic
double-`nodeExecuteBefore` the old throttle keyed on.
Co-Authored-By: Claude <noreply@anthropic.com>