diff --git a/packages/ui/src/features/loops/components/LoopDetailView.tsx b/packages/ui/src/features/loops/components/LoopDetailView.tsx
index f16b8b85bf..8318a684cf 100644
--- a/packages/ui/src/features/loops/components/LoopDetailView.tsx
+++ b/packages/ui/src/features/loops/components/LoopDetailView.tsx
@@ -1,16 +1,26 @@
import { ArrowLeftIcon, RepeatIcon } from "@phosphor-icons/react";
import type { LoopSchemas } from "@posthog/api-client/loops";
-import { Switch } from "@posthog/quill";
+import {
+ AlertDialog,
+ AlertDialogClose,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ Badge,
+ Button,
+ Switch,
+ Textarea,
+} from "@posthog/quill";
import { useSetHeaderContent } from "@posthog/ui/hooks/useSetHeaderContent";
-import { Badge } from "@posthog/ui/primitives/Badge";
-import { Button } from "@posthog/ui/primitives/Button";
import { toast } from "@posthog/ui/primitives/toast";
import {
navigateToEditLoop,
navigateToLoops,
} from "@posthog/ui/router/navigationBridge";
-import { AlertDialog, Flex, Text } from "@radix-ui/themes";
-import { useState } from "react";
+import { Flex, Text } from "@radix-ui/themes";
+import { useRef, useState } from "react";
import { useLoop } from "../hooks/useLoop";
import {
useDeleteLoop,
@@ -101,24 +111,25 @@ export function LoopDetailView({ loopId }: { loopId: string }) {
-
+
{loop.name}
-
+
{loopStatusLabel(loop)}
- {loop.visibility}
+ {loop.visibility}
);
}
+function loopStatusBadgeVariant(
+ loop: LoopSchemas.Loop,
+): "default" | "destructive" | "success" {
+ const color = loopStatusColor(loop);
+ if (color === "green") return "success";
+ if (color === "red") return "destructive";
+ return "default";
+}
+
function ConfigSummarySection({ loop }: { loop: LoopSchemas.Loop }) {
return (
@@ -278,13 +298,74 @@ function ConfigSummarySection({ loop }: { loop: LoopSchemas.Loop }) {
)}
+
+
+ );
+}
-
-
- {loop.instructions}
-
-
+function InstructionsSection({ loop }: { loop: LoopSchemas.Loop }) {
+ const updateLoop = useUpdateLoop(loop.id);
+ const [draft, setDraft] = useState(null);
+ // Escape reverts and blurs; skip the resulting onBlur save.
+ const skipCommit = useRef(false);
+
+ const commit = (value: string) => {
+ if (skipCommit.current) {
+ skipCommit.current = false;
+ return;
+ }
+ const trimmed = value.trim();
+ if (!trimmed) {
+ setDraft(null);
+ return;
+ }
+ if (updateLoop.isPending) return;
+ if (trimmed === loop.instructions.trim()) {
+ setDraft(null);
+ return;
+ }
+ updateLoop.mutate(
+ { instructions: trimmed },
+ {
+ onSuccess: () => {
+ setDraft(null);
+ toast.success("Instructions updated");
+ },
+ onError: (error) => {
+ setDraft(null);
+ toast.error("Failed to update instructions", {
+ description: error.message,
+ });
+ },
+ },
+ );
+ };
+
+ return (
+
+
+
+ Instructions
+
+ {updateLoop.isPending ? (
+ Saving…
+ ) : null}
+
);
}