Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions packages/ui/src/features/loops/components/LoopBehaviorFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { LoopSchemas } from "@posthog/api-client/loops";
import { Switch } from "@posthog/quill";
import { Flex, Text } from "@radix-ui/themes";
import { isAutoFixEnabled, withAutoFix } from "../loopFormTypes";

interface LoopBehaviorFieldsProps {
behaviors: LoopSchemas.LoopBehaviors;
onChange: (behaviors: LoopSchemas.LoopBehaviors) => void;
disabled?: boolean;
}

export function LoopBehaviorFields({
behaviors,
onChange,
disabled,
}: LoopBehaviorFieldsProps) {
return (
<Flex
direction="column"
gap="2"
className="rounded-(--radius-2) border border-border bg-(--color-panel-solid) p-3"
>
<Flex align="center" justify="between" gap="2">
<Flex direction="column" gap="0">
<Text className="font-medium text-[13px] text-gray-12">
Auto-fix pull requests
</Text>
<Text className="text-[12px] text-gray-10">
Watch CI and review comments on PRs this loop opens, and let Claude
push fixes.
</Text>
</Flex>
<Switch
checked={isAutoFixEnabled(behaviors)}
disabled={disabled}
aria-label="Auto-fix pull requests"
onCheckedChange={(checked) =>
onChange(withAutoFix(behaviors, checked))
}
/>
</Flex>
</Flex>
);
}
24 changes: 19 additions & 5 deletions packages/ui/src/features/loops/components/LoopForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { useLoopDraftStore } from "../loopDraftStore";
import {
emptyLoopFormValues,
formValuesToLoopWrite,
isAutoFixEnabled,
isLoopFormValid,
isTriggerDraftValid,
type LoopFormValues,
loopToFormValues,
} from "../loopFormTypes";
import { LoopBehaviorFields } from "./LoopBehaviorFields";
import { Field } from "./LoopFormPrimitives";
import { LoopModelFields } from "./LoopModelFields";
import { LoopNotificationsFields } from "./LoopNotificationsFields";
Expand Down Expand Up @@ -230,6 +232,16 @@ export function LoopForm({ loop }: LoopFormProps) {

<Divider />

<Field label="Behavior">
<LoopBehaviorFields
behaviors={values.behaviors}
disabled={isSubmitting}
onChange={(behaviors) => patch({ behaviors })}
/>
</Field>

<Divider />

<Field label="Notifications">
<LoopNotificationsFields
notifications={values.notifications}
Expand Down Expand Up @@ -358,9 +370,8 @@ function Stepper({
{STEPS.map((label, index) => {
const isCurrent = index === current;
const isDone = index < current && complete[index];
// Free navigation back to any earlier step; forward only into the step
// immediately after a completed one, so you can't skip required fields.
const reachable = index <= current || complete[index - 1];
// Steps navigate freely in both directions; skipping ahead is safe
// because Next stays gated per step and Create on the whole form.
return (
<Flex
key={label}
Expand All @@ -369,9 +380,8 @@ function Stepper({
>
<button
type="button"
disabled={!reachable}
onClick={() => onSelect(index)}
className="flex min-w-0 items-center gap-2 disabled:cursor-not-allowed"
className="flex min-w-0 cursor-pointer items-center gap-2"
>
<Flex
align="center"
Expand Down Expand Up @@ -473,6 +483,10 @@ function ReviewList({ values }: { values: LoopFormValues }) {
: values.triggers.map(describeTrigger).join(", ")
}
/>
<ReviewRow
label="Auto-fix PRs"
value={isAutoFixEnabled(values.behaviors) ? "On" : "Off"}
/>
<ReviewRow
label="Notifications"
value={channels.length === 0 ? "None" : channels.join(", ")}
Expand Down
28 changes: 28 additions & 0 deletions packages/ui/src/features/loops/loopFormTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface LoopFormValues {
*/
repositories: LoopSchemas.LoopRepositoryEntry[];
triggers: LoopTriggerDraft[];
behaviors: LoopSchemas.LoopBehaviors;
notifications: LoopSchemas.LoopNotifications;
}

Expand All @@ -51,6 +52,30 @@ export function defaultLoopNotifications(): LoopSchemas.LoopNotifications {
return { push: { ...off }, email: { ...off }, slack: { ...off } };
}

export function defaultLoopBehaviors(): LoopSchemas.LoopBehaviors {
return {
create_prs: true,
watch_ci: false,
fix_review_comments: false,
max_fix_iterations: 3,
};
}

/** The single "Auto-fix pull requests" toggle drives both CI-watching and
* review-comment fixing; it reads as on only when both are on. */
export function isAutoFixEnabled(
behaviors: LoopSchemas.LoopBehaviors,
): boolean {
return behaviors.watch_ci && behaviors.fix_review_comments;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Mixed Behaviors Display As Off

When an existing loop has only watch_ci or fix_review_comments enabled, this predicate displays Auto-fix as Off even though one behavior remains active. Clicking the switch then enables both fields instead of disabling the active behavior, so the control and Review step misrepresent the persisted state.

Rule Used: When implementing new features, ensure that the UI... (source)

Learned From
PostHog/posthog#32595
PostHog/posthog#32677

}

export function withAutoFix(
behaviors: LoopSchemas.LoopBehaviors,
enabled: boolean,
): LoopSchemas.LoopBehaviors {
return { ...behaviors, watch_ci: enabled, fix_review_comments: enabled };
}

let draftKeySeq = 0;

export function nextDraftTriggerKey(): string {
Expand All @@ -69,6 +94,7 @@ export function emptyLoopFormValues(): LoopFormValues {
reasoningEffort: null,
repositories: [],
triggers: [],
behaviors: defaultLoopBehaviors(),
notifications: defaultLoopNotifications(),
};
}
Expand All @@ -90,6 +116,7 @@ export function loopToFormValues(loop: LoopSchemas.Loop): LoopFormValues {
enabled: trigger.enabled,
config: trigger.config,
})),
behaviors: loop.behaviors,
notifications: loop.notifications,
};
}
Expand All @@ -112,6 +139,7 @@ export function formValuesToLoopWrite(
enabled: trigger.enabled,
config: trigger.config,
})),
behaviors: values.behaviors,
notifications: values.notifications,
};
}
Expand Down
Loading