Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit dfdda80

Browse files
committed
UI
1 parent afb1a49 commit dfdda80

30 files changed

Lines changed: 206 additions & 106 deletions

packages/types/src/global-settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ export const globalSettingsSchema = z.object({
203203
* Used by the worktree feature to open the Roo Code sidebar in a new window.
204204
*/
205205
worktreeAutoOpenPath: z.string().optional(),
206+
/**
207+
* Whether to show the worktree selector in the home screen.
208+
* @default true
209+
*/
210+
showWorktreesInHomeScreen: z.boolean().optional(),
206211
})
207212

208213
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

packages/types/src/vscode-extension-host.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface ExtensionMessage {
107107
| "worktreeDefaults"
108108
| "worktreeIncludeStatus"
109109
| "branchWorktreeIncludeResult"
110+
| "folderSelected"
110111
text?: string
111112
payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
112113
checkpointWarning?: {
@@ -256,6 +257,8 @@ export interface ExtensionMessage {
256257
copyProgressBytesCopied?: number
257258
copyProgressTotalBytes?: number
258259
copyProgressItemName?: string
260+
// folderSelected
261+
path?: string
259262
}
260263

261264
export interface OpenAiCodexRateLimitsMessage {
@@ -331,6 +334,7 @@ export type ExtensionState = Pick<
331334
| "includeCurrentCost"
332335
| "maxGitStatusFiles"
333336
| "requestDelaySeconds"
337+
| "showWorktreesInHomeScreen"
334338
> & {
335339
version: string
336340
clineMessages: ClineMessage[]
@@ -599,6 +603,7 @@ export interface WebviewMessage {
599603
| "checkBranchWorktreeInclude"
600604
| "createWorktreeInclude"
601605
| "checkoutBranch"
606+
| "browseForWorktreePath"
602607
text?: string
603608
editedMessageContent?: string
604609
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"

src/core/webview/webviewMessageHandler.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,34 @@ export const webviewMessageHandler = async (
35563556
break
35573557
}
35583558

3559+
case "browseForWorktreePath": {
3560+
try {
3561+
const options: vscode.OpenDialogOptions = {
3562+
canSelectFiles: false,
3563+
canSelectFolders: true,
3564+
canSelectMany: false,
3565+
openLabel: t("worktrees:selectWorktreeLocation"),
3566+
title: t("worktrees:selectFolderForWorktree"),
3567+
defaultUri: vscode.workspace.workspaceFolders?.[0]?.uri
3568+
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, "..")
3569+
: undefined,
3570+
}
3571+
3572+
const result = await vscode.window.showOpenDialog(options)
3573+
if (result && result[0]) {
3574+
await provider.postMessageToWebview({
3575+
type: "folderSelected",
3576+
path: result[0].fsPath,
3577+
})
3578+
}
3579+
} catch (error) {
3580+
const errorMessage = error instanceof Error ? error.message : String(error)
3581+
provider.log(`Error opening folder picker: ${errorMessage}`)
3582+
}
3583+
3584+
break
3585+
}
3586+
35593587
default: {
35603588
// console.log(`Unhandled message type: ${message.type}`)
35613589
//

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
9696
cloudIsAuthenticated,
9797
messageQueue = [],
9898
isBrowserSessionActive,
99+
showWorktreesInHomeScreen,
99100
} = useExtensionState()
100101

101102
const messagesRef = useRef(messages)
@@ -1543,7 +1544,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15431544
</div>
15441545
)}
15451546

1546-
{!task && <WorktreeSelector />}
1547+
{!task && showWorktreesInHomeScreen && <WorktreeSelector />}
15471548

15481549
{task && (
15491550
<>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const WorktreeSelector = ({ disabled = false }: WorktreeSelectorProps) =>
7777
}, [])
7878

7979
// Don't render if not a git repo or only one worktree
80-
if (!isGitRepo) {
80+
if (!isGitRepo || worktrees.length <= 1) {
8181
return null
8282
}
8383

@@ -91,7 +91,7 @@ export const WorktreeSelector = ({ disabled = false }: WorktreeSelectorProps) =>
9191
data-testid="worktree-selector-trigger"
9292
className={cn(
9393
"inline-flex gap-1 mx-2 mb-1 items-center relative whitespace-nowrap px-3 py-2",
94-
"bg-transparent rounded-full text-vscode-foreground text-left",
94+
"bg-transparent rounded-full text-vscode-foreground text-left text-sm",
9595
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
9696
disabled
9797
? "opacity-50 cursor-not-allowed"

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,7 @@ describe("WorktreeSelector", () => {
218218
})
219219
})
220220

221-
test("shows info icon with tooltip in footer", () => {
222-
render(<WorktreeSelector />)
223-
224-
simulateWorktreeListMessage(mockWorktrees)
225-
226-
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
227-
228-
const infoIcon = document.querySelector(".codicon-info")
229-
expect(infoIcon).toBeInTheDocument()
230-
})
231-
232-
test("shows title in footer", () => {
221+
test("shows title in header", () => {
233222
render(<WorktreeSelector />)
234223

235224
simulateWorktreeListMessage(mockWorktrees)

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
CheckCheck,
1414
SquareMousePointer,
1515
GitBranch,
16-
History,
1716
Bell,
1817
Database,
1918
SquareTerminal,
@@ -29,6 +28,7 @@ import {
2928
Server,
3029
Users2,
3130
ArrowLeft,
31+
GitCommitVertical,
3232
} from "lucide-react"
3333

3434
import {
@@ -518,15 +518,15 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
518518
{ id: "providers", icon: Plug },
519519
{ id: "modes", icon: Users2 },
520520
{ id: "mcp", icon: Server },
521-
{ id: "worktrees", icon: GitBranch },
522521
{ id: "autoApprove", icon: CheckCheck },
523522
{ id: "slashCommands", icon: SquareSlash },
524523
{ id: "browser", icon: SquareMousePointer },
525-
{ id: "checkpoints", icon: History },
524+
{ id: "checkpoints", icon: GitCommitVertical },
526525
{ id: "notifications", icon: Bell },
527526
{ id: "contextManagement", icon: Database },
528527
{ id: "terminal", icon: SquareTerminal },
529528
{ id: "prompts", icon: MessageSquare },
529+
{ id: "worktrees", icon: GitBranch },
530530
{ id: "ui", icon: Glasses },
531531
{ id: "experimental", icon: FlaskConical },
532532
{ id: "language", icon: Globe },

webview-ui/src/components/ui/input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
88
<input
99
type={type}
1010
className={cn(
11-
"flex w-full text-vscode-input-foreground border border-vscode-dropdown-border bg-vscode-input-background rounded-xs px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
11+
"flex w-full text-vscode-input-foreground border border-vscode-dropdown-border bg-vscode-input-background rounded-xs px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
1212
className,
1313
)}
1414
ref={ref}

webview-ui/src/components/ui/toggle-switch.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
1717
"aria-label": ariaLabel,
1818
"data-testid": dataTestId,
1919
}) => {
20-
const dimensions = size === "small" ? { width: 16, height: 8, dotSize: 4 } : { width: 20, height: 10, dotSize: 6 }
20+
const dimensions = size === "small" ? { width: 20, height: 10, dotSize: 8 } : { width: 26, height: 10, dotSize: 6 }
2121

2222
const handleKeyDown = (e: React.KeyboardEvent) => {
2323
if (e.key === "Enter" || e.key === " ") {
@@ -40,20 +40,20 @@ export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
4040
height: `${dimensions.height}px`,
4141
backgroundColor: checked
4242
? "var(--vscode-button-background)"
43-
: "var(--vscode-titleBar-inactiveForeground)",
43+
: "var(--vscode-button-secondaryBackground)",
4444
borderRadius: `${dimensions.height / 2}px`,
4545
position: "relative",
4646
cursor: disabled ? "not-allowed" : "pointer",
4747
transition: "background-color 0.2s",
48-
opacity: disabled ? 0.4 : checked ? 0.8 : 0.6,
48+
opacity: disabled ? 0.6 : 1,
4949
}}
5050
onClick={disabled ? undefined : onChange}
5151
onKeyDown={handleKeyDown}>
5252
<div
5353
style={{
5454
width: `${dimensions.dotSize}px`,
5555
height: `${dimensions.dotSize}px`,
56-
backgroundColor: "var(--vscode-titleBar-activeForeground)",
56+
backgroundColor: "var(--vscode-foreground)",
5757
borderRadius: "50%",
5858
position: "absolute",
5959
top: `${(dimensions.height - dimensions.dotSize) / 2}px`,

webview-ui/src/components/worktrees/CreateWorktreeModal.tsx

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,9 @@ import type { WorktreeDefaultsResponse, BranchInfo, WorktreeIncludeStatus } from
55

66
import { vscode } from "@/utils/vscode"
77
import { useAppTranslation } from "@/i18n/TranslationContext"
8-
import {
9-
Dialog,
10-
DialogContent,
11-
DialogDescription,
12-
DialogFooter,
13-
DialogHeader,
14-
DialogTitle,
15-
Button,
16-
Input,
17-
} from "@/components/ui"
8+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Button, Input } from "@/components/ui"
189
import { SearchableSelect, type SearchableSelectOption } from "@/components/ui/searchable-select"
10+
import { CornerDownRight, Folder, FolderSearch, Info } from "lucide-react"
1911

2012
interface CreateWorktreeModalProps {
2113
open: boolean
@@ -81,6 +73,12 @@ export const CreateWorktreeModal = ({
8173
setIncludeStatus(message.worktreeIncludeStatus)
8274
break
8375
}
76+
case "folderSelected": {
77+
if (message.path) {
78+
setWorktreePath(message.path)
79+
}
80+
break
81+
}
8482
case "worktreeCopyProgress": {
8583
setCopyProgress({
8684
bytesCopied: message.copyProgressBytesCopied ?? 0,
@@ -152,14 +150,13 @@ export const CreateWorktreeModal = ({
152150
<DialogContent className="max-w-lg">
153151
<DialogHeader>
154152
<DialogTitle>{t("worktrees:createWorktree")}</DialogTitle>
155-
<DialogDescription>{t("worktrees:createWorktreeDescription")}</DialogDescription>
156153
</DialogHeader>
157154

158155
<div className="flex flex-col gap-3">
159156
{/* No .worktreeinclude warning - shows when the current worktree doesn't have .worktreeinclude */}
160157
{includeStatus?.exists === false && (
161158
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder text-sm">
162-
<span className="codicon codicon-warning text-vscode-charts-yellow flex-shrink-0" />
159+
<Info />
163160
<span className="text-vscode-foreground">
164161
<span className="font-medium">{t("worktrees:noIncludeFileWarning")}</span>
165162
{" — "}
@@ -170,17 +167,6 @@ export const CreateWorktreeModal = ({
170167
</div>
171168
)}
172169

173-
{/* Branch name */}
174-
<div className="flex flex-col gap-1">
175-
<label className="text-sm text-vscode-foreground">{t("worktrees:branchName")}</label>
176-
<Input
177-
value={branchName}
178-
onChange={(e) => setBranchName(e.target.value)}
179-
placeholder={defaults?.suggestedBranch || "worktree/feature-name"}
180-
className="rounded-full"
181-
/>
182-
</div>
183-
184170
{/* Base branch selector */}
185171
<div className="flex flex-col gap-1">
186172
<label className="text-sm text-vscode-foreground">{t("worktrees:baseBranch")}</label>
@@ -201,16 +187,32 @@ export const CreateWorktreeModal = ({
201187
)}
202188
</div>
203189

190+
{/* Branch name */}
191+
<div className="flex items-center gap-2">
192+
<CornerDownRight className="size-4 ml-2 shrink-0" />
193+
<label className="text-sm text-vscode-foreground shrink-0">{t("worktrees:branchName")}</label>
194+
<Input
195+
value={branchName}
196+
onChange={(e) => setBranchName(e.target.value)}
197+
placeholder={defaults?.suggestedBranch || "worktree/feature-name"}
198+
className="rounded-full"
199+
/>
200+
</div>
201+
204202
{/* Worktree path */}
205-
<div className="flex flex-col gap-1">
206-
<label className="text-sm text-vscode-foreground">{t("worktrees:worktreePath")}</label>
203+
<div className="flex items-center gap-2 relative">
204+
<Folder className="size-4 ml-2 shrink-0" />
205+
<label className="text-sm text-vscode-foreground shrink-0">{t("worktrees:worktreePath")}</label>
207206
<Input
208207
value={worktreePath}
209208
onChange={(e) => setWorktreePath(e.target.value)}
210209
placeholder={defaults?.suggestedPath || "/path/to/worktree"}
211-
className="rounded-full"
210+
className="rounded-full flex-1 pr-9"
211+
/>
212+
<FolderSearch
213+
className="size-4 shrink-0 absolute right-3 cursor-pointer hover:opacity-75 transition-opacity"
214+
onClick={() => vscode.postMessage({ type: "browseForWorktreePath" })}
212215
/>
213-
<p className="text-xs text-vscode-descriptionForeground">{t("worktrees:pathHint")}</p>
214216
</div>
215217

216218
{/* Error message */}
@@ -244,7 +246,7 @@ export const CreateWorktreeModal = ({
244246
<Button variant="secondary" onClick={onClose} disabled={isCreating}>
245247
{t("worktrees:cancel")}
246248
</Button>
247-
<Button onClick={handleCreate} disabled={!isValid || isCreating}>
249+
<Button variant="primary" onClick={handleCreate} disabled={!isValid || isCreating}>
248250
{isCreating ? (
249251
<>
250252
<span className="codicon codicon-loading codicon-modifier-spin mr-2" />

0 commit comments

Comments
 (0)