Skip to content
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
19 changes: 16 additions & 3 deletions apps/web-roo-code/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function Privacy() {
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
Roo Code Cloud Privacy Policy
</h1>
<p className="text-muted-foreground">Last Updated: August 20, 2025</p>
<p className="text-muted-foreground">Last Updated: September 19, 2025</p>

<p className="lead">
This Privacy Policy explains how Roo Code, Inc. (&quot;Roo Code,&quot; &quot;we,&quot;
Expand Down Expand Up @@ -86,8 +86,8 @@ export default function Privacy() {
Your source code does not transit Roo Code servers unless you explicitly choose Roo Code
as a model provider (proxy mode).
</strong>{" "}
When Roo Code Cloud is your model provider, your code briefly transits Roo Code servers only to
forward it to the upstream model, is not stored, and is deleted immediately after
When Roo Code Cloud is your model provider, your code briefly transits Roo Code servers only
to forward it to the upstream model, is not stored, and is deleted immediately after
forwarding. Otherwise, your code is sent <strong>directly</strong>—via client‑to‑provider
TLS—to the model you select. Roo Code never stores, inspects, or trains on your code.
</li>
Expand Down Expand Up @@ -184,6 +184,13 @@ export default function Privacy() {
<li>
<strong>Send product updates and roadmap communications</strong> (opt‑out available)
</li>
<li>
Copy link
Contributor

Choose a reason for hiding this comment

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

The privacy policy now includes a section on marketing communications. Has this been reviewed by legal to ensure compliance with applicable data protection regulations (GDPR, CCPA, etc.)?

<strong>Send onboarding, educational, and promotional communications</strong>. We may use
your account information (such as your name and email address) to send you onboarding
messages, product tutorials, feature announcements, newsletters, and other marketing
communications. You can opt out of non‑transactional emails at any time (see “Your Choices”
below).
</li>
</ul>

<h2 className="mt-12 text-2xl font-bold">3. Where Your Data Goes (And Doesn&apos;t)</h2>
Expand Down Expand Up @@ -277,6 +284,12 @@ export default function Privacy() {
<strong>Delete your Cloud account</strong> at any time from{" "}
<strong>Security Settings</strong> inside Roo Code Cloud.
</li>
<li>
<strong>Marketing communications:</strong> You can unsubscribe from marketing and
promotional emails by clicking the unsubscribe link in those emails. Transactional or
service‑related emails (such as password resets, billing notices, or security alerts) will
continue even if you opt out.
</li>
</ul>

<h2 className="mt-12 text-2xl font-bold">6. Security Practices</h2>
Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3035,5 +3035,10 @@ export const webviewMessageHandler = async (
})
break
}
case "openSettings": {
// Jump the user to the Providers settings panel when they click the CODE SUPERNOVA upsell link
await vscode.commands.executeCommand(getCommand("settingsButtonClicked"))
break
}
}
}
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export interface WebviewMessage {
| "editQueuedMessage"
| "dismissUpsell"
| "getDismissedUpsells"
| "openSettings"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
Expand Down
36 changes: 21 additions & 15 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import { CheckpointWarning } from "./CheckpointWarning"
import { QueuedMessages } from "./QueuedMessages"
import DismissibleUpsell from "../common/DismissibleUpsell"
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
import { Cloud } from "lucide-react"

export interface ChatViewProps {
isHidden: boolean
Expand Down Expand Up @@ -131,7 +130,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
messagesRef.current = messages
}, [messages])

const { tasks } = useTaskSearch()
const { tasks, recentTasks } = useTaskSearch()
const hasTaskHistory = recentTasks.length > 0

// Initialize expanded state based on the persisted setting (default to expanded if undefined)
const [isExpanded, setIsExpanded] = useState(
Expand Down Expand Up @@ -212,7 +212,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro

const {
isOpen: isUpsellOpen,
openUpsell,
openUpsell: _openUpsell,
closeUpsell,
handleConnect,
} = useCloudUpsell({
Expand Down Expand Up @@ -1819,10 +1819,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
) : (
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-4 relative">
{/* Moved Task Bar Header Here */}
{tasks.length !== 0 && (
{hasTaskHistory && (
<div className="flex text-vscode-descriptionForeground w-full mx-auto px-5 pt-3">
<div className="flex items-center gap-1 cursor-pointer" onClick={toggleExpanded}>
{tasks.length < 10 && (
{recentTasks.length < 10 && (
<span className={`font-medium text-xs `}>{t("history:recentTasks")}</span>
)}
<span
Expand All @@ -1847,23 +1847,29 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
) : (
<>
<DismissibleUpsell
upsellId="taskList"
icon={<Cloud className="size-4 mt-0.5 shrink-0" />}
onClick={() => openUpsell()}
upsellId="taskListSupernova"
dismissOnClick={false}
className="bg-vscode-editor-background p-4 !text-base">
<Trans
i18nKey="cloud:upsell.taskList"
components={{
learnMoreLink: <VSCodeLink href="#" />,
}}
/>
<div className="w-full leading-relaxed">
<Trans
i18nKey="cloud:upsell.taskList"
components={{
lineBreak: <br />,
providersLink: (
<VSCodeLink
href="#"
onClick={() => vscode.postMessage({ type: "openSettings" })}
/>
),
}}
/>
</div>
</DismissibleUpsell>
</>
)}
</div>
{/* Show the task history preview if expanded and tasks exist */}
{taskHistory.length > 0 && isExpanded && <HistoryPreview />}
{hasTaskHistory && isExpanded && <HistoryPreview />}
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ vi.mock("qrcode", () => ({
// Mock react-i18next
vi.mock("react-i18next")

// Mock the cloud config
vi.mock("@roo-code/cloud/src/config", () => ({
getRooCodeApiUrl: vi.fn(() => "https://app.roocode.com"),
}))

// Mock the extension state context
vi.mock("@/context/ExtensionStateContext", () => ({
ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => children,
Expand Down
21 changes: 18 additions & 3 deletions webview-ui/src/components/cloud/CloudView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
vscode.postMessage({ type: "openExternal", url: cloudUrl })
}

const handleStartFreeTrial = () => {
vscode.postMessage({ type: "openExternal", url: "https://app.roocode.com/billing" })
}

const handleOpenCloudUrl = () => {
if (cloudApiUrl) {
vscode.postMessage({ type: "openExternal", url: cloudApiUrl })
Expand Down Expand Up @@ -272,9 +276,20 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
<div className={cn(authInProgress && "opacity-50")}>{renderCloudBenefitsContent(t)}</div>

{!authInProgress && (
<VSCodeButton appearance="primary" onClick={handleConnectClick} className="w-full">
{t("cloud:connect")}
</VSCodeButton>
<>
<VSCodeButton
Copy link
Contributor

Choose a reason for hiding this comment

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

This button logic is duplicated in ApiOptions.tsx (lines 666-679). Could we extract this into a shared component to avoid duplication and ensure consistency?

appearance="primary"
onClick={handleStartFreeTrial}
className="w-full">
{t("cloud:startFreeTrial")}
</VSCodeButton>
<VSCodeButton
appearance="secondary"
onClick={handleConnectClick}
className="w-full">
{t("cloud:connect")}
</VSCodeButton>
</>
)}

{/* Manual entry section */}
Expand Down
10 changes: 7 additions & 3 deletions webview-ui/src/components/history/HistoryPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import { useTaskSearch } from "./useTaskSearch"
import TaskItem from "./TaskItem"

const HistoryPreview = () => {
const { tasks } = useTaskSearch()
const { tasks, recentTasks } = useTaskSearch()
const { t } = useAppTranslation()

const handleViewAllHistory = () => {
vscode.postMessage({ type: "switchTab", tab: "history" })
}

const previewSource = tasks.length > 0 ? tasks : recentTasks
const previewTasks = previewSource.slice(0, 3)
const hasAnyTasks = recentTasks.length > 0

return (
<div className="flex flex-col gap-3">
{tasks.length !== 0 && (
{hasAnyTasks && (
<>
{tasks.slice(0, 3).map((item) => (
{previewTasks.map((item) => (
<TaskItem key={item.id} item={item} variant="compact" />
))}
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe("HistoryPreview", () => {
it("renders nothing when no tasks are available", () => {
mockUseTaskSearch.mockReturnValue({
tasks: [],
recentTasks: [],
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand All @@ -107,6 +108,7 @@ describe("HistoryPreview", () => {
it("renders up to 3 tasks when tasks are available", () => {
mockUseTaskSearch.mockReturnValue({
tasks: mockTasks,
recentTasks: mockTasks,
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand All @@ -132,6 +134,7 @@ describe("HistoryPreview", () => {
const threeTasks = mockTasks.slice(0, 3)
mockUseTaskSearch.mockReturnValue({
tasks: threeTasks,
recentTasks: threeTasks,
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand All @@ -156,6 +159,7 @@ describe("HistoryPreview", () => {
const oneTask = mockTasks.slice(0, 1)
mockUseTaskSearch.mockReturnValue({
tasks: oneTask,
recentTasks: oneTask,
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand All @@ -175,6 +179,7 @@ describe("HistoryPreview", () => {
it("passes correct props to TaskItem components", () => {
mockUseTaskSearch.mockReturnValue({
tasks: mockTasks.slice(0, 3),
recentTasks: mockTasks.slice(0, 3),
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand Down Expand Up @@ -214,6 +219,7 @@ describe("HistoryPreview", () => {
it("renders with correct container classes", () => {
mockUseTaskSearch.mockReturnValue({
tasks: mockTasks.slice(0, 1),
recentTasks: mockTasks.slice(0, 1),
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
Expand All @@ -228,4 +234,25 @@ describe("HistoryPreview", () => {

expect(container.firstChild).toHaveClass("flex", "flex-col", "gap-3")
})

Copy link
Contributor

Choose a reason for hiding this comment

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

Good addition of the fallback test case! Consider adding more edge cases:

  • What happens when both tasks and recentTasks are empty?
  • What happens when tasks has fewer than 3 items?
  • Test the interaction between filtered and recent tasks

it("falls back to recent tasks when filtered tasks are empty", () => {
mockUseTaskSearch.mockReturnValue({
tasks: [],
recentTasks: mockTasks,
searchQuery: "",
setSearchQuery: vi.fn(),
sortOption: "newest",
setSortOption: vi.fn(),
lastNonRelevantSort: null,
setLastNonRelevantSort: vi.fn(),
showAllWorkspaces: false,
setShowAllWorkspaces: vi.fn(),
})

render(<HistoryPreview />)

expect(screen.getByTestId("task-item-task-1")).toBeInTheDocument()
expect(screen.getByTestId("task-item-task-2")).toBeInTheDocument()
expect(screen.getByTestId("task-item-task-3")).toBeInTheDocument()
})
})
6 changes: 6 additions & 0 deletions webview-ui/src/components/history/useTaskSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export const useTaskSearch = () => {
return tasks
}, [taskHistory, showAllWorkspaces, cwd])

const recentTasks = useMemo(
() => [...presentableTasks].sort((a, b) => (b.ts || 0) - (a.ts || 0)),
[presentableTasks],
)

const fzf = useMemo(() => {
return new Fzf(presentableTasks, {
selector: (item) => item.task,
Expand Down Expand Up @@ -80,6 +85,7 @@ export const useTaskSearch = () => {

return {
tasks,
recentTasks,
searchQuery,
setSearchQuery,
sortOption,
Expand Down
8 changes: 8 additions & 0 deletions webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,14 @@ const ApiOptions = ({
<div className="flex flex-col gap-2">
<VSCodeButton
Copy link
Contributor

Choose a reason for hiding this comment

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

This duplicates the button logic from CloudView.tsx. Consider extracting into a shared component to maintain DRY principles and ensure consistent behavior across the app.

appearance="primary"
onClick={() =>
vscode.postMessage({ type: "openExternal", url: "https://app.roocode.com/billing" })
}
className="w-fit">
{t("settings:providers.roo.startFreeTrialButton")}
</VSCodeButton>
<VSCodeButton
appearance="secondary"
onClick={() => vscode.postMessage({ type: "rooCloudSignIn" })}
className="w-fit">
{t("settings:providers.roo.connectButton")}
Expand Down
9 changes: 5 additions & 4 deletions webview-ui/src/i18n/locales/ca/cloud.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/ca/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions webview-ui/src/i18n/locales/de/cloud.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion webview-ui/src/i18n/locales/de/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading