Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import {
ArrowLeft,
ArrowsClockwise,
CaretRight,
Cloud,
Code,
CreditCard,
Cube,
Folder,
GearSix,
GithubLogo,
HardDrives,
Keyboard,
Palette,
SignOut,
Expand All @@ -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";
Expand All @@ -61,12 +59,7 @@ const SIDEBAR_ITEMS: SidebarItem[] = [
{
id: "environments",
label: "Environments",
icon: <HardDrives size={16} />,
},
{
id: "cloud-environments",
label: "Cloud environments",
icon: <Cloud size={16} />,
icon: <Cube size={16} />,
},
{
id: "personalization",
Expand All @@ -92,7 +85,7 @@ const CATEGORY_TITLES: Record<SettingsCategory, string> = {
workspaces: "Workspaces",
worktrees: "Worktrees",
environments: "Environments",
"cloud-environments": "Cloud environments",
"cloud-environments": "Environments",
personalization: "Personalization",
"claude-code": "Claude Code",
shortcuts: "Shortcuts",
Expand All @@ -109,7 +102,7 @@ const CATEGORY_COMPONENTS: Record<SettingsCategory, React.ComponentType> = {
workspaces: WorkspacesSettings,
worktrees: WorktreesSettings,
environments: EnvironmentsSettings,
"cloud-environments": CloudEnvironmentsSettings,
"cloud-environments": EnvironmentsSettings,
personalization: PersonalizationSettings,
"claude-code": ClaudeCodeSettings,
shortcuts: ShortcutsSettings,
Expand All @@ -121,7 +114,7 @@ const CATEGORY_COMPONENTS: Record<SettingsCategory, React.ComponentType> = {
};

export function SettingsDialog() {
const { isOpen, activeCategory, close, setCategory } =
const { isOpen, activeCategory, close, setCategory, formMode } =
useSettingsDialogStore();
const isAuthenticated = useAuthStateValue(
(state) => state.status === "authenticated",
Expand Down Expand Up @@ -211,14 +204,20 @@ export function SettingsDialog() {

<ScrollArea className="flex-1">
<div className="flex flex-col pt-2">
{sidebarItems.map((item) => (
<SidebarNavItem
key={item.id}
item={item}
isActive={activeCategory === item.id}
onClick={() => setCategory(item.id)}
/>
))}
{sidebarItems.map((item) => {
const isActive =
activeCategory === item.id ||
(item.id === "environments" &&
activeCategory === "cloud-environments");
return (
<SidebarNavItem
key={item.id}
item={item}
isActive={isActive}
onClick={() => setCategory(item.id)}
/>
);
})}
</div>
</ScrollArea>

Expand Down Expand Up @@ -273,9 +272,11 @@ export function SettingsDialog() {
<ScrollArea className="h-full w-full">
<Box p="6" mx="auto" className="relative z-[1] max-w-[800px]">
<Flex direction="column" gap="4">
<Text className="font-medium text-lg leading-6.5">
{CATEGORY_TITLES[activeCategory]}
</Text>
{!formMode && (
<Text className="font-medium text-lg leading-6.5">
{CATEGORY_TITLES[activeCategory]}
</Text>
)}
<ActiveComponent />
</Flex>
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -195,6 +195,7 @@ export function CloudEnvironmentsSettings() {
const consumeInitialAction = useSettingsDialogStore(
(s) => s.consumeInitialAction,
);
const setFormMode = useSettingsDialogStore((s) => s.setFormMode);
const [editingEnv, setEditingEnv] = useState<SandboxEnvironment | null>(null);
const [isCreating, setIsCreating] = useState(false);
const [form, setForm] = useState<FormState>(emptyForm());
Expand All @@ -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: [] };
Expand Down Expand Up @@ -289,14 +295,26 @@ export function CloudEnvironmentsSettings() {
if (isFormOpen) {
return (
<Flex direction="column" gap="4">
<Text className="font-medium text-base">
{editingEnv ? "Update cloud environment" : "New cloud environment"}
</Text>
<Text color="gray" className="text-[13px]">
<button
type="button"
onClick={closeForm}
className="flex w-fit cursor-pointer items-center gap-1 border-0 bg-transparent p-0 text-[12px] text-gray-11 hover:text-gray-12"
>
<ArrowLeft size={10} />
<span>Back to environments</span>
</button>

<Text className="font-medium text-[13px]">
{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"}
</Text>
{editingEnv && (
<Text color="gray" className="text-[12px]">
Changes take effect on the next session that uses this environment;
running sessions are not affected.
</Text>
)}

<Flex direction="column" gap="1">
<Text className="font-medium text-sm">Name</Text>
Expand Down Expand Up @@ -482,15 +500,21 @@ export function CloudEnvironmentsSettings() {

return (
<Flex direction="column" gap="4">
<Flex justify="between" align="start" gap="4">
<Text color="gray" className="text-[13px]">
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.
</Text>
<Text color="gray" className="text-[13px]">
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{" "}
<Text color="gray" className="font-medium text-[13px]">
Default
</Text>{" "}
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.
</Text>
<Flex justify="between" align="center">
<Text className="font-medium text-[13px]">Environments</Text>
<Button size="1" variant="outline" onClick={openCreate}>
<Plus size={12} />
New environment
Expand Down
Original file line number Diff line number Diff line change
@@ -1,119 +1,64 @@
import { useFolders } from "@features/folders/hooks/useFolders";
import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore";
import type { Environment } from "@main/services/environment/schemas";
import type { RegisteredFolder } from "@main/services/folders/schemas";
import { Flex, Text } from "@radix-ui/themes";
import { useTRPC } from "@renderer/trpc/client";
import { useQueries } from "@tanstack/react-query";
import { useCallback, useEffect, useMemo, useState } from "react";
import { EnvironmentForm } from "./EnvironmentForm";
import { ProjectEnvironmentCard } from "./ProjectEnvironmentCard";
import { Cloud, HardDrives } from "@phosphor-icons/react";
import { Flex, SegmentedControl, Text } from "@radix-ui/themes";
import { CloudEnvironmentsSettings } from "./CloudEnvironmentsSettings";
import { LocalEnvironmentsSettings } from "./LocalEnvironmentsSettings";

export interface ProjectEnvironments {
folder: RegisteredFolder;
environments: Environment[];
isLoading: boolean;
}

interface FormTarget {
folder: RegisteredFolder;
environment?: Environment;
}
type Segment = "local" | "cloud";

export function EnvironmentsSettings() {
const trpc = useTRPC();
const { folders } = useFolders();
const [formTarget, setFormTarget] = useState<FormTarget | null>(null);

const environmentQueries = useQueries({
queries: folders.map((folder) =>
trpc.environment.list.queryOptions(
{ repoPath: folder.path },
{ staleTime: 30_000 },
),
),
});

const projects = useMemo(() => {
const result: ProjectEnvironments[] = [];

for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
const query = environmentQueries[i];

result.push({
folder,
environments: query?.data ?? [],
isLoading: query?.isLoading ?? true,
});
}

return result.sort((a, b) => a.folder.name.localeCompare(b.folder.name));
}, [folders, environmentQueries]);
const activeCategory = useSettingsDialogStore((s) => s.activeCategory);
const setCategory = useSettingsDialogStore((s) => s.setCategory);
const formMode = useSettingsDialogStore((s) => s.formMode);

const context = useSettingsDialogStore((s) => s.context);
const clearContext = useSettingsDialogStore((s) => s.clearContext);
const segment: Segment =
activeCategory === "cloud-environments" ? "cloud" : "local";

useEffect(() => {
if (!context.repoPath) return;
const folder = folders.find((f) => f.path === context.repoPath);
if (folder) {
setFormTarget({ folder });
}
clearContext();
}, [context.repoPath, folders, clearContext]);

const handleCreate = useCallback((folder: RegisteredFolder) => {
setFormTarget({ folder });
}, []);

const handleEdit = useCallback(
(folder: RegisteredFolder, environment: Environment) => {
setFormTarget({ folder, environment });
},
[],
);

const handleBack = useCallback(() => {
setFormTarget(null);
}, []);

if (formTarget) {
return (
<EnvironmentForm
key={formTarget.environment?.id ?? formTarget.folder.id}
folder={formTarget.folder}
environment={formTarget.environment}
onBack={handleBack}
/>
);
}
const handleSegmentChange = (value: string) => {
setCategory(value === "cloud" ? "cloud-environments" : "environments");
};

return (
<Flex direction="column" gap="4">
<Text color="gray" className="text-[13px]">
An environment defines how a fresh worktree of a project is prepared.
The setup script runs once when the worktree is created, so the agent
starts in a project that's already installed, built, and ready.
Environments are stored as TOML files inside the project and can be
committed so teammates share the same setup.
</Text>
<Text className="font-medium text-[13px]">Projects</Text>
{projects.length === 0 ? (
<Text color="gray" className="text-[13px]">
No projects registered. Open a folder to get started.
</Text>
{!formMode && (
<>
<Text color="gray" className="text-[13px]">
An environment defines what the agent works inside when you start a
task.{" "}
<Text color="gray" className="font-medium text-[13px]">
Local
</Text>{" "}
environments prepare a project on your machine;{" "}
<Text color="gray" className="font-medium text-[13px]">
cloud
</Text>{" "}
environments configure remote sandboxes.
</Text>
<SegmentedControl.Root
value={segment}
onValueChange={handleSegmentChange}
size="2"
>
<SegmentedControl.Item value="local">
<Flex align="center" gap="2">
<HardDrives size={14} />
<Text>Local</Text>
</Flex>
</SegmentedControl.Item>
<SegmentedControl.Item value="cloud">
<Flex align="center" gap="2">
<Cloud size={14} />
<Text>Cloud</Text>
</Flex>
</SegmentedControl.Item>
</SegmentedControl.Root>
</>
)}

{segment === "cloud" ? (
<CloudEnvironmentsSettings />
) : (
<Flex direction="column" gap="3">
{projects.map((project) => (
<ProjectEnvironmentCard
key={project.folder.id}
project={project}
onCreate={handleCreate}
onEdit={handleEdit}
/>
))}
</Flex>
<LocalEnvironmentsSettings />
)}
</Flex>
);
Expand Down
Loading
Loading