fix(frontend): flow progress bar for early-stop completion and error handler (WIN-1961) (#9254)

Two FlowProgressBar bugs:

1. stop_after_if (without 'label as skipped') ends the flow with
   step < modules.length, leaving the bar at <100% with a spinner.
2. failure_module execution drives step past modules.length, so the bar
   overflows past 100% and never reflects the error.

The fix clamps progress to the failed module when the error handler
runs, and forces 100% Done when the flow completed successfully but
stopped early.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-05-20 09:54:09 +00:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 76d949e7bc
commit cc141effa3
@@ -58,6 +58,22 @@
let newCurrentStepId: string | undefined = undefined
let newIsWaitingForEvents = false
// Error handler (failure_module) was triggered: backend sets step >= modules.length
// and runs the failure_module. Clamp progress to the failed module so the bar shows
// the error indicator at the correct position instead of overflowing past 100%.
if (
maxDone >= modules.length &&
job?.flow_status?.failure_module?.type !== 'WaitingForPriorSteps'
) {
const failedIdx = modules.findIndex((m) => m?.type === 'Failure')
if (failedIdx >= 0) {
newError = failedIdx
maxDone = failedIdx + 1
} else {
maxDone = modules.length
}
}
if (modules.length > maxDone) {
const nextModule = modules[maxDone]
if (nextModule.type === 'InProgress') {
@@ -110,12 +126,25 @@
subLength = subStepLength ? Math.max(subStepLength, 1) : undefined
subIndex = subStepIndex
length = Math.max(modules.length, 1)
index = maxDone
const newIsSkipped = 'is_skipped' in job && Boolean(job.is_skipped)
// Early-stop completion: stop_after_if (without 'label as skipped') ends the flow
// with step < modules.length. Without this, the bar stays at < 100% with 'Running'.
if (
'success' in job &&
job.success === true &&
!newIsSkipped &&
newError === undefined &&
maxDone < modules.length
) {
index = modules.length
} else {
index = maxDone
}
nextInProgress = newNextInProgress
currentStepId = newCurrentStepId
isWaitingForEvents = newIsWaitingForEvents
isCanceled = job?.canceled || false
isSkipped = 'is_skipped' in job && Boolean(job.is_skipped)
isSkipped = newIsSkipped
}
export function reset() {