From ea8d1edbbf0d65179b97fc15c73759009d1c3463 Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Wed, 6 May 2026 12:11:02 +0200 Subject: [PATCH] chore(code): merge environments + cloud environments settings into one page Combine the two sidebar entries into a single Environments page with a SegmentedControl for switching between Local and Cloud. Adds shared intro copy framing what an environment is, and sharper per-segment copy. Hides the page chrome (title + tabs) when the form is open via a new formMode flag in the settings store. Cloud form gets a Back to environments button and matching header styling. Generated-By: PostHog Code Task-Id: f97e9506-0ae1-4d8a-aa7c-fc1fea276f87 --- .../settings/components/SettingsDialog.tsx | 47 +++--- .../CloudEnvironmentsSettings.tsx | 56 +++++-- .../environments/EnvironmentsSettings.tsx | 157 ++++++------------ .../LocalEnvironmentsSettings.tsx | 128 ++++++++++++++ .../environments/ProjectEnvironmentCard.tsx | 2 +- .../settings/stores/settingsDialogStore.ts | 14 +- 6 files changed, 256 insertions(+), 148 deletions(-) rename apps/code/src/renderer/features/settings/components/sections/{ => environments}/CloudEnvironmentsSettings.tsx (90%) create mode 100644 apps/code/src/renderer/features/settings/components/sections/environments/LocalEnvironmentsSettings.tsx diff --git a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx index 0f4ee8269f..dc35eadc24 100644 --- a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx +++ b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx @@ -14,13 +14,12 @@ import { ArrowLeft, ArrowsClockwise, CaretRight, - Cloud, Code, CreditCard, + Cube, Folder, GearSix, GithubLogo, - HardDrives, Keyboard, Palette, SignOut, @@ -34,7 +33,6 @@ import { type ReactNode, useEffect, useMemo } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { AdvancedSettings } from "./sections/AdvancedSettings"; import { ClaudeCodeSettings } from "./sections/ClaudeCodeSettings"; -import { CloudEnvironmentsSettings } from "./sections/CloudEnvironmentsSettings"; import { EnvironmentsSettings } from "./sections/environments/EnvironmentsSettings"; import { GeneralSettings } from "./sections/GeneralSettings"; import { GitHubSettings } from "./sections/GitHubSettings"; @@ -61,12 +59,7 @@ const SIDEBAR_ITEMS: SidebarItem[] = [ { id: "environments", label: "Environments", - icon: , - }, - { - id: "cloud-environments", - label: "Cloud environments", - icon: , + icon: , }, { id: "personalization", @@ -92,7 +85,7 @@ const CATEGORY_TITLES: Record = { workspaces: "Workspaces", worktrees: "Worktrees", environments: "Environments", - "cloud-environments": "Cloud environments", + "cloud-environments": "Environments", personalization: "Personalization", "claude-code": "Claude Code", shortcuts: "Shortcuts", @@ -109,7 +102,7 @@ const CATEGORY_COMPONENTS: Record = { workspaces: WorkspacesSettings, worktrees: WorktreesSettings, environments: EnvironmentsSettings, - "cloud-environments": CloudEnvironmentsSettings, + "cloud-environments": EnvironmentsSettings, personalization: PersonalizationSettings, "claude-code": ClaudeCodeSettings, shortcuts: ShortcutsSettings, @@ -121,7 +114,7 @@ const CATEGORY_COMPONENTS: Record = { }; export function SettingsDialog() { - const { isOpen, activeCategory, close, setCategory } = + const { isOpen, activeCategory, close, setCategory, formMode } = useSettingsDialogStore(); const isAuthenticated = useAuthStateValue( (state) => state.status === "authenticated", @@ -211,14 +204,20 @@ export function SettingsDialog() {
- {sidebarItems.map((item) => ( - setCategory(item.id)} - /> - ))} + {sidebarItems.map((item) => { + const isActive = + activeCategory === item.id || + (item.id === "environments" && + activeCategory === "cloud-environments"); + return ( + setCategory(item.id)} + /> + ); + })}
@@ -273,9 +272,11 @@ export function SettingsDialog() { - - {CATEGORY_TITLES[activeCategory]} - + {!formMode && ( + + {CATEGORY_TITLES[activeCategory]} + + )} diff --git a/apps/code/src/renderer/features/settings/components/sections/CloudEnvironmentsSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/environments/CloudEnvironmentsSettings.tsx similarity index 90% rename from apps/code/src/renderer/features/settings/components/sections/CloudEnvironmentsSettings.tsx rename to apps/code/src/renderer/features/settings/components/sections/environments/CloudEnvironmentsSettings.tsx index 8becbe7d65..399190ab24 100644 --- a/apps/code/src/renderer/features/settings/components/sections/CloudEnvironmentsSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/environments/CloudEnvironmentsSettings.tsx @@ -1,6 +1,6 @@ import { useSandboxEnvironments } from "@features/settings/hooks/useSandboxEnvironments"; import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore"; -import { PencilSimple, Plus, Trash } from "@phosphor-icons/react"; +import { ArrowLeft, PencilSimple, Plus, Trash } from "@phosphor-icons/react"; import { ChevronDownIcon } from "@radix-ui/react-icons"; import { Badge, @@ -195,6 +195,7 @@ export function CloudEnvironmentsSettings() { const consumeInitialAction = useSettingsDialogStore( (s) => s.consumeInitialAction, ); + const setFormMode = useSettingsDialogStore((s) => s.setFormMode); const [editingEnv, setEditingEnv] = useState(null); const [isCreating, setIsCreating] = useState(false); const [form, setForm] = useState(emptyForm()); @@ -210,6 +211,11 @@ export function CloudEnvironmentsSettings() { const isFormOpen = isCreating || editingEnv !== null; + useEffect(() => { + setFormMode(isFormOpen); + return () => setFormMode(false); + }, [isFormOpen, setFormMode]); + const domainValidation = useMemo(() => { if (form.network_access_level !== "custom") return { domains: [], errors: [] }; @@ -289,14 +295,26 @@ export function CloudEnvironmentsSettings() { if (isFormOpen) { return ( - - {editingEnv ? "Update cloud environment" : "New cloud environment"} - - + + + {editingEnv - ? "Changes take effect on the next session that uses this environment; running sessions are not affected." - : "Once created, you can pick this environment in the Cloud section of the workspace picker when starting a task."} + ? `Editing cloud environment ${editingEnv.name}` + : "Creating cloud environment"} + {editingEnv && ( + + Changes take effect on the next session that uses this environment; + running sessions are not affected. + + )} Name @@ -482,15 +500,21 @@ export function CloudEnvironmentsSettings() { return ( - - - A cloud environment is a reusable configuration applied to remote - sandbox sessions — it controls which outbound network hosts the - sandbox can reach and what environment variables (like API keys) are - available to the agent. Pick an environment in the Cloud section of - the workspace picker when starting a task; the Default option uses - full network access. - + + A cloud environment is a sandbox profile for tasks that run remotely. It + controls which outbound hosts the sandbox can reach and which + environment variables — like API keys — are injected before the agent + starts. Account-wide, so the same profile is available across all your + projects. The built-in{" "} + + Default + {" "} + uses full network access; create your own to lock things down or share + secrets with the agent. Pick one in the Cloud section of the workspace + picker when starting a task. + + + Environments