From 359976c3a32532db3d3a0aac76e411c9d2ae43a1 Mon Sep 17 00:00:00 2001 From: Matt Pua Date: Thu, 23 Jul 2026 10:49:01 -0400 Subject: [PATCH 1/2] fix: prevent skipping incomplete loop steps Generated-By: PostHog Code Task-Id: 05d83215-fe0a-4441-8c70-c3063488a351 --- .../loops/components/LoopForm.test.ts | 24 +++++++++++++++++++ .../features/loops/components/LoopForm.tsx | 11 +++++---- .../loops/components/loopStepNavigation.ts | 7 ++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/features/loops/components/LoopForm.test.ts create mode 100644 packages/ui/src/features/loops/components/loopStepNavigation.ts diff --git a/packages/ui/src/features/loops/components/LoopForm.test.ts b/packages/ui/src/features/loops/components/LoopForm.test.ts new file mode 100644 index 0000000000..9710271ece --- /dev/null +++ b/packages/ui/src/features/loops/components/LoopForm.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { canNavigateToLoopStep } from "./loopStepNavigation"; + +describe("canNavigateToLoopStep", () => { + it("blocks forward navigation when the current step is incomplete", () => { + expect(canNavigateToLoopStep(0, 1, [false, true, true, false])).toBe(false); + expect(canNavigateToLoopStep(0, 3, [false, true, true, false])).toBe(false); + }); + + it("allows forward navigation through completed steps", () => { + expect(canNavigateToLoopStep(0, 1, [true, false, true, false])).toBe(true); + expect(canNavigateToLoopStep(0, 2, [true, true, true, false])).toBe(true); + }); + + it("blocks skipping an incomplete intervening step", () => { + expect(canNavigateToLoopStep(0, 3, [true, false, true, false])).toBe(false); + }); + + it("always allows backward navigation", () => { + expect(canNavigateToLoopStep(3, 0, [false, false, false, false])).toBe( + true, + ); + }); +}); diff --git a/packages/ui/src/features/loops/components/LoopForm.tsx b/packages/ui/src/features/loops/components/LoopForm.tsx index 7361bd3596..0915e2f8bb 100644 --- a/packages/ui/src/features/loops/components/LoopForm.tsx +++ b/packages/ui/src/features/loops/components/LoopForm.tsx @@ -40,6 +40,7 @@ import { LoopModelFields } from "./LoopModelFields"; import { LoopNotificationsFields } from "./LoopNotificationsFields"; import { LoopRepositoryPicker } from "./LoopRepositoryPicker"; import { LoopTriggerEditor } from "./LoopTriggerEditor"; +import { canNavigateToLoopStep } from "./loopStepNavigation"; const VISIBILITY_OPTIONS: { value: LoopSchemas.LoopVisibilityEnum; @@ -446,8 +447,7 @@ function Stepper({ {STEPS.map((label, index) => { const isCurrent = index === current; const isDone = index < current && complete[index]; - // Steps navigate freely in both directions; skipping ahead is safe - // because Next stays gated per step and Create on the whole form. + const canSelect = canNavigateToLoopStep(current, index, complete); return (