From ad9f1fa4541f2eefb0b013bac42cd626af424852 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 1 May 2026 20:03:40 +0200 Subject: [PATCH] fix: nested-restart iteration count for step-id collisions across subflow boundaries (#9003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support restart from steps inside BranchOne, ForLoop, Subflow Co-Authored-By: Claude Opus 4.7 (1M context) * fix: preserve original job kind in nested restart, support expanded subflow steps Co-Authored-By: Claude Opus 4.7 (1M context) * fix: read selected iteration from graph state for nested ForLoop restart Co-Authored-By: Claude Opus 4.7 (1M context) * feat: iteration selectors per ForLoop in restart popup, more nested restart tests Co-Authored-By: Claude Opus 4.7 (1M context) * refactor: extract useNestedRestartState composable Co-Authored-By: Claude Opus 4.7 (1M context) * test: cover deployed-subflow + FlowDependencies path in nested restart Co-Authored-By: Claude Opus 4.7 (1M context) * chore: update sqlx prepare cache Co-Authored-By: Claude Opus 4.7 (1M context) * fix: detect BranchOne/ForLoop ancestors inside expanded subflows for nested restart Co-Authored-By: Claude Opus 4.7 (1M context) * fix: hide restart button for non-restartable steps (parallel containers, untaken branches) Co-Authored-By: Claude Opus 4.7 (1M context) * fix: address review feedback on nested restart PR - preview FlowRestartButton: hide nested case (chain UUIDs aren't resolvable in preview path; users can use the run page for nested restart instead) - branchOneAncestorMatchesOriginal: be permissive when status isn't reachable (don't hide the button for BranchOnes nested deeper than top-level) - worker_flow.rs: apply nested_restart_payload swap on the is_simple ForLoop fast path too, so simple iterations don't bypass restart spawn interception - FlowStatusViewer: reset expandedSubflows cache on jobId change; drop $bindable({}) banned pattern for the new prop - API resolver: validate the leaf step exists before returning (fail-fast) - doc fix: branch_or_iteration_n is 0-based, not 1-based - selectedJobStepIsTopLevel reset on early-return in composable - comment iterationCounts collision caveat - new HTTP-level integration tests covering the API endpoint contract: happy path (top-level + nested), unknown step, out-of-range iteration, parallel-loop rejection Co-Authored-By: Claude Opus 4.7 (1M context) * revert: remove unreachable nested-restart swap on is_simple ForLoop fast path The swap is unreachable in valid flows: `is_simple_modules` requires the body to be a single `script` / `rawscript` / `flowscript` (per `FlowModule::is_simple`), none of which spawn flow-kind children. Any nested-restart chain targeting a leaf inside such an iteration is rejected by the API at leaf validation. Even if a chain reached the worker via `JobPayload::RawFlow.restarted_from`, the resulting `RestartedFlow` would fail to push (script kind isn't a flow kind). Replaced the swap with an explanatory comment so the next reader knows why the symmetry with the non-simple path was deliberately not added. Co-Authored-By: Claude Opus 4.7 (1M context) * fix: handle undefined expandedSubflows + tighten branchOne match check Co-Authored-By: Claude Opus 4.7 (1M context) * fix: iteration count for restart popup picks up wrong loop on step-id collision When a top-level ForLoop step shares an id with a loop nested inside an expanded subflow (e.g. parent has step `e` with 4 iterations and the subflow at step `h` also has step `e` with 1 iteration), the popup's iteration `` of `0..count-1` instead of a free-form - * number input — same surface as the graph's iteration tabs. + * number input — same surface as the graph's iteration tabs. Used for the + * SELECTED step's iteration picker when it is itself a top-level ForLoop. */ iterationCounts?: Record + /** + * Iteration counts for nested-path entries, keyed by the popup's field-key + * (`'top'` for the outer container, `'inner-N'` for nested ancestors). + * Populated by the composable so each entry uses the *correct* graph-state + * key (prefixed for in-subflow ancestors), avoiding collisions when the + * same step id appears at multiple nesting levels. + */ + nestedPathIterationCounts?: Record /** Called when flow is restarted. If not provided, will navigate to the new run using goto (requires SvelteKit) */ onRestart?: (stepId: string, branchOrIterationN: number, flowVersion?: number) => void /** Called when flow restart completes with the new job ID. Used for navigation in non-SvelteKit contexts */ @@ -65,6 +74,7 @@ nestedTopBranchOrIterationN = undefined, presetIterationN = undefined, iterationCounts = undefined, + nestedPathIterationCounts = undefined, onRestart, onRestartComplete }: Props = $props() @@ -303,7 +313,7 @@ {/if} {#each iterationFields as field (field.key)} - {@const count = iterationCounts?.[field.label] ?? 0} + {@const count = nestedPathIterationCounts?.[field.key] ?? 0}