Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/core/src/workflow/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const workflowAction = z
prompt: z.string().min(1).max(8_000),
adapter: z.enum(["claude", "codex"]).optional(),
model: z.string().min(1).optional(),
auto: z.boolean().optional(),
})
.strict();
export type WorkflowAction = z.infer<typeof workflowAction>;
Expand Down Expand Up @@ -90,6 +91,7 @@ export const validationDiagnostic = z
"duplicate_action_id",
"action_empty_prompt",
"action_empty_label",
"action_auto_not_bool",
]),
message: z.string(),
situationId: situationId.optional(),
Expand Down
26 changes: 25 additions & 1 deletion packages/ui/src/features/home/config/ActionEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import { useWorkflowEditorStore } from "@posthog/ui/features/home/stores/workflo
import { UnifiedModelSelector } from "@posthog/ui/features/sessions/components/UnifiedModelSelector";
import { useSettingsStore } from "@posthog/ui/features/settings/settingsStore";
import { usePreviewConfig } from "@posthog/ui/features/task-detail/hooks/usePreviewConfig";
import { Card, Flex, Text, TextArea, TextField } from "@radix-ui/themes";
import {
Card,
Flex,
Switch,
Text,
TextArea,
TextField,
} from "@radix-ui/themes";
import { useMemo } from "react";
import { SITUATION_TONE } from "./workflowMapLayout";

Expand Down Expand Up @@ -148,6 +155,23 @@ export function ActionEditorPanel({
</Text>
)}
</Field>

<Field label="Auto-run">
<Switch
className="self-start"
size="1"
checked={action.auto ?? false}
onCheckedChange={(checked) =>
patch({ auto: checked || undefined })
}
aria-label="Auto-run this action"
/>
<Text className="mt-1 text-[10px] text-gray-10">
{action.auto
? "A cloud task starts on its own — skipped while the workstream already has a running task."
: "Run automatically when a workstream enters this state."}
</Text>
</Field>
</Card>

<Flex justify="between" align="center" className="mt-3">
Expand Down
12 changes: 10 additions & 2 deletions packages/ui/src/features/home/config/SituationStation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Plus, Sparkle } from "@phosphor-icons/react";
import { Lightning, Plus, Sparkle } from "@phosphor-icons/react";
import {
SITUATIONS,
type SituationId,
Expand Down Expand Up @@ -111,7 +111,15 @@ export function SituationStation({ id, bindings }: Props) {
: action.label
}
>
<Sparkle size={9} />
{action.auto ? (
<Lightning
size={9}
weight="fill"
className="text-(--amber-9)"
/>
) : (
<Sparkle size={9} />
)}
<span className="truncate">{action.label || "(no label)"}</span>
</button>
);
Expand Down
22 changes: 22 additions & 0 deletions packages/ui/src/features/home/stores/workflowEditorStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ describe("workflowEditorStore", () => {
expect(store().draft?.bindings.working[0].label).toBe("Review");
expect(store().dirty).toBe(false);
});

it("toggles the auto flag and marks dirty", () => {
store().beginEdit(makeConfig({ ci_failing: [makeAction({ id: "a" })] }));
store().updateAction("ci_failing", "a", { auto: true });
expect(store().draft?.bindings.ci_failing[0].auto).toBe(true);
expect(store().dirty).toBe(true);
store().updateAction("ci_failing", "a", { auto: false });
expect(store().draft?.bindings.ci_failing[0].auto).toBe(false);
});
Comment on lines +121 to +128

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.

P2 The test verifies that toggling auto to false leaves dirty as true, but it never checks whether the dirty flag clears after a round-trip (enable → disable back to the original state). A test that calls updateAction with { auto: true } then { auto: false } and asserts dirty === false would have caught the serialisation mismatch noted in ActionEditorPanel.tsx.


it("clears dirty when auto is toggled on then back off", () => {
// The editor sends `checked || undefined`, so disabling clears the key
// rather than writing `auto: false`. That restores the exact baseline
// serialization (no `auto` key), so the dirty flag must return to false —
// otherwise an enable→disable round-trip falsely prompts an unsaved-changes save.
store().beginEdit(makeConfig({ ci_failing: [makeAction({ id: "a" })] }));
store().updateAction("ci_failing", "a", { auto: true });
expect(store().dirty).toBe(true);
store().updateAction("ci_failing", "a", { auto: undefined });
expect(store().draft?.bindings.ci_failing[0].auto).toBeUndefined();
expect(store().dirty).toBe(false);
});
});

describe("removeAction", () => {
Expand Down