Skip to content

Commit ceb2f26

Browse files
allquixoticclaude
andcommitted
feat(chat): remove long-running-task Cloud upsell banner (3.53.10)
Rips out the "This might take a while. Continue from anywhere with Cloud." banner that Roo added to TaskHeader to advertise their Cloud / Roomote feature. CRC is not using Cloud/Roomote, so the banner is dead advertising. - Remove <DismissibleUpsell> render + 2-minute setTimeout + isTaskComplete calc + useCloudUpsell hook + CloudUpsellDialog render from TaskHeader. These existed solely to drive the banner. - Delete webview-ui/src/components/common/DismissibleUpsell.{tsx,spec.tsx} -- it had no other callers once the banner is gone. - Drop DismissibleUpsell mocks and the "Empty state cloud promo suppression" + "DismissibleUpsell behavior" describe blocks from the four ChatView / TaskHeader test files that referenced them. - Strip the `cloud:upsell.longRunningTask` key from all 18 locale cloud.json files. CloudUpsellDialog + useCloudUpsell are left in place -- they still drive legitimate user-initiated flows in ShareButton / ChatView / CloudView. The autoApprovePowerUser / taskList upsell i18n keys are already orphaned (no code references) and were left alone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 98c1e57 commit ceb2f26

27 files changed

Lines changed: 5 additions & 1195 deletions

src/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "%extension.displayName%",
44
"description": "%extension.description%",
55
"publisher": "allquixotic",
6-
"version": "3.53.9",
6+
"version": "3.53.10",
77
"icon": "assets/icons/icon.png",
88
"galleryBanner": {
99
"color": "#617A91",

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { memo, useEffect, useRef, useState, useMemo } from "react"
1+
import { memo, useRef, useState, useMemo } from "react"
22
import { useTranslation } from "react-i18next"
3-
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
4-
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
5-
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
63
import { ChevronUp, ChevronDown, HardDriveDownload, HardDriveUpload, FoldVertical, ArrowLeft } from "lucide-react"
74
import prettyBytes from "pretty-bytes"
85

96
import type { ClineMessage } from "@roo-code/types"
107

118
import { getModelMaxOutputTokens } from "@roo/api"
12-
import { findLastIndex } from "@roo/array"
139

1410
import { formatLargeNumber } from "@src/utils/format"
1511
import { cn } from "@src/lib/utils"
@@ -60,37 +56,9 @@ const TaskHeader = ({
6056
todos,
6157
}: TaskHeaderProps) => {
6258
const { t } = useTranslation()
63-
const { apiConfiguration, currentTaskItem, clineMessages } = useExtensionState()
59+
const { apiConfiguration, currentTaskItem } = useExtensionState()
6460
const { id: modelId, info: model } = useSelectedModel(apiConfiguration)
6561
const [isTaskExpanded, setIsTaskExpanded] = useState(false)
66-
const [showLongRunningTaskMessage, setShowLongRunningTaskMessage] = useState(false)
67-
const { isOpen, openUpsell, closeUpsell, handleConnect } = useCloudUpsell({
68-
autoOpenOnAuth: false,
69-
})
70-
71-
// Check if the task is complete by looking at the last relevant message (skipping resume messages)
72-
const isTaskComplete =
73-
clineMessages && clineMessages.length > 0
74-
? (() => {
75-
const lastRelevantIndex = findLastIndex(
76-
clineMessages,
77-
(m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"),
78-
)
79-
return lastRelevantIndex !== -1
80-
? clineMessages[lastRelevantIndex]?.ask === "completion_result"
81-
: false
82-
})()
83-
: false
84-
85-
useEffect(() => {
86-
const timer = setTimeout(() => {
87-
if (currentTaskItem && !isTaskComplete) {
88-
setShowLongRunningTaskMessage(true)
89-
}
90-
}, 120_000) // Show upsell after 2 minutes
91-
92-
return () => clearTimeout(timer)
93-
}, [currentTaskItem, isTaskComplete])
9462

9563
const textContainerRef = useRef<HTMLDivElement>(null)
9664
const textRef = useRef<HTMLDivElement>(null)
@@ -144,15 +112,6 @@ const TaskHeader = ({
144112
</Button>
145113
</div>
146114
)}
147-
{showLongRunningTaskMessage && !isTaskComplete && (
148-
<DismissibleUpsell
149-
upsellId="longRunningTask"
150-
onClick={() => openUpsell()}
151-
dismissOnClick={false}
152-
variant="banner">
153-
{t("cloud:upsell.longRunningTask")}
154-
</DismissibleUpsell>
155-
)}
156115
<div
157116
className={cn(
158117
"px-3 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
@@ -466,7 +425,6 @@ const TaskHeader = ({
466425
{/* Todo list - always shown at bottom when todos exist */}
467426
{hasTodos && <TodoListDisplay todos={todos ?? (task as any)?.tool?.todos ?? []} />}
468427
</div>
469-
<CloudUpsellDialog open={isOpen} onOpenChange={closeUpsell} onConnect={handleConnect} />
470428
</div>
471429
)
472430
}

webview-ui/src/components/chat/__tests__/ChatView.notification-sound.spec.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,6 @@ vi.mock("../AutoApproveMenu", () => ({
6262
default: () => null,
6363
}))
6464

65-
// Mock DismissibleUpsell component
66-
vi.mock("@/components/common/DismissibleUpsell", () => ({
67-
default: function MockDismissibleUpsell({ children }: { children: React.ReactNode }) {
68-
return <div data-testid="dismissible-upsell">{children}</div>
69-
},
70-
}))
71-
7265
// Mock QueuedMessages component
7366
vi.mock("../QueuedMessages", () => ({
7467
QueuedMessages: function MockQueuedMessages({

webview-ui/src/components/chat/__tests__/ChatView.preserve-images.spec.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,6 @@ vi.mock("../AutoApproveMenu", () => ({
5353
default: () => null,
5454
}))
5555

56-
// Mock DismissibleUpsell component
57-
vi.mock("@/components/common/DismissibleUpsell", () => ({
58-
default: function MockDismissibleUpsell({ children }: { children: React.ReactNode }) {
59-
return <div data-testid="dismissible-upsell">{children}</div>
60-
},
61-
}))
62-
6356
// Mock QueuedMessages component
6457
vi.mock("../QueuedMessages", () => ({
6558
QueuedMessages: function MockQueuedMessages({
@@ -102,7 +95,6 @@ vi.mock("@src/components/welcome/RooHero", () => ({
10295
},
10396
}))
10497

105-
10698
// Mock i18n
10799
vi.mock("react-i18next", () => ({
108100
useTranslation: () => ({

webview-ui/src/components/chat/__tests__/ChatView.scroll-debug-repro.spec.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ vi.mock("@src/components/welcome/RooHero", nullDefaultModule)
8888
vi.mock("@src/components/welcome/RooTips", nullDefaultModule)
8989
vi.mock("./TaskHeader", () => ({ default: () => <div data-testid="task-header" /> }))
9090
vi.mock("./ProfileViolationWarning", nullDefaultModule)
91-
vi.mock("../common/DismissibleUpsell", nullDefaultModule)
9291

9392
vi.mock("./CheckpointWarning", () => ({ CheckpointWarning: () => null }))
9493
vi.mock("./QueuedMessages", () => ({ QueuedMessages: () => null }))

webview-ui/src/components/chat/__tests__/ChatView.spec.tsx

Lines changed: 0 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,6 @@ vi.mock("react-virtuoso", () => ({
7676
},
7777
}))
7878

79-
// Mock DismissibleUpsell component
80-
vi.mock("@/components/common/DismissibleUpsell", () => ({
81-
default: function MockDismissibleUpsell({ children }: { children: React.ReactNode }) {
82-
return <div data-testid="dismissible-upsell">{children}</div>
83-
},
84-
}))
85-
8679
// Mock QueuedMessages component
8780
vi.mock("../QueuedMessages", () => ({
8881
QueuedMessages: function MockQueuedMessages({
@@ -660,143 +653,6 @@ describe("ChatView - No Profile State", () => {
660653
})
661654
})
662655

663-
describe("ChatView - Empty state cloud promo suppression", () => {
664-
beforeEach(() => vi.clearAllMocks())
665-
666-
it("does not show DismissibleUpsell when user is authenticated to Cloud", () => {
667-
const { queryByTestId } = renderChatView()
668-
669-
// Hydrate state with user authenticated to cloud
670-
mockPostMessage({
671-
cloudIsAuthenticated: true,
672-
taskHistory: [
673-
{ id: "1", ts: Date.now() - 3000 },
674-
{ id: "2", ts: Date.now() - 2000 },
675-
{ id: "3", ts: Date.now() - 1000 },
676-
{ id: "4", ts: Date.now() },
677-
],
678-
clineMessages: [], // No active task
679-
})
680-
681-
// Should not show DismissibleUpsell when authenticated
682-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
683-
})
684-
685-
it("does not show DismissibleUpsell when user has only run 3 tasks in their history", () => {
686-
const { queryByTestId } = renderChatView()
687-
688-
// Hydrate state with user not authenticated but only 3 tasks
689-
mockPostMessage({
690-
cloudIsAuthenticated: false,
691-
taskHistory: [
692-
{ id: "1", ts: Date.now() - 2000 },
693-
{ id: "2", ts: Date.now() - 1000 },
694-
{ id: "3", ts: Date.now() },
695-
],
696-
clineMessages: [], // No active task
697-
})
698-
699-
// Should not show DismissibleUpsell with less than 4 tasks
700-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
701-
})
702-
703-
it("does not show DismissibleUpsell when user is not authenticated and has run 6 or more tasks", async () => {
704-
const { queryByTestId } = renderChatView()
705-
706-
// Hydrate state with user not authenticated and 4 tasks
707-
mockPostMessage({
708-
cloudIsAuthenticated: false,
709-
taskHistory: [
710-
{ id: "1", ts: Date.now() - 6000 },
711-
{ id: "2", ts: Date.now() - 5000 },
712-
{ id: "3", ts: Date.now() - 4000 },
713-
{ id: "4", ts: Date.now() - 3000 },
714-
{ id: "5", ts: Date.now() - 2000 },
715-
{ id: "6", ts: Date.now() - 1000 },
716-
{ id: "7", ts: Date.now() },
717-
],
718-
clineMessages: [], // No active task
719-
})
720-
721-
// Wait for component to render and confirm the task-list upsell stays suppressed
722-
await waitFor(() => {
723-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
724-
})
725-
})
726-
727-
it("does not show DismissibleUpsell when there is an active task (regardless of auth status)", async () => {
728-
const { queryByTestId } = renderChatView()
729-
730-
// Hydrate state with active task
731-
mockPostMessage({
732-
cloudIsAuthenticated: false,
733-
taskHistory: [
734-
{ id: "1", ts: Date.now() - 3000 },
735-
{ id: "2", ts: Date.now() - 2000 },
736-
{ id: "3", ts: Date.now() - 1000 },
737-
{ id: "4", ts: Date.now() },
738-
],
739-
clineMessages: [
740-
{
741-
type: "say",
742-
say: "task",
743-
ts: Date.now(),
744-
text: "Active task",
745-
},
746-
],
747-
})
748-
749-
// Wait for component to render with active task
750-
await waitFor(() => {
751-
// Should not show DismissibleUpsell during active task
752-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
753-
// Should not show RooTips either since the entire welcome screen is hidden during active tasks
754-
expect(queryByTestId("roo-tips")).not.toBeInTheDocument()
755-
// Should not show RooHero either since the entire welcome screen is hidden during active tasks
756-
expect(queryByTestId("roo-hero")).not.toBeInTheDocument()
757-
})
758-
})
759-
760-
it("shows RooTips when user is authenticated (instead of DismissibleUpsell)", () => {
761-
const { queryByTestId, getByTestId } = renderChatView()
762-
763-
// Hydrate state with user authenticated to cloud
764-
mockPostMessage({
765-
cloudIsAuthenticated: true,
766-
taskHistory: [
767-
{ id: "1", ts: Date.now() - 3000 },
768-
{ id: "2", ts: Date.now() - 2000 },
769-
{ id: "3", ts: Date.now() - 1000 },
770-
{ id: "4", ts: Date.now() },
771-
],
772-
clineMessages: [], // No active task
773-
})
774-
775-
// Should not show DismissibleUpsell but should show RooTips
776-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
777-
expect(getByTestId("roo-tips")).toBeInTheDocument()
778-
})
779-
780-
it("shows RooTips when user has fewer than 6 tasks (instead of DismissibleUpsell)", () => {
781-
const { queryByTestId, getByTestId } = renderChatView()
782-
783-
// Hydrate state with user not authenticated but fewer than 4 tasks
784-
mockPostMessage({
785-
cloudIsAuthenticated: false,
786-
taskHistory: [
787-
{ id: "1", ts: Date.now() - 2000 },
788-
{ id: "2", ts: Date.now() - 1000 },
789-
{ id: "3", ts: Date.now() },
790-
],
791-
clineMessages: [], // No active task
792-
})
793-
794-
// Should not show DismissibleUpsell but should show RooTips
795-
expect(queryByTestId("dismissible-upsell")).not.toBeInTheDocument()
796-
expect(getByTestId("roo-tips")).toBeInTheDocument()
797-
})
798-
})
799-
800656
describe("ChatView - Message Queueing Tests", () => {
801657
beforeEach(() => {
802658
vi.clearAllMocks()

0 commit comments

Comments
 (0)