From fd8ed37f8fdf8f1b3e1f2ed31dc23760900b103d Mon Sep 17 00:00:00 2001
From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com>
Date: Thu, 11 Jun 2026 21:01:32 +0000
Subject: [PATCH] fix(sessions): add cancel control to cloud initializing
screen
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A provisioning cloud task renders CloudInitializingView as a full-screen
spinner with no interactive controls. If the SSE stream never delivers a
status transition, the task is stuck on "Getting things ready…" with no way
to stop, cancel, or resume.
Surface a Cancel button during the initializing render path, wired to the
existing onCancelPrompt callback (cancelPrompt -> cancelCloudPrompt), which
already sends a `cancel` command over tRPC for null/queued/in_progress runs
and reuses the TASK_RUN_CANCELLED analytics.
Generated-By: PostHog Code
Task-Id: 677a6fa9-0f43-4d72-aa8a-12dd3a008619
---
.../components/CloudInitializingView.test.tsx | 72 +++++++++++++++++++
.../components/CloudInitializingView.tsx | 23 +++++-
.../sessions/components/SessionView.tsx | 5 +-
3 files changed, 98 insertions(+), 2 deletions(-)
create mode 100644 packages/ui/src/features/sessions/components/CloudInitializingView.test.tsx
diff --git a/packages/ui/src/features/sessions/components/CloudInitializingView.test.tsx b/packages/ui/src/features/sessions/components/CloudInitializingView.test.tsx
new file mode 100644
index 000000000..6fc383aab
--- /dev/null
+++ b/packages/ui/src/features/sessions/components/CloudInitializingView.test.tsx
@@ -0,0 +1,72 @@
+import { Theme } from "@radix-ui/themes";
+import { act, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { CloudInitializingView } from "./CloudInitializingView";
+
+// The view hides everything behind a 2s reveal delay; fast-forward past it so
+// the heading and controls are mounted.
+function reveal() {
+ act(() => {
+ vi.advanceTimersByTime(2000);
+ });
+}
+
+describe("CloudInitializingView", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("renders a cancel control while provisioning when onCancel is provided", () => {
+ render(
+
+ {}} />
+ ,
+ );
+ reveal();
+ expect(screen.getByText("Getting things ready…")).toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: "Cancel" }),
+ ).toBeInTheDocument();
+ });
+
+ it("omits the cancel control when no handler is provided", () => {
+ render(
+
+
+ ,
+ );
+ reveal();
+ expect(
+ screen.queryByRole("button", { name: "Cancel" }),
+ ).not.toBeInTheDocument();
+ });
+
+ it("invokes onCancel once and shows a pending label on click", () => {
+ const onCancel = vi.fn();
+ render(
+
+
+ ,
+ );
+ reveal();
+
+ const button = screen.getByRole("button", { name: "Cancel" });
+ act(() => {
+ button.click();
+ });
+
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ const pending = screen.getByRole("button", { name: "Cancelling…" });
+ expect(pending).toBeDisabled();
+
+ // A second click is a no-op while the cancel is in flight.
+ act(() => {
+ pending.click();
+ });
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/ui/src/features/sessions/components/CloudInitializingView.tsx b/packages/ui/src/features/sessions/components/CloudInitializingView.tsx
index b444721d5..82012f085 100644
--- a/packages/ui/src/features/sessions/components/CloudInitializingView.tsx
+++ b/packages/ui/src/features/sessions/components/CloudInitializingView.tsx
@@ -1,11 +1,13 @@
import { Spinner } from "@phosphor-icons/react";
import type { TaskRunStatus } from "@posthog/shared/domain-types";
-import { Flex, Text } from "@radix-ui/themes";
+import { Button, Flex, Text } from "@radix-ui/themes";
import { useEffect, useState } from "react";
import zenHedgehog from "../../../assets/images/zen.png";
interface CloudInitializingViewProps {
cloudStatus: TaskRunStatus | null;
+ /** Cancels the provisioning cloud run. When omitted, no cancel control is shown. */
+ onCancel?: () => void;
}
const REVEAL_DELAY_MS = 2000;
@@ -35,15 +37,23 @@ function copyFor(cloudStatus: TaskRunStatus | null): {
export function CloudInitializingView({
cloudStatus,
+ onCancel,
}: CloudInitializingViewProps) {
const { heading, subtitle } = copyFor(cloudStatus);
const [revealed, setRevealed] = useState(false);
+ const [cancelling, setCancelling] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setRevealed(true), REVEAL_DELAY_MS);
return () => clearTimeout(timer);
}, []);
+ const handleCancel = () => {
+ if (cancelling) return;
+ setCancelling(true);
+ onCancel?.();
+ };
+
if (!revealed) {
return (
+ {onCancel && (
+
+ )}
);
}
diff --git a/packages/ui/src/features/sessions/components/SessionView.tsx b/packages/ui/src/features/sessions/components/SessionView.tsx
index fccb3f179..0b871c70c 100644
--- a/packages/ui/src/features/sessions/components/SessionView.tsx
+++ b/packages/ui/src/features/sessions/components/SessionView.tsx
@@ -458,7 +458,10 @@ export function SessionView({
>
) : isInitializing ? (
isCloud ? (
-
+
) : pendingTaskPrompt?.promptText ? (