diff --git a/apps/web/src/components/BranchToolbar.browser.tsx b/apps/web/src/components/BranchToolbar.browser.tsx
new file mode 100644
index 0000000000..65d2f0b955
--- /dev/null
+++ b/apps/web/src/components/BranchToolbar.browser.tsx
@@ -0,0 +1,133 @@
+import "../index.css";
+
+import { MessageId, ProjectId, ThreadId } from "@t3tools/contracts";
+import { page } from "vitest/browser";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { render } from "vitest-browser-react";
+
+import { useComposerDraftStore } from "../composerDraftStore";
+import { useStore } from "../store";
+import BranchToolbar from "./BranchToolbar";
+
+vi.mock("./BranchToolbarBranchSelector", () => ({
+ BranchToolbarBranchSelector: () =>
,
+}));
+
+const THREAD_ID = ThreadId.makeUnsafe("thread-branch-toolbar");
+const PROJECT_ID = ProjectId.makeUnsafe("project-branch-toolbar");
+
+describe("BranchToolbar", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ document.body.innerHTML = "";
+ useComposerDraftStore.setState({
+ draftsByThreadId: {},
+ draftThreadsByThreadId: {},
+ projectDraftThreadIdByProjectId: {},
+ });
+ useStore.setState({
+ projects: [
+ {
+ id: PROJECT_ID,
+ name: "Project",
+ cwd: "/repo/project",
+ model: "gpt-5",
+ expanded: true,
+ scripts: [],
+ },
+ ],
+ threads: [],
+ threadsHydrated: false,
+ });
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = "";
+ });
+
+ it("renders a segmented local/worktree toggle for editable draft threads", async () => {
+ useComposerDraftStore.setState({
+ draftThreadsByThreadId: {
+ [THREAD_ID]: {
+ projectId: PROJECT_ID,
+ createdAt: "2026-03-11T10:00:00.000Z",
+ runtimeMode: "full-access",
+ interactionMode: "default",
+ branch: "main",
+ worktreePath: null,
+ envMode: "local",
+ },
+ },
+ projectDraftThreadIdByProjectId: {
+ [PROJECT_ID]: THREAD_ID,
+ },
+ });
+
+ const onEnvModeChange = vi.fn<(mode: "local" | "worktree") => void>();
+ await render(
+ ,
+ );
+
+ const localToggle = page.getByRole("button", { name: "Local" });
+ const worktreeToggle = page.getByRole("button", { name: "New worktree" });
+
+ await expect.element(localToggle).toHaveAttribute("aria-pressed", "true");
+ await expect.element(worktreeToggle).toHaveAttribute("aria-pressed", "false");
+
+ await worktreeToggle.click();
+
+ expect(onEnvModeChange).toHaveBeenCalledTimes(1);
+ expect(onEnvModeChange).toHaveBeenCalledWith("worktree");
+ });
+
+ it("keeps the selected mode visible when the environment is locked", async () => {
+ useStore.setState({
+ projects: useStore.getState().projects,
+ threads: [
+ {
+ id: THREAD_ID,
+ codexThreadId: null,
+ projectId: PROJECT_ID,
+ title: "Locked thread",
+ model: "gpt-5",
+ runtimeMode: "full-access",
+ interactionMode: "default",
+ session: null,
+ createdAt: "2026-03-11T10:00:00.000Z",
+ latestTurn: null,
+ lastVisitedAt: undefined,
+ branch: "main",
+ worktreePath: null,
+ turnDiffSummaries: [],
+ activities: [],
+ proposedPlans: [],
+ error: null,
+ messages: [
+ {
+ id: MessageId.makeUnsafe("message-1"),
+ role: "user",
+ text: "hello",
+ streaming: false,
+ createdAt: "2026-03-11T10:00:00.000Z",
+ },
+ ],
+ },
+ ],
+ threadsHydrated: false,
+ });
+
+ const onEnvModeChange = vi.fn<(mode: "local" | "worktree") => void>();
+ await render(
+ ,
+ );
+
+ const localToggle = page.getByRole("button", { name: "Local" });
+ const worktreeToggle = page.getByRole("button", { name: "New worktree" });
+
+ await expect.element(localToggle).toBeDisabled();
+ await expect.element(worktreeToggle).toBeDisabled();
+ await expect.element(localToggle).toHaveAttribute("aria-pressed", "true");
+
+ expect(onEnvModeChange).not.toHaveBeenCalled();
+ });
+});
diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx
index b2c60b2429..2ba8112f45 100644
--- a/apps/web/src/components/BranchToolbar.tsx
+++ b/apps/web/src/components/BranchToolbar.tsx
@@ -11,7 +11,7 @@ import {
resolveEffectiveEnvMode,
} from "./BranchToolbar.logic";
import { BranchToolbarBranchSelector } from "./BranchToolbarBranchSelector";
-import { Button } from "./ui/button";
+import { Toggle, ToggleGroup } from "./ui/toggle-group";
interface BranchToolbarProps {
threadId: ThreadId;
@@ -47,6 +47,9 @@ export default function BranchToolbar({
hasServerThread,
draftThreadEnvMode: draftThread?.envMode,
});
+ const envToggleValue = activeWorktreePath ? "worktree" : effectiveEnvMode;
+ const envToggleDisabled = envLocked || activeWorktreePath !== null;
+ const worktreeToggleLabel = activeWorktreePath ? "Worktree" : "New worktree";
const setThreadBranch = useCallback(
(branch: string | null, worktreePath: string | null) => {
@@ -103,23 +106,40 @@ export default function BranchToolbar({
if (!activeThreadId || !activeProject) return null;
return (
-
-
- {envLocked || activeWorktreePath ? (
-
- {activeWorktreePath ? "Worktree" : "Local"}
-
- ) : (
-