ux: improve worktree selector and creation UX (#10940)

* Delete modal

* Restructured

* Much more prominent

* UI

* i18n

* Fixes i18n

* Remove mergeresultmodel

* i18n

* tests

* knip

* code review
This commit is contained in:
Bruno Bergher 2026-01-24 22:06:37 +00:00 • committed by GitHub
parent c7910a99c7
commit c56f0f43c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
97 changed files with 1208 additions and 399 deletions

View file

@ -203,6 +203,11 @@ export const globalSettingsSchema = z.object({
* Used by the worktree feature to open the Roo Code sidebar in a new window.
*/
worktreeAutoOpenPath: z.string().optional(),
/**
* Whether to show the worktree selector in the home screen.
* @default true
*/
showWorktreesInHomeScreen: z.boolean().optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

View file

@ -107,6 +107,7 @@ export interface ExtensionMessage {
| "worktreeDefaults"
| "worktreeIncludeStatus"
| "branchWorktreeIncludeResult"
| "folderSelected"
text?: string
payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
checkpointWarning?: {
@ -119,7 +120,6 @@ export interface ExtensionMessage {
| "historyButtonClicked"
| "marketplaceButtonClicked"
| "cloudButtonClicked"
| "worktreesButtonClicked"
| "didBecomeVisible"
| "focusInput"
| "switchTab"
@ -257,6 +257,8 @@ export interface ExtensionMessage {
copyProgressBytesCopied?: number
copyProgressTotalBytes?: number
copyProgressItemName?: string
// folderSelected
path?: string
}
export interface OpenAiCodexRateLimitsMessage {
@ -332,6 +334,7 @@ export type ExtensionState = Pick<
| "includeCurrentCost"
| "maxGitStatusFiles"
| "requestDelaySeconds"
| "showWorktreesInHomeScreen"
> & {
version: string
clineMessages: ClineMessage[]
@ -600,6 +603,7 @@ export interface WebviewMessage {
| "checkBranchWorktreeInclude"
| "createWorktreeInclude"
| "checkoutBranch"
| "browseForWorktreePath"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"

View file

@ -35,7 +35,6 @@ export const commandIds = [
"popoutButtonClicked",
"cloudButtonClicked",
"settingsButtonClicked",
"worktreesButtonClicked",
"openInNewTab",

View file

@ -134,12 +134,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
if (!visibleProvider) return
visibleProvider.postMessageToWebview({ type: "action", action: "marketplaceButtonClicked" })
},
worktreesButtonClicked: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) return
TelemetryService.instance.captureTitleButtonClicked("worktrees")
visibleProvider.postMessageToWebview({ type: "action", action: "worktreesButtonClicked" })
},
newTask: handleNewTask,
setCustomStoragePath: async () => {
const { promptForCustomStoragePath } = await import("../utils/storage")

View file

@ -3556,6 +3556,34 @@ export const webviewMessageHandler = async (
break
}
case "browseForWorktreePath": {
try {
const options: vscode.OpenDialogOptions = {
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: t("worktrees:selectWorktreeLocation"),
title: t("worktrees:selectFolderForWorktree"),
defaultUri: vscode.workspace.workspaceFolders?.[0]?.uri
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, "..")
: undefined,
}
const result = await vscode.window.showOpenDialog(options)
if (result && result[0]) {
await provider.postMessageToWebview({
type: "folderSelected",
path: result[0].fsPath,
})
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
provider.log(`Error opening folder picker: ${errorMessage}`)
}
break
}
default: {
// console.log(`Unhandled message type: ${message.type}`)
//

4
src/i18n/locales/ca/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/de/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/es/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/fr/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/hi/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/id/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/it/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/ja/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/ko/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/nl/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/pl/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/pt-BR/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/ru/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/tr/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/vi/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/zh-CN/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

4
src/i18n/locales/zh-TW/worktrees.json generated Normal file
View file

@ -0,0 +1,4 @@
{
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree"
}

View file

@ -100,11 +100,6 @@
"title": "%command.settings.title%",
"icon": "$(settings-gear)"
},
{
"command": "roo-cline.worktreesButtonClicked",
"title": "%command.worktrees.title%",
"icon": "$(git-branch)"
},
{
"command": "roo-cline.openInNewTab",
"title": "%command.openInNewTab.title%",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Obrir a l'Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Configuració",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentació",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Ordres que es poden executar automàticament quan 'Aprova sempre les operacions d'execució' està activat",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Im Editor Öffnen",
"command.cloud.title": "Cloud",
"command.settings.title": "Einstellungen",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Dokumentation",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Befehle, die automatisch ausgeführt werden können, wenn 'Ausführungsoperationen immer genehmigen' aktiviert ist",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Abrir en Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Configuración",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentación",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Comandos que pueden ejecutarse automáticamente cuando 'Aprobar siempre operaciones de ejecución' está activado",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Ouvrir dans l'Éditeur",
"command.cloud.title": "Cloud",
"command.settings.title": "Paramètres",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentation",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Commandes pouvant être exécutées automatiquement lorsque 'Toujours approuver les opérations d'exécution' est activé",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "एडिटर में खोलें",
"command.cloud.title": "Cloud",
"command.settings.title": "सेटिंग्स",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "दस्तावेज़ीकरण",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "वे कमांड जो स्वचालित रूप से निष्पादित की जा सकती हैं जब 'हमेशा निष्पादन संचालन को स्वीकृत करें' सक्रिय हो",

View file

@ -11,7 +11,6 @@
"command.openInEditor.title": "Buka di Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Pengaturan",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Dokumentasi",
"command.openInNewTab.title": "Buka di Tab Baru",
"command.explainCode.title": "Jelaskan Kode",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Apri nell'Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Impostazioni",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentazione",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Comandi che possono essere eseguiti automaticamente quando 'Approva sempre le operazioni di esecuzione' è attivato",

View file

@ -11,7 +11,6 @@
"command.openInEditor.title": "エディタで開く",
"command.cloud.title": "Cloud",
"command.settings.title": "設定",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "ドキュメント",
"command.openInNewTab.title": "新しいタブで開く",
"command.explainCode.title": "コードの説明",

View file

@ -11,7 +11,6 @@
"command.openInEditor.title": "Open in Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Settings",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentation",
"command.openInNewTab.title": "Open In New Tab",
"command.explainCode.title": "Explain Code",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "에디터에서 열기",
"command.cloud.title": "Cloud",
"command.settings.title": "설정",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "문서",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "'항상 실행 작업 승인' 이 활성화되어 있을 때 자동으로 실행할 수 있는 명령어",

View file

@ -11,7 +11,6 @@
"command.openInEditor.title": "Openen in Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Instellingen",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentatie",
"command.openInNewTab.title": "Openen in Nieuw Tabblad",
"command.explainCode.title": "Leg Code Uit",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Otwórz w Edytorze",
"command.cloud.title": "Cloud",
"command.settings.title": "Ustawienia",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Dokumentacja",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Polecenia, które mogą być wykonywane automatycznie, gdy włączona jest opcja 'Zawsze zatwierdzaj operacje wykonania'",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Abrir no Editor",
"command.cloud.title": "Cloud",
"command.settings.title": "Configurações",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Documentação",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Comandos que podem ser executados automaticamente quando 'Sempre aprovar operações de execução' está ativado",

View file

@ -11,7 +11,6 @@
"command.openInEditor.title": "Открыть в редакторе",
"command.cloud.title": "Cloud",
"command.settings.title": "Настройки",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Документация",
"command.openInNewTab.title": "Открыть в новой вкладке",
"command.explainCode.title": "Объяснить код",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Düzenleyicide Aç",
"command.cloud.title": "Cloud",
"command.settings.title": "Ayarlar",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Dokümantasyon",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "'Her zaman yürütme işlemlerini onayla' etkinleştirildiğinde otomatik olarak yürütülebilen komutlar",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "Mở trong Trình Soạn Thảo",
"command.cloud.title": "Cloud",
"command.settings.title": "Cài Đặt",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "Tài Liệu",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Các lệnh có thể được thực thi tự động khi 'Luôn phê duyệt các thao tác thực thi' được bật",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "在编辑器中打开",
"command.cloud.title": "Cloud",
"command.settings.title": "设置",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "文档",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "当启用'始终批准执行操作'时可以自动执行的命令",

View file

@ -24,7 +24,6 @@
"command.openInEditor.title": "在編輯器中開啟",
"command.cloud.title": "Cloud",
"command.settings.title": "設定",
"command.worktrees.title": "Worktrees",
"command.documentation.title": "文件",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "當啟用'始終批准執行操作'時可以自動執行的命令",

View file

@ -20,12 +20,11 @@ import { CheckpointRestoreDialog } from "./components/chat/CheckpointRestoreDial
import { DeleteMessageDialog, EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog"
import ErrorBoundary from "./components/ErrorBoundary"
import { CloudView } from "./components/cloud/CloudView"
import { WorktreesView } from "./components/worktrees"
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
import { TooltipProvider } from "./components/ui/tooltip"
import { STANDARD_TOOLTIP_DELAY } from "./components/ui/standard-tooltip"
type Tab = "settings" | "history" | "chat" | "marketplace" | "cloud" | "worktrees"
type Tab = "settings" | "history" | "chat" | "marketplace" | "cloud"
interface DeleteMessageDialogState {
isOpen: boolean
@ -51,7 +50,6 @@ const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]
historyButtonClicked: "history",
marketplaceButtonClicked: "marketplace",
cloudButtonClicked: "cloud",
worktreesButtonClicked: "worktrees",
}
const App = () => {
@ -247,7 +245,6 @@ const App = () => {
organizations={cloudOrganizations}
/>
)}
{tab === "worktrees" && <WorktreesView onDone={() => switchTab("chat")} />}
<ChatView
ref={chatViewRef}
isHidden={tab !== "chat"}

View file

@ -45,6 +45,7 @@ import SystemPromptWarning from "./SystemPromptWarning"
import ProfileViolationWarning from "./ProfileViolationWarning"
import { CheckpointWarning } from "./CheckpointWarning"
import { QueuedMessages } from "./QueuedMessages"
import { WorktreeSelector } from "./WorktreeSelector"
import DismissibleUpsell from "../common/DismissibleUpsell"
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
import { Cloud } from "lucide-react"
@ -95,6 +96,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
cloudIsAuthenticated,
messageQueue = [],
isBrowserSessionActive,
showWorktreesInHomeScreen,
} = useExtensionState()
const messagesRef = useRef(messages)
@ -1543,6 +1545,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
</div>
)}
{!task && showWorktreesInHomeScreen && <WorktreeSelector />}
{task && (
<>
<div className="grow flex" ref={scrollContainerRef}>

View file

@ -0,0 +1,193 @@
import React, { useState, useCallback, useEffect, useMemo } from "react"
import { GitBranch, Check, ChevronDown, Plus } from "lucide-react"
import type { Worktree, WorktreeListResponse } from "@roo-code/types"
import { cn } from "@/lib/utils"
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, Button } from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { vscode } from "@/utils/vscode"
import { CreateWorktreeModal } from "../worktrees/CreateWorktreeModal"
import { IconButton } from "./IconButton"
interface WorktreeSelectorProps {
disabled?: boolean
}
export const WorktreeSelector = ({ disabled = false }: WorktreeSelectorProps) => {
const { t } = useAppTranslation()
const [open, setOpen] = useState(false)
const [worktrees, setWorktrees] = useState<Worktree[]>([])
const [isGitRepo, setIsGitRepo] = useState(true)
const [showCreateModal, setShowCreateModal] = useState(false)
const portalContainer = useRooPortal("roo-portal")
// Find current worktree
const currentWorktree = useMemo(() => worktrees.find((w) => w.isCurrent), [worktrees])
// Fetch worktrees when popover opens
const fetchWorktrees = useCallback(() => {
vscode.postMessage({ type: "listWorktrees" })
}, [])
// Handle messages from extension
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const message = event.data
if (message.type === "worktreeList") {
const response: WorktreeListResponse = message
setWorktrees(response.worktrees || [])
setIsGitRepo(response.isGitRepo)
}
}
window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
}, [])
// Initial fetch and refresh on open
useEffect(() => {
fetchWorktrees()
}, [fetchWorktrees])
useEffect(() => {
if (open) {
fetchWorktrees()
}
}, [open, fetchWorktrees])
const handleSelect = useCallback((worktreePath: string) => {
vscode.postMessage({
type: "switchWorktree",
worktreePath: worktreePath,
worktreeNewWindow: false,
})
setOpen(false)
}, [])
const handleSettingsClick = useCallback(() => {
vscode.postMessage({
type: "switchTab",
tab: "settings",
values: { section: "worktrees" },
})
setOpen(false)
}, [])
// Don't render if not a git repo or only one worktree
if (!isGitRepo || worktrees.length <= 1) {
return null
}
const title = t("worktrees:selector.tooltip")
return (
<Popover open={open} onOpenChange={setOpen} data-testid="worktree-selector-root">
<StandardTooltip content={title}>
<PopoverTrigger
disabled={disabled}
data-testid="worktree-selector-trigger"
className={cn(
"inline-flex gap-1 mx-2 mb-1 items-center relative whitespace-nowrap px-3 py-2",
"bg-transparent rounded-full text-vscode-foreground text-left text-sm",
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
disabled
? "opacity-50 cursor-not-allowed"
: "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer",
)}>
<span className="font-semibold mr-2">{t("worktrees:selector.worktree")}:</span>
<GitBranch className="w-3 h-3" />
<span className="truncate">{currentWorktree?.branch || t("worktrees:noBranch")}</span>
<ChevronDown className="size-3" />
</PopoverTrigger>
</StandardTooltip>
<PopoverContent
align="start"
sideOffset={4}
container={portalContainer}
className="p-0 overflow-hidden min-w-80 max-w-9/10">
<div className="flex flex-col w-full">
{/* Bottom bar with settings cog and title */}
<div className="px-3 pb-4">
<div className="flex flex-row items-center justify-between">
<h4 className="">{t("worktrees:selector.title")}</h4>
<IconButton
iconClass="codicon-settings-gear"
title={t("worktrees:selector.settings")}
onClick={handleSettingsClick}
/>
</div>
<p className="m-0 text-xs text-vscode-descriptionForeground">
{t("worktrees:selector.description")}
</p>
</div>
{/* Worktree list */}
<div className="max-h-[300px] overflow-y-auto py-1">
{worktrees.map((worktree) => {
const isSelected = worktree.isCurrent
return (
<div
key={worktree.path}
onClick={() => !isSelected && handleSelect(worktree.path)}
data-testid="worktree-selector-item"
className={cn(
"px-3 py-1.5 text-sm cursor-pointer flex items-center",
"hover:bg-vscode-list-hoverBackground",
isSelected &&
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
)}>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<GitBranch className="w-3 h-3 shrink-0" />
<span className="font-bold truncate">
{worktree.branch || t("worktrees:noBranch")}
</span>
{worktree.isBare && (
<span className="text-xs opacity-70">{t("worktrees:primary")}</span>
)}
</div>
<div className="text-xs text-vscode-descriptionForeground ml-5 truncate">
{worktree.path}
</div>
</div>
{isSelected && <Check className="ml-auto size-4 p-0.5" />}
</div>
)
})}
</div>
{/* New worktree button */}
<div className="px-3 py-2 border-t border-vscode-panel-border">
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={() => {
setShowCreateModal(true)
setOpen(false)
}}>
<Plus className="w-3 h-3 mr-2" />
{t("worktrees:newWorktree")}
</Button>
</div>
</div>
</PopoverContent>
{/* Create Worktree Modal */}
{showCreateModal && (
<CreateWorktreeModal
open={showCreateModal}
onClose={() => setShowCreateModal(false)}
openAfterCreate={true}
onSuccess={() => {
setShowCreateModal(false)
fetchWorktrees()
}}
/>
)}
</Popover>
)
}

View file

@ -0,0 +1,299 @@
import { render, screen, fireEvent, act } from "@/utils/test-utils"
import type { Worktree, WorktreeListResponse } from "@roo-code/types"
import { WorktreeSelector } from "../WorktreeSelector"
const mockPostMessage = vi.fn()
vi.mock("@/utils/vscode", () => ({
vscode: {
postMessage: (...args: unknown[]) => mockPostMessage(...args),
},
}))
vi.mock("@/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock("@/components/ui/hooks/useRooPortal", () => ({
useRooPortal: () => document.body,
}))
const mockWorktrees: Worktree[] = [
{
path: "/path/to/main",
branch: "main",
commitHash: "abc123",
isCurrent: true,
isBare: true,
isDetached: false,
isLocked: false,
},
{
path: "/path/to/feature-branch",
branch: "feature-branch",
commitHash: "def456",
isCurrent: false,
isBare: false,
isDetached: false,
isLocked: false,
},
{
path: "/path/to/another-branch",
branch: "another-branch",
commitHash: "ghi789",
isCurrent: false,
isBare: false,
isDetached: false,
isLocked: false,
},
]
const simulateWorktreeListMessage = (worktrees: Worktree[], isGitRepo: boolean = true) => {
const message: Partial<WorktreeListResponse> & { type: string } = {
type: "worktreeList",
worktrees,
isGitRepo,
isMultiRoot: false,
isSubfolder: false,
gitRootPath: "/path/to/repo",
}
act(() => {
window.dispatchEvent(new MessageEvent("message", { data: message }))
})
}
describe("WorktreeSelector", () => {
beforeEach(() => {
mockPostMessage.mockClear()
})
test("requests worktrees on mount", () => {
render(<WorktreeSelector />)
expect(mockPostMessage).toHaveBeenCalledWith({ type: "listWorktrees" })
})
test("does not render when not a git repo", () => {
const { container } = render(<WorktreeSelector />)
simulateWorktreeListMessage([], false)
expect(container.querySelector('[data-testid="worktree-selector-trigger"]')).not.toBeInTheDocument()
})
test("does not render when only one worktree exists", () => {
const { container } = render(<WorktreeSelector />)
simulateWorktreeListMessage([mockWorktrees[0]])
expect(container.querySelector('[data-testid="worktree-selector-trigger"]')).not.toBeInTheDocument()
})
test("renders trigger when multiple worktrees exist", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
expect(screen.getByTestId("worktree-selector-trigger")).toBeInTheDocument()
})
test("shows current branch name on trigger", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
const trigger = screen.getByTestId("worktree-selector-trigger")
expect(trigger).toHaveTextContent("main")
})
test("opens popover and shows all worktrees when clicked", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// Should show all worktree items
const items = screen.getAllByTestId("worktree-selector-item")
expect(items).toHaveLength(3)
})
test("shows worktree branch names and paths in popover", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// "main" appears twice: once in trigger and once in popover list
expect(screen.getAllByText("main").length).toBeGreaterThanOrEqual(2)
expect(screen.getByText("feature-branch")).toBeInTheDocument()
expect(screen.getByText("another-branch")).toBeInTheDocument()
expect(screen.getByText("/path/to/main")).toBeInTheDocument()
expect(screen.getByText("/path/to/feature-branch")).toBeInTheDocument()
})
test("shows primary badge on primary worktree", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
expect(screen.getByText("worktrees:primary")).toBeInTheDocument()
})
test("sends switch message when selecting a different worktree", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// Click on feature-branch worktree
const items = screen.getAllByTestId("worktree-selector-item")
fireEvent.click(items[1]) // Second item is feature-branch
expect(mockPostMessage).toHaveBeenCalledWith({
type: "switchWorktree",
worktreePath: "/path/to/feature-branch",
worktreeNewWindow: false,
})
})
test("does not send switch message when selecting current worktree", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
mockPostMessage.mockClear()
// Click on current worktree (main)
const items = screen.getAllByTestId("worktree-selector-item")
fireEvent.click(items[0])
expect(mockPostMessage).not.toHaveBeenCalledWith(
expect.objectContaining({
type: "switchWorktree",
}),
)
})
test("shows settings button in footer", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// Check for settings gear icon button
const settingsButton = document.querySelector(".codicon-settings-gear")
expect(settingsButton).toBeInTheDocument()
})
test("navigates to worktree settings when settings button clicked", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// Find and click the settings button
const settingsButton = document.querySelector(".codicon-settings-gear")
expect(settingsButton).toBeInTheDocument()
fireEvent.click(settingsButton!.closest("button")!)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "switchTab",
tab: "settings",
values: { section: "worktrees" },
})
})
test("shows title in header", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
expect(screen.getByText("worktrees:selector.title")).toBeInTheDocument()
})
test("shows description in popover", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
expect(screen.getByText("worktrees:selector.description")).toBeInTheDocument()
})
test("is disabled when disabled prop is true", () => {
render(<WorktreeSelector disabled={true} />)
simulateWorktreeListMessage(mockWorktrees)
const trigger = screen.getByTestId("worktree-selector-trigger")
expect(trigger).toBeDisabled()
})
test("refreshes worktrees when popover opens", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
mockPostMessage.mockClear()
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
expect(mockPostMessage).toHaveBeenCalledWith({ type: "listWorktrees" })
})
test("shows check mark on current worktree", () => {
render(<WorktreeSelector />)
simulateWorktreeListMessage(mockWorktrees)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// The current worktree should have Check component (Check from lucide-react)
const items = screen.getAllByTestId("worktree-selector-item")
const currentItem = items[0] // main is current
const checkIcon = currentItem.querySelector("svg.lucide-check")
expect(checkIcon).toBeInTheDocument()
})
test("handles worktree with no branch (detached HEAD)", () => {
const worktreesWithDetached: Worktree[] = [
...mockWorktrees,
{
path: "/path/to/detached",
branch: "",
commitHash: "xyz999",
isCurrent: false,
isBare: false,
isDetached: true,
isLocked: false,
},
]
render(<WorktreeSelector />)
simulateWorktreeListMessage(worktreesWithDetached)
fireEvent.click(screen.getByTestId("worktree-selector-trigger"))
// Should show "worktrees:noBranch" translation key for detached HEAD
expect(screen.getByText("worktrees:noBranch")).toBeInTheDocument()
})
})

View file

@ -28,6 +28,7 @@ import {
Server,
Users2,
ArrowLeft,
GitCommitVertical,
} from "lucide-react"
import {
@ -79,6 +80,7 @@ import { SlashCommandsSettings } from "./SlashCommandsSettings"
import { UISettings } from "./UISettings"
import ModesView from "../modes/ModesView"
import McpView from "../mcp/McpView"
import { WorktreesView } from "../worktrees/WorktreesView"
import { SettingsSearch } from "./SettingsSearch"
import { useSearchIndexRegistry, SearchIndexProvider } from "./useSettingsSearch"
@ -104,6 +106,7 @@ export const sectionNames = [
"terminal",
"modes",
"mcp",
"worktrees",
"prompts",
"ui",
"experimental",
@ -518,11 +521,12 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{ id: "autoApprove", icon: CheckCheck },
{ id: "slashCommands", icon: SquareSlash },
{ id: "browser", icon: SquareMousePointer },
{ id: "checkpoints", icon: GitBranch },
{ id: "checkpoints", icon: GitCommitVertical },
{ id: "notifications", icon: Bell },
{ id: "contextManagement", icon: Database },
{ id: "terminal", icon: SquareTerminal },
{ id: "prompts", icon: MessageSquare },
{ id: "worktrees", icon: GitBranch },
{ id: "ui", icon: Glasses },
{ id: "experimental", icon: FlaskConical },
{ id: "language", icon: Globe },
@ -889,6 +893,9 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{/* MCP Section */}
{renderTab === "mcp" && <McpView />}
{/* Worktrees Section */}
{renderTab === "worktrees" && <WorktreesView />}
{/* Prompts Section */}
{renderTab === "prompts" && (
<PromptsSettings

View file

@ -28,6 +28,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
// Mock UI components
vi.mock("@src/components/ui", () => ({
ToggleSwitch: ({ checked, onChange, "aria-label": ariaLabel, "data-testid": dataTestId }: any) => (
<button role="switch" aria-checked={checked} aria-label={ariaLabel} data-testid={dataTestId} onClick={onChange}>
Toggle
</button>
),
AlertDialog: ({ open, children }: any) => (open ? <div data-testid="alert-dialog">{children}</div> : null),
AlertDialogContent: ({ children }: any) => <div>{children}</div>,
AlertDialogTitle: ({ children }: any) => <div data-testid="alert-title">{children}</div>,

View file

@ -116,6 +116,11 @@ vi.mock("../../../components/common/Tab", () => ({
vi.mock("@/components/ui", () => ({
...vi.importActual("@/components/ui"),
ToggleSwitch: ({ checked, onChange, "aria-label": ariaLabel, "data-testid": dataTestId }: any) => (
<button role="switch" aria-checked={checked} aria-label={ariaLabel} data-testid={dataTestId} onClick={onChange}>
Toggle
</button>
),
Popover: ({ children }: any) => <div data-testid="popover">{children}</div>,
PopoverTrigger: ({ children }: any) => <div data-testid="popover-trigger">{children}</div>,
PopoverContent: ({ children }: any) => <div data-testid="popover-content">{children}</div>,

View file

@ -29,6 +29,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
// Mock UI components
vi.mock("@src/components/ui", () => ({
ToggleSwitch: ({ checked, onChange, "aria-label": ariaLabel, "data-testid": dataTestId }: any) => (
<button role="switch" aria-checked={checked} aria-label={ariaLabel} data-testid={dataTestId} onClick={onChange}>
Toggle
</button>
),
AlertDialog: ({ children }: any) => <div>{children}</div>,
AlertDialogContent: ({ children }: any) => <div>{children}</div>,
AlertDialogTitle: ({ children }: any) => <div>{children}</div>,

View file

@ -85,8 +85,8 @@ describe("ToggleSwitch", () => {
const toggle = screen.getByRole("switch")
expect(toggle).toBeInTheDocument()
// Medium size should be 20px x 10px
expect(toggle).toHaveStyle({ width: "20px", height: "10px" })
// Medium size should be 26px x 10px
expect(toggle).toHaveStyle({ width: "26px", height: "10px" })
})
it("defaults to small size", () => {
@ -95,7 +95,7 @@ describe("ToggleSwitch", () => {
const toggle = screen.getByRole("switch")
expect(toggle).toBeInTheDocument()
// Small size should be 16px x 8px
expect(toggle).toHaveStyle({ width: "16px", height: "8px" })
// Small size should be 20px x 10px
expect(toggle).toHaveStyle({ width: "20px", height: "10px" })
})
})

View file

@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-30 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
{
variants: {
variant: {

View file

@ -40,12 +40,12 @@ function DialogContent({ className, children, ...props }: React.ComponentProps<t
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-vscode-editor-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
"bg-vscode-editor-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<DialogPrimitive.Close className="ring-offset-background cursor-pointer focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
@ -68,7 +68,7 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
className={cn("flex justify-end gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
)

View file

@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
<input
type={type}
className={cn(
"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",
"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",
className,
)}
ref={ref}

View file

@ -17,7 +17,7 @@ export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
"aria-label": ariaLabel,
"data-testid": dataTestId,
}) => {
const dimensions = size === "small" ? { width: 16, height: 8, dotSize: 4 } : { width: 20, height: 10, dotSize: 6 }
const dimensions = size === "small" ? { width: 20, height: 10, dotSize: 8 } : { width: 26, height: 10, dotSize: 6 }
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
@ -40,12 +40,12 @@ export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
height: `${dimensions.height}px`,
backgroundColor: checked
? "var(--vscode-button-background)"
: "var(--vscode-titleBar-inactiveForeground)",
: "var(--vscode-button-secondaryBackground)",
borderRadius: `${dimensions.height / 2}px`,
position: "relative",
cursor: disabled ? "not-allowed" : "pointer",
transition: "background-color 0.2s",
opacity: disabled ? 0.4 : checked ? 0.8 : 0.6,
opacity: disabled ? 0.6 : 1,
}}
onClick={disabled ? undefined : onChange}
onKeyDown={handleKeyDown}>
@ -53,7 +53,7 @@ export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
style={{
width: `${dimensions.dotSize}px`,
height: `${dimensions.dotSize}px`,
backgroundColor: "var(--vscode-titleBar-activeForeground)",
backgroundColor: "var(--vscode-foreground)",
borderRadius: "50%",
position: "absolute",
top: `${(dimensions.height - dimensions.dotSize) / 2}px`,

View file

@ -5,17 +5,9 @@ import type { WorktreeDefaultsResponse, BranchInfo, WorktreeIncludeStatus } from
import { vscode } from "@/utils/vscode"
import { useAppTranslation } from "@/i18n/TranslationContext"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Button,
Input,
} from "@/components/ui"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Button, Input } from "@/components/ui"
import { SearchableSelect, type SearchableSelectOption } from "@/components/ui/searchable-select"
import { CornerDownRight, Folder, FolderSearch, Info } from "lucide-react"
interface CreateWorktreeModalProps {
open: boolean
@ -81,6 +73,12 @@ export const CreateWorktreeModal = ({
setIncludeStatus(message.worktreeIncludeStatus)
break
}
case "folderSelected": {
if (message.path) {
setWorktreePath(message.path)
}
break
}
case "worktreeCopyProgress": {
setCopyProgress({
bytesCopied: message.copyProgressBytesCopied ?? 0,
@ -152,14 +150,13 @@ export const CreateWorktreeModal = ({
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>{t("worktrees:createWorktree")}</DialogTitle>
<DialogDescription>{t("worktrees:createWorktreeDescription")}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
{/* No .worktreeinclude warning - shows when the current worktree doesn't have .worktreeinclude */}
{includeStatus?.exists === false && (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder text-sm">
<span className="codicon codicon-warning text-vscode-charts-yellow flex-shrink-0" />
<Info />
<span className="text-vscode-foreground">
<span className="font-medium">{t("worktrees:noIncludeFileWarning")}</span>
{" — "}
@ -170,17 +167,6 @@ export const CreateWorktreeModal = ({
</div>
)}
{/* Branch name */}
<div className="flex flex-col gap-1">
<label className="text-sm text-vscode-foreground">{t("worktrees:branchName")}</label>
<Input
value={branchName}
onChange={(e) => setBranchName(e.target.value)}
placeholder={defaults?.suggestedBranch || "worktree/feature-name"}
className="rounded-full"
/>
</div>
{/* Base branch selector */}
<div className="flex flex-col gap-1">
<label className="text-sm text-vscode-foreground">{t("worktrees:baseBranch")}</label>
@ -201,16 +187,32 @@ export const CreateWorktreeModal = ({
)}
</div>
{/* Branch name */}
<div className="flex items-center gap-2">
<CornerDownRight className="size-4 ml-2 shrink-0" />
<label className="text-sm text-vscode-foreground shrink-0">{t("worktrees:branchName")}</label>
<Input
value={branchName}
onChange={(e) => setBranchName(e.target.value)}
placeholder={defaults?.suggestedBranch || "worktree/feature-name"}
className="rounded-full"
/>
</div>
{/* Worktree path */}
<div className="flex flex-col gap-1">
<label className="text-sm text-vscode-foreground">{t("worktrees:worktreePath")}</label>
<div className="flex items-center gap-2 relative">
<Folder className="size-4 ml-2 shrink-0" />
<label className="text-sm text-vscode-foreground shrink-0">{t("worktrees:worktreePath")}</label>
<Input
value={worktreePath}
onChange={(e) => setWorktreePath(e.target.value)}
placeholder={defaults?.suggestedPath || "/path/to/worktree"}
className="rounded-full"
className="rounded-full flex-1 pr-9"
/>
<FolderSearch
className="size-4 shrink-0 absolute right-3 cursor-pointer hover:opacity-75 transition-opacity"
onClick={() => vscode.postMessage({ type: "browseForWorktreePath" })}
/>
<p className="text-xs text-vscode-descriptionForeground">{t("worktrees:pathHint")}</p>
</div>
{/* Error message */}
@ -244,7 +246,7 @@ export const CreateWorktreeModal = ({
<Button variant="secondary" onClick={onClose} disabled={isCreating}>
{t("worktrees:cancel")}
</Button>
<Button onClick={handleCreate} disabled={!isValid || isCreating}>
<Button variant="primary" onClick={handleCreate} disabled={!isValid || isCreating}>
{isCreating ? (
<>
<span className="codicon codicon-loading codicon-modifier-spin mr-2" />

View file

@ -4,16 +4,8 @@ import type { Worktree } from "@roo-code/types"
import { vscode } from "@/utils/vscode"
import { useAppTranslation } from "@/i18n/TranslationContext"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Button,
Checkbox,
} from "@/components/ui"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Button, Checkbox } from "@/components/ui"
import { Folder, GitBranch, TriangleAlert } from "lucide-react"
interface DeleteWorktreeModalProps {
open: boolean
@ -63,35 +55,38 @@ export const DeleteWorktreeModal = ({ open, onClose, worktree, onSuccess }: Dele
<Dialog open={open} onOpenChange={(isOpen: boolean) => !isOpen && onClose()}>
<DialogContent>
<DialogHeader>
<div className="flex items-center gap-2">
<span className="codicon codicon-warning text-vscode-charts-yellow text-xl" />
<DialogTitle>{t("worktrees:deleteWorktree")}</DialogTitle>
</div>
<DialogDescription>{t("worktrees:deleteWorktreeDescription")}</DialogDescription>
<DialogTitle>{t("worktrees:deleteWorktree")}</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-3 overflow-hidden">
{/* Worktree info */}
<div className="px-2 py-1.5 rounded bg-vscode-input-background border border-vscode-input-border">
<div className="flex items-center gap-2">
<span className="codicon codicon-git-branch flex-shrink-0" />
<span className="font-medium text-vscode-foreground truncate">
<div className="flex flex-col p-5 gap-2 cursor-default rounded-xl text-vscode-foreground bg-vscode-input-background">
<p className="flex items-center gap-2 m-0">
<GitBranch className="size-4 shrink-0" />
<span className="font-medium truncate">
{worktree.branch ||
(worktree.isDetached ? t("worktrees:detachedHead") : t("worktrees:noBranch"))}
</span>
</div>
<div className="text-xs text-vscode-descriptionForeground mt-0.5 truncate">{worktree.path}</div>
</p>
<p className="flex items-start gap-2 m-0">
<Folder className="size-4 shrink-0" />
<span className="m-0 text-sm font-mono font-medium text-vscode-descriptionForeground">
{worktree.path}
</span>
</p>
</div>
{/* Warning message */}
<div className="flex items-start gap-2 p-2 rounded bg-vscode-inputValidation-warningBackground border border-vscode-inputValidation-warningBorder text-sm">
<span className="codicon codicon-warning text-vscode-charts-yellow flex-shrink-0 mt-0.5" />
<div className="min-w-0">
<p className="text-vscode-foreground m-0">{t("worktrees:deleteWarning")}</p>
<ul className="mt-1 mb-0 pl-0 list-none space-y-0.5 text-vscode-descriptionForeground">
<li>• {t("worktrees:deleteWarningBranch", { branch: worktree.branch || "HEAD" })}</li>
<div className="flex items-start gap-2 px-5 py-2">
<TriangleAlert className="size-4 text-vscode-charts-yellow flex-shrink-0" />
<div className="flex flex-col min-w-0 gap-2">
<p className="m-0 text-vscode-foreground">{t("worktrees:deleteWarning")}</p>
<ul className="m-0 pl-0 list-none space-y-1 text-vscode-descriptionForeground">
<li>• {t("worktrees:deleteWarningBranch")}</li>
<li>• {t("worktrees:deleteWarningFiles")}</li>
</ul>
<p className="m-0 text-vscode-descriptionForeground">{t("worktrees:deleteNoticeLarge")}</p>
</div>
</div>

View file

@ -2,21 +2,20 @@ import { useState, useEffect, useCallback } from "react"
import type { Worktree, WorktreeListResponse, WorktreeIncludeStatus } from "@roo-code/types"
import { Badge, Button, StandardTooltip } from "@/components/ui"
import { Badge, Button, StandardTooltip, ToggleSwitch } from "@/components/ui"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { vscode } from "@/utils/vscode"
import { Tab, TabContent, TabHeader } from "../common/Tab"
import { SectionHeader } from "../settings/SectionHeader"
import { CreateWorktreeModal } from "./CreateWorktreeModal"
import { DeleteWorktreeModal } from "./DeleteWorktreeModal"
import { Folder, GitBranch, Lock, Plus, SquareArrowOutUpRight, Trash } from "lucide-react"
type WorktreesViewProps = {
onDone: () => void
}
export const WorktreesView = ({ onDone }: WorktreesViewProps) => {
export const WorktreesView = () => {
const { t } = useAppTranslation()
const { showWorktreesInHomeScreen, setShowWorktreesInHomeScreen } = useExtensionState()
// State
const [worktrees, setWorktrees] = useState<Worktree[]>([])
@ -123,87 +122,178 @@ export const WorktreesView = ({ onDone }: WorktreesViewProps) => {
})
}, [])
// Handle toggle show in home screen
const handleToggleShowInHomeScreen = useCallback(() => {
const newValue = !showWorktreesInHomeScreen
setShowWorktreesInHomeScreen(newValue)
vscode.postMessage({
type: "updateSettings",
updatedSettings: { showWorktreesInHomeScreen: newValue },
})
}, [showWorktreesInHomeScreen, setShowWorktreesInHomeScreen])
// Render error states
if (!isGitRepo) {
return (
<Tab>
<TabHeader className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">{t("worktrees:title")}</h3>
<Button onClick={onDone}>{t("worktrees:done")}</Button>
</TabHeader>
<TabContent>
<div className="flex flex-col items-center justify-center h-48 text-vscode-descriptionForeground">
<span className="codicon codicon-warning text-4xl mb-4" />
<p className="text-center">{t("worktrees:notGitRepo")}</p>
</div>
</TabContent>
</Tab>
<div>
<SectionHeader>{t("worktrees:title")}</SectionHeader>
<div className="px-5 text-sm">
<p className="text-vscode-descriptionForeground">{t("worktrees:description")}</p>
<p>{t("worktrees:notGitRepo")}</p>
</div>
</div>
)
}
if (isMultiRoot) {
return (
<Tab>
<TabHeader className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">{t("worktrees:title")}</h3>
<Button onClick={onDone}>{t("worktrees:done")}</Button>
</TabHeader>
<TabContent>
<div className="flex flex-col items-center justify-center h-48 text-vscode-descriptionForeground">
<span className="codicon codicon-warning text-4xl mb-4" />
<p className="text-center">{t("worktrees:multiRootNotSupported")}</p>
</div>
</TabContent>
</Tab>
<div>
<SectionHeader>{t("worktrees:title")}</SectionHeader>
<div className="px-5 text-sm">
<p className="text-vscode-descriptionForeground">{t("worktrees:description")}</p>
<p>{t("worktrees:multiRootNotSupported")}</p>
</div>
</div>
)
}
if (isSubfolder) {
return (
<Tab>
<TabHeader className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">{t("worktrees:title")}</h3>
<Button onClick={onDone}>{t("worktrees:done")}</Button>
</TabHeader>
<TabContent>
<div className="flex flex-col items-center justify-center h-48 text-vscode-descriptionForeground">
<span className="codicon codicon-warning text-4xl mb-4" />
<p className="text-center">{t("worktrees:subfolderNotSupported")}</p>
<p className="text-sm mt-2 text-center">
{t("worktrees:gitRoot")}:{" "}
<code className="bg-vscode-input-background px-2 py-1 rounded">{gitRootPath}</code>
</p>
</div>
</TabContent>
</Tab>
<div>
<SectionHeader>{t("worktrees:title")}</SectionHeader>
<div className="px-5 text-sm">
<p className="text-vscode-descriptionForeground">{t("worktrees:description")}</p>
<p>{t("worktrees:subfolderNotSupported")}</p>
<p>
{t("worktrees:gitRoot")}:{" "}
<code className="bg-vscode-input-background p-1 rounded-md">{gitRootPath}</code>
</p>
</div>
</div>
)
}
return (
<Tab>
<TabHeader className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<h3 className="text-vscode-foreground m-0">{t("worktrees:title")}</h3>
<Button onClick={onDone}>{t("worktrees:done")}</Button>
</div>
<p className="text-vscode-descriptionForeground text-sm m-0">{t("worktrees:description")}</p>
<div className="flex flex-col h-full overflow-hidden">
{/* Fixed Header */}
<div className="flex-shrink-0">
<SectionHeader>{t("worktrees:title")}</SectionHeader>
<div className="flex flex-col gap-2 px-5 py-2">
<p className="text-vscode-descriptionForeground text-sm m-0">{t("worktrees:description")}</p>
{/* Show in Home Screen toggle */}
<label
className="flex cursor-pointer items-center gap-2 text-sm text-vscode-descriptionForeground"
onClick={handleToggleShowInHomeScreen}>
<ToggleSwitch checked={showWorktreesInHomeScreen} onChange={handleToggleShowInHomeScreen} />
<span>{t("worktrees:showInHomeScreen")}</span>
</label>
{/* New Worktree button */}
<Button variant="secondary" className="py-1" onClick={() => setShowCreateModal(true)}>
<Plus />
{t("worktrees:newWorktree")}
</Button>
</div>
</div>
{/* Scrollable List Area */}
<div className="flex-1 overflow-y-auto px-4 py-2 min-h-0">
{isLoading ? (
<div className="flex items-center justify-center h-48">
<span className="codicon codicon-loading codicon-modifier-spin text-2xl" />
</div>
) : error ? (
<div className="flex flex-col items-center justify-center h-48 text-vscode-errorForeground">
<span className="codicon codicon-error text-4xl mb-4" />
<p className="text-center">{error}</p>
</div>
) : (
<div className="flex flex-col gap-1">
{worktrees.map((worktree) => (
<div
key={worktree.path}
className={`p-2.5 px-3.5 rounded-xl hover:bg-vscode-list-hoverBackground border border-transparent ${
worktree.isCurrent
? " bg-vscode-list-activeSelectionBackground border-vscode-list-activeSelectionForeground/20"
: "cursor-pointer"
}`}
onClick={
worktree.isCurrent ? undefined : () => handleSwitchWorktree(worktree.path, false)
}>
<div className="flex items-start min-[400px]:items-center justify-between gap-2 flex-col min-[400px]:flex-row overflow-hidden">
<div className={`flex-1 min-w-0 ${worktree.isCurrent && "cursor-default"}`}>
{/* Info */}
<div className="flex items-center gap-2 overflow-hidden">
<GitBranch className="size-3 shrink-0" />
<span className="font-medium truncate">
{worktree.branch ||
(worktree.isDetached
? t("worktrees:detachedHead")
: t("worktrees:noBranch"))}
</span>
{worktree.isBare && (
<Badge className="text-[0.7em] -mt-0.25 py-0.5">
{t("worktrees:primary")}
</Badge>
)}
{worktree.isLocked && (
<StandardTooltip content={worktree.lockReason || t("worktrees:locked")}>
<Lock className="text-vscode-charts-yellow" />
</StandardTooltip>
)}
</div>
<div className="flex gap-2 text-xs text-vscode-descriptionForeground mt-1">
<Folder className="size-3 shrink-0 mt-0.5" />
<span className="truncate">{worktree.path}</span>
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-1 ml-3 min-[400px]:ml-0 flex-shrink-0">
<StandardTooltip content={t("worktrees:openInNewWindow")}>
<Button
variant="ghost"
size="icon"
disabled={worktree.isCurrent}
onClick={(e) => {
e.stopPropagation()
handleSwitchWorktree(worktree.path, true)
}}>
<SquareArrowOutUpRight />
</Button>
</StandardTooltip>
<StandardTooltip content={t("worktrees:delete")}>
<Button
variant="ghost"
size="icon"
disabled={worktree.isCurrent || worktree.isBare}
onClick={(e) => {
e.stopPropagation()
setDeleteWorktree(worktree)
}}>
<Trash className="text-destructive" />
</Button>
</StandardTooltip>
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Fixed Footer - Settings */}
<div className="flex-shrink-0 flex flex-col border-t border-vscode-sideBar-background">
{/* Worktree include status */}
{includeStatus && (
<div className="flex items-center gap-2 text-sm">
<div className="flex items-center gap-2 text-sm px-5 py-3 justify-between text-vscode-descriptionForeground border-t border-vscode-sideBar-background">
{includeStatus.exists ? (
<>
<span className="codicon codicon-check text-vscode-charts-green" />
<span className="text-vscode-descriptionForeground">
{t("worktrees:includeFileExists")}
</span>
</>
<span>{t("worktrees:includeFileExists")}</span>
) : (
<>
<span className="codicon codicon-warning text-vscode-charts-yellow" />
<span className="text-vscode-descriptionForeground">
{t("worktrees:noIncludeFile")}
</span>
<span>{t("worktrees:noIncludeFile")}</span>
{includeStatus.hasGitignore && (
<Button
variant="secondary"
@ -217,97 +307,7 @@ export const WorktreesView = ({ onDone }: WorktreesViewProps) => {
)}
</div>
)}
</TabHeader>
<TabContent className="px-2 py-0">
{isLoading ? (
<div className="flex items-center justify-center h-48">
<span className="codicon codicon-loading codicon-modifier-spin text-2xl" />
</div>
) : error ? (
<div className="flex flex-col items-center justify-center h-48 text-vscode-errorForeground">
<span className="codicon codicon-error text-4xl mb-4" />
<p className="text-center">{error}</p>
</div>
) : (
<div className="flex flex-col gap-1 py-2">
{worktrees.map((worktree) => (
<div
key={worktree.path}
className={`p-3 rounded-xl border transition-colors ${
worktree.isCurrent
? "border-vscode-focusBorder bg-vscode-list-activeSelectionBackground"
: "border-transparent bg-vscode-editor-background hover:bg-vscode-editor-foreground/10"
}`}>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="codicon codicon-git-branch" />
<span className="font-medium truncate">
{worktree.branch ||
(worktree.isDetached
? t("worktrees:detachedHead")
: t("worktrees:noBranch"))}
</span>
{worktree.isBare && <Badge>{t("worktrees:primary")}</Badge>}
{worktree.isCurrent && (
<Badge variant="secondary">{t("worktrees:current")}</Badge>
)}
{worktree.isLocked && (
<StandardTooltip content={worktree.lockReason || t("worktrees:locked")}>
<span className="codicon codicon-lock text-vscode-charts-yellow" />
</StandardTooltip>
)}
</div>
<div className="text-xs text-vscode-descriptionForeground mt-1 truncate">
{worktree.path}
</div>
</div>
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
{!worktree.isCurrent && (
<>
<StandardTooltip content={t("worktrees:openInCurrentWindow")}>
<Button
variant="ghost"
size="sm"
onClick={() => handleSwitchWorktree(worktree.path, false)}>
<span className="codicon codicon-window" />
</Button>
</StandardTooltip>
<StandardTooltip content={t("worktrees:openInNewWindow")}>
<Button
variant="ghost"
size="sm"
onClick={() => handleSwitchWorktree(worktree.path, true)}>
<span className="codicon codicon-empty-window" />
</Button>
</StandardTooltip>
</>
)}
{!worktree.isBare && !worktree.isCurrent && (
<StandardTooltip content={t("worktrees:delete")}>
<Button
variant="ghost"
size="sm"
onClick={() => setDeleteWorktree(worktree)}>
<span className="codicon codicon-trash text-vscode-errorForeground" />
</Button>
</StandardTooltip>
)}
</div>
</div>
</div>
))}
{/* New Worktree button */}
<Button variant="secondary" className="mt-2" onClick={() => setShowCreateModal(true)}>
<span className="codicon codicon-add mr-2" />
{t("worktrees:newWorktree")}
</Button>
</div>
)}
</TabContent>
</div>
{/* Create Modal */}
{showCreateModal && (
@ -333,6 +333,6 @@ export const WorktreesView = ({ onDone }: WorktreesViewProps) => {
}}
/>
)}
</Tab>
</div>
)
}

View file

@ -1,3 +0,0 @@
export { WorktreesView } from "./WorktreesView"
export { CreateWorktreeModal } from "./CreateWorktreeModal"
export { DeleteWorktreeModal } from "./DeleteWorktreeModal"

View file

@ -161,6 +161,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setIncludeCurrentTime: (value: boolean) => void
includeCurrentCost?: boolean
setIncludeCurrentCost: (value: boolean) => void
showWorktreesInHomeScreen: boolean
setShowWorktreesInHomeScreen: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -621,6 +623,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setIncludeCurrentTime,
includeCurrentCost,
setIncludeCurrentCost,
showWorktreesInHomeScreen: state.showWorktreesInHomeScreen ?? true,
setShowWorktreesInHomeScreen: (value) =>
setState((prevState) => ({ ...prevState, showWorktreesInHomeScreen: value })),
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>

View file

@ -29,6 +29,7 @@
"providers": "Proveïdors",
"modes": "Modes",
"mcp": "Servidors MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-aprovació",
"browser": "Accés a l'ordinador",
"checkpoints": "Punts de control",

View file

@ -18,13 +18,12 @@
"detachedHead": "HEAD separat",
"noBranch": "Sense branca",
"openInCurrentWindow": "Obre a la finestra actual",
"switchInThisWindow": "Canvia a worktree",
"openInNewWindow": "Obre en una finestra nova",
"delete": "Suprimeix",
"newWorktree": "Worktree nou",
"createWorktree": "Crea worktree",
"createWorktreeDescription": "Crea un worktree nou per treballar en una branca diferent dins del seu propi directori.",
"branchName": "Nom de la branca",
"createNewBranch": "Crea una branca nova",
"checkoutExisting": "Fes checkout d'una branca existent",
@ -35,8 +34,9 @@
"noBranchFound": "No s'ha trobat cap branca",
"localBranches": "Branques locals",
"remoteBranches": "Branques remotes",
"worktreePath": "Camí del worktree",
"pathHint": "El camí on es crearà el worktree",
"worktreePath": "En",
"selectWorktreeLocation": "Selecciona la ubicació del Worktree",
"selectFolderForWorktree": "Selecciona carpeta per al worktree nou",
"noIncludeFileWarning": "No hi ha cap fitxer .worktreeinclude",
"noIncludeFileHint": "Sense un fitxer .worktreeinclude, fitxers com node_modules no es copiaran al worktree nou. Potser hauràs d'executar npm install després de crear-lo.",
"create": "Crea",
@ -46,13 +46,24 @@
"cancel": "Cancel·la",
"deleteWorktree": "Suprimeix worktree",
"deleteWorktreeDescription": "Això eliminarà el worktree i tots els seus fitxers.",
"deleteWarning": "Aquesta acció no es pot desfer. S'eliminarà:",
"deleteWarningBranch": "La branca '{{branch}}' i tots els canvis sense confirmar",
"deleteWarningBranch": "La branca i qualsevol canvi sense confirmar",
"deleteWarningFiles": "Tots els fitxers del directori del worktree",
"deleteNoticeLarge": "Si és un gran repositori, podria trigar una mica.",
"forceDelete": "Força la supressió",
"worktreeIsLocked": "el worktree està bloquejat",
"deleting": "S'està suprimint...",
"close": "Tanca"
"close": "Tanca",
"showInHomeScreen": "Mostra worktrees a la pantalla d'inici",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Canvia entre worktrees",
"description": "Els worktrees de Git et permeten treballar en diverses branques simultàniament. Fes clic per canviar l'espai de treball.",
"settings": "Gestiona Worktrees",
"info": "Els worktrees de Git et permeten tenir diversos directoris de treball adjunts al mateix repositori."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Anbieter",
"modes": "Modi",
"mcp": "MCP-Server",
"worktrees": "Worktrees",
"autoApprove": "Auto-Genehmigung",
"browser": "Computerzugriff",
"checkpoints": "Kontrollpunkte",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Kein Branch",
"openInCurrentWindow": "Im aktuellen Fenster öffnen",
"switchInThisWindow": "Zu Worktree wechseln",
"openInNewWindow": "In neuem Fenster öffnen",
"delete": "Löschen",
"newWorktree": "Neuer Worktree",
"createWorktree": "Worktree erstellen",
"createWorktreeDescription": "Erstelle einen neuen Worktree, um in einem eigenen Verzeichnis an einem separaten Branch zu arbeiten.",
"branchName": "Branch-Name",
"createNewBranch": "Neuen Branch erstellen",
"checkoutExisting": "Vorhandenen Branch auschecken",
@ -35,8 +34,9 @@
"noBranchFound": "Kein Branch gefunden",
"localBranches": "Lokale Branches",
"remoteBranches": "Remote-Branches",
"worktreePath": "Worktree-Pfad",
"pathHint": "Der Pfad, in dem der Worktree erstellt wird",
"worktreePath": "In",
"selectWorktreeLocation": "Worktree-Speicherort auswählen",
"selectFolderForWorktree": "Ordner für neuen Worktree auswählen",
"noIncludeFileWarning": "Keine .worktreeinclude-Datei",
"noIncludeFileHint": "Ohne eine .worktreeinclude-Datei werden Dateien wie node_modules nicht in den neuen Worktree kopiert. Möglicherweise musst du nach dem Erstellen npm install ausführen.",
"create": "Erstellen",
@ -46,13 +46,24 @@
"cancel": "Abbrechen",
"deleteWorktree": "Worktree löschen",
"deleteWorktreeDescription": "Dadurch wird der Worktree und alle seine Dateien entfernt.",
"deleteWarning": "Diese Aktion kann nicht rückgängig gemacht werden. Folgendes wird gelöscht:",
"deleteWarningBranch": "Der Branch '{{branch}}' und alle nicht committeten Änderungen",
"deleteWarningBranch": "Der Branch und alle nicht committeten Änderungen",
"deleteWarningFiles": "Alle Dateien im Worktree-Verzeichnis",
"deleteNoticeLarge": "Bei großen Repositorys kann dies eine Weile dauern.",
"forceDelete": "Löschen erzwingen",
"worktreeIsLocked": "Worktree ist gesperrt",
"deleting": "Wird gelöscht...",
"close": "Schließen"
"close": "Schließen",
"showInHomeScreen": "Worktrees auf dem Startbildschirm anzeigen",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Zwischen Worktrees wechseln",
"description": "Git Worktrees ermöglichen dir, gleichzeitig an mehreren Branches zu arbeiten. Klicke hier, um den Workspace zu wechseln.",
"settings": "Worktrees verwalten",
"info": "Git Worktrees ermöglichen dir, mehrere Arbeitsverzeichnisse mit demselben Repository zu verbinden."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Providers",
"modes": "Modes",
"mcp": "MCP Servers",
"worktrees": "Worktrees",
"autoApprove": "Auto-Approve",
"browser": "Browser",
"checkpoints": "Checkpoints",

View file

@ -1,14 +1,14 @@
{
"title": "Worktrees",
"done": "Done",
"description": "Git worktrees allow you to work on multiple branches simultaneously in separate directories. Each worktree gets its own VS Code window with Roo Code.",
"description": "Git worktrees are great for agentic coding, because they allow you to work on multiple branches of a repo at the same time, by keeping them in different directories. Each worktree gets its own VS Code window with Roo Code.",
"notGitRepo": "This workspace is not a Git repository. Worktrees require a Git repository to function.",
"notGitRepo": "This workspace is not a Git repository. Open a workspace with a Git repository to set up worktrees.",
"multiRootNotSupported": "Worktrees are not supported in multi-root workspaces. Please open a single folder to use worktrees.",
"subfolderNotSupported": "This workspace is a subfolder of a Git repository. Please open the repository root to use worktrees.",
"gitRoot": "Git root",
"includeFileExists": ".worktreeinclude file found - files will be copied to new worktrees",
"includeFileExists": ".worktreeinclude file found. Files will be copied to new worktrees.",
"noIncludeFile": "No .worktreeinclude file found",
"createFromGitignore": "Create from .gitignore",
@ -18,27 +18,27 @@
"detachedHead": "Detached HEAD",
"noBranch": "No branch",
"openInCurrentWindow": "Open in current window",
"switchInThisWindow": "Switch to worktree",
"openInNewWindow": "Open in new window",
"delete": "Delete",
"newWorktree": "New Worktree",
"newWorktree": "Create worktree",
"createWorktree": "Create Worktree",
"createWorktreeDescription": "Create a new worktree to work on a separate branch in its own directory.",
"branchName": "Branch name",
"branchName": "New branch",
"createNewBranch": "Create new branch",
"checkoutExisting": "Checkout existing branch",
"baseBranch": "Base branch",
"baseBranch": "From base branch",
"loadingBranches": "Loading branches...",
"selectBranch": "Select branch",
"searchBranch": "Search branches...",
"noBranchFound": "No branch found",
"localBranches": "Local branches",
"remoteBranches": "Remote branches",
"worktreePath": "Worktree path",
"pathHint": "The path where the worktree will be created",
"worktreePath": "In",
"selectWorktreeLocation": "Select Worktree Location",
"selectFolderForWorktree": "Select folder for new worktree",
"noIncludeFileWarning": "No .worktreeinclude file",
"noIncludeFileHint": "Without a .worktreeinclude file, files like node_modules won't be copied to the new worktree. You may need to run npm install after creating it.",
"noIncludeFileHint": "Worktrees are created with just the files from their branch. If you want to copy over local files like .env and node_modules, include them in a .worktreeinclude file at the root of the repository.",
"create": "Create",
"creating": "Creating...",
"copyingFiles": "Copying files...",
@ -46,13 +46,24 @@
"cancel": "Cancel",
"deleteWorktree": "Delete Worktree",
"deleteWorktreeDescription": "This will remove the worktree and all its files.",
"deleteWarning": "This action cannot be undone. The following will be deleted:",
"deleteWarningBranch": "The branch '{{branch}}' and all uncommitted changes",
"deleteWarning": "This can't be undone. It will delete:",
"deleteWarningBranch": "The branch and any uncommitted changes",
"deleteWarningFiles": "All files in the worktree directory",
"deleteNoticeLarge": "If this is a large repository, it may take a little while.",
"forceDelete": "Force delete",
"worktreeIsLocked": "worktree is locked",
"deleting": "Deleting...",
"close": "Close"
"close": "Close",
"showInHomeScreen": "Show worktrees in home screen",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Switch between worktrees",
"description": "Git worktrees let you work on multiple branches simultaneously. Click to switch workspace.",
"settings": "Manage Worktrees",
"info": "Git worktrees allow you to have multiple working directories attached to the same repository."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Proveedores",
"modes": "Modos",
"mcp": "Servidores MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-aprobación",
"browser": "Acceso al ordenador",
"checkpoints": "Puntos de control",

View file

@ -18,13 +18,12 @@
"detachedHead": "HEAD separado",
"noBranch": "Sin rama",
"openInCurrentWindow": "Abrir en la ventana actual",
"switchInThisWindow": "Cambiar a worktree",
"openInNewWindow": "Abrir en una ventana nueva",
"delete": "Eliminar",
"newWorktree": "Nuevo Worktree",
"createWorktree": "Crear Worktree",
"createWorktreeDescription": "Crea un worktree nuevo para trabajar en una rama separada en su propio directorio.",
"branchName": "Nombre de la rama",
"createNewBranch": "Crear rama nueva",
"checkoutExisting": "Hacer checkout de una rama existente",
@ -35,8 +34,9 @@
"noBranchFound": "No se encontró ninguna rama",
"localBranches": "Ramas locales",
"remoteBranches": "Ramas remotas",
"worktreePath": "Ruta del worktree",
"pathHint": "La ruta donde se creará el worktree",
"worktreePath": "En",
"selectWorktreeLocation": "Seleccionar ubicación del Worktree",
"selectFolderForWorktree": "Seleccionar carpeta para el worktree nuevo",
"noIncludeFileWarning": "No hay archivo .worktreeinclude",
"noIncludeFileHint": "Sin un archivo .worktreeinclude, archivos como node_modules no se copiarán al worktree nuevo. Puede que tengas que ejecutar npm install después de crearlo.",
"create": "Crear",
@ -46,13 +46,24 @@
"cancel": "Cancelar",
"deleteWorktree": "Eliminar Worktree",
"deleteWorktreeDescription": "Esto eliminará el worktree y todos sus archivos.",
"deleteWarning": "Esta acción no se puede deshacer. Se eliminará lo siguiente:",
"deleteWarningBranch": "La rama '{{branch}}' y todos los cambios sin confirmar",
"deleteWarningBranch": "La rama y todos los cambios sin confirmar",
"deleteWarningFiles": "Todos los archivos del directorio del worktree",
"deleteNoticeLarge": "Si es un repositorio grande, podría tomar un tiempo.",
"forceDelete": "Forzar eliminación",
"worktreeIsLocked": "el worktree está bloqueado",
"deleting": "Eliminando...",
"close": "Cerrar"
"close": "Cerrar",
"showInHomeScreen": "Mostrar worktrees en la pantalla de inicio",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Cambiar entre worktrees",
"description": "Los worktrees de Git te permiten trabajar en varias ramas simultáneamente. Haz clic para cambiar el espacio de trabajo.",
"settings": "Gestionar Worktrees",
"info": "Los worktrees de Git te permiten tener varios directorios de trabajo adjuntos al mismo repositorio."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Fournisseurs",
"modes": "Modes",
"mcp": "Serveurs MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-approbation",
"browser": "Accès ordinateur",
"checkpoints": "Points de contrôle",

View file

@ -18,13 +18,12 @@
"detachedHead": "HEAD détachée",
"noBranch": "Aucune branche",
"openInCurrentWindow": "Ouvrir dans la fenêtre actuelle",
"switchInThisWindow": "Changer vers worktree",
"openInNewWindow": "Ouvrir dans une nouvelle fenêtre",
"delete": "Supprimer",
"newWorktree": "Nouveau Worktree",
"createWorktree": "Créer un worktree",
"createWorktreeDescription": "Crée un worktree pour travailler sur une autre branche dans son propre répertoire.",
"branchName": "Nom de la branche",
"createNewBranch": "Créer une nouvelle branche",
"checkoutExisting": "Checkout d'une branche existante",
@ -35,8 +34,9 @@
"noBranchFound": "Aucune branche trouvée",
"localBranches": "Branches locales",
"remoteBranches": "Branches distantes",
"worktreePath": "Chemin du worktree",
"pathHint": "Le chemin où le worktree sera créé",
"worktreePath": "En",
"selectWorktreeLocation": "Sélectionner l'emplacement du Worktree",
"selectFolderForWorktree": "Sélectionner le dossier pour le nouveau worktree",
"noIncludeFileWarning": "Aucun fichier .worktreeinclude",
"noIncludeFileHint": "Sans fichier .worktreeinclude, des fichiers comme node_modules ne seront pas copiés dans le nouveau worktree. Tu devras peut-être exécuter npm install après l'avoir créé.",
"create": "Créer",
@ -46,13 +46,24 @@
"cancel": "Annuler",
"deleteWorktree": "Supprimer le worktree",
"deleteWorktreeDescription": "Cela supprimera le worktree et tous ses fichiers.",
"deleteWarning": "Cette action est irréversible. Les éléments suivants seront supprimés :",
"deleteWarningBranch": "La branche '{{branch}}' et toutes les modifications non validées",
"deleteWarningBranch": "La branche et toutes les modifications non validées",
"deleteWarningFiles": "Tous les fichiers dans le répertoire du worktree",
"deleteNoticeLarge": "Si c'est un grand dépôt, cela peut prendre un peu de temps.",
"forceDelete": "Forcer la suppression",
"worktreeIsLocked": "le worktree est verrouillé",
"deleting": "Suppression...",
"close": "Fermer"
"close": "Fermer",
"showInHomeScreen": "Afficher les worktrees sur l'écran d'accueil",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Changer entre les worktrees",
"description": "Les worktrees Git te permettent de travailler sur plusieurs branches en même temps. Clique pour changer d'espace de travail.",
"settings": "Gérer les Worktrees",
"info": "Les worktrees Git te permettent d'avoir plusieurs répertoires de travail attachés au même dépôt."
}
}

View file

@ -29,6 +29,7 @@
"providers": "प्रदाता",
"modes": "मोड",
"mcp": "एमसीपी सर्वर",
"worktrees": "Worktrees",
"autoApprove": "अनुमोदन",
"browser": "ब्राउज़र",
"checkpoints": "चेकपॉइंट",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "कोई ब्रांच नहीं",
"openInCurrentWindow": "वर्तमान विंडो में खोलें",
"switchInThisWindow": "Worktree पर स्विच करें",
"openInNewWindow": "नई विंडो में खोलें",
"delete": "हटाएँ",
"newWorktree": "नया Worktree",
"createWorktree": "Worktree बनाएँ",
"createWorktreeDescription": "अपने अलग डायरेक्टरी में किसी अलग ब्रांच पर काम करने के लिए नया worktree बनाएँ।",
"branchName": "ब्रांच नाम",
"createNewBranch": "नई ब्रांच बनाएँ",
"checkoutExisting": "मौजूदा ब्रांच checkout करें",
@ -35,8 +34,9 @@
"noBranchFound": "कोई ब्रांच नहीं मिली",
"localBranches": "लोकल ब्रांच",
"remoteBranches": "रिमोट ब्रांच",
"worktreePath": "Worktree पाथ",
"pathHint": "वह पाथ जहाँ worktree बनाया जाएगा",
"worktreePath": "में",
"selectWorktreeLocation": "Worktree स्थान चुनें",
"selectFolderForWorktree": "नए worktree के लिए फ़ोल्डर चुनें",
"noIncludeFileWarning": ".worktreeinclude फ़ाइल नहीं है",
"noIncludeFileHint": ".worktreeinclude फ़ाइल के बिना, node_modules जैसी फ़ाइलें नए worktree में कॉपी नहीं होंगी। इसे बनाने के बाद आपको npm install चलाना पड़ सकता है।",
"create": "बनाएँ",
@ -46,13 +46,24 @@
"cancel": "रद्द करें",
"deleteWorktree": "Worktree हटाएँ",
"deleteWorktreeDescription": "यह worktree और उसकी सभी फ़ाइलें हटा देगा।",
"deleteWarning": "यह कार्रवाई वापस नहीं ली जा सकती। निम्नलिखित हटाया जाएगा:",
"deleteWarningBranch": "ब्रांच '{{branch}}' और सभी uncommitted बदलाव",
"deleteWarningBranch": "ब्रांच और सभी uncommitted बदलाव",
"deleteWarningFiles": "worktree डायरेक्टरी की सभी फ़ाइलें",
"deleteNoticeLarge": "अगर यह एक बड़ी रिपोज़िटरी है तो यह कुछ समय ले सकता है।",
"forceDelete": "जबरन हटाएँ",
"worktreeIsLocked": "worktree लॉक्ड है",
"deleting": "हटाया जा रहा है...",
"close": "बंद करें"
"close": "बंद करें",
"showInHomeScreen": "होम स्क्रीन में worktrees दिखाएँ",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Worktrees के बीच स्विच करें",
"description": "Git worktrees आपको एक साथ कई ब्रांच पर काम करने देते हैं। workspace स्विच करने के लिए क्लिक करें।",
"settings": "Worktrees को प्रबंधित करें",
"info": "Git worktrees आपको एक ही रिपोज़िटरी से जुड़ी कई working directories रखने देते हैं।"
}
}

View file

@ -29,6 +29,7 @@
"providers": "Provider",
"modes": "Mode",
"mcp": "Server MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-Approve",
"browser": "Browser",
"checkpoints": "Checkpoint",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Tidak ada branch",
"openInCurrentWindow": "Buka di jendela saat ini",
"switchInThisWindow": "Beralih ke worktree",
"openInNewWindow": "Buka di jendela baru",
"delete": "Hapus",
"newWorktree": "Worktree Baru",
"createWorktree": "Buat Worktree",
"createWorktreeDescription": "Buat worktree baru untuk bekerja pada branch terpisah di direktori sendiri.",
"branchName": "Nama branch",
"createNewBranch": "Buat branch baru",
"checkoutExisting": "Checkout branch yang sudah ada",
@ -35,8 +34,9 @@
"noBranchFound": "Branch tidak ditemukan",
"localBranches": "Branch lokal",
"remoteBranches": "Branch remote",
"worktreePath": "Path worktree",
"pathHint": "Path tempat worktree akan dibuat",
"worktreePath": "Di",
"selectWorktreeLocation": "Pilih Lokasi Worktree",
"selectFolderForWorktree": "Pilih folder untuk worktree baru",
"noIncludeFileWarning": "Tidak ada file .worktreeinclude",
"noIncludeFileHint": "Tanpa file .worktreeinclude, file seperti node_modules tidak akan disalin ke worktree baru. Kamu mungkin perlu menjalankan npm install setelah membuatnya.",
"create": "Buat",
@ -46,13 +46,24 @@
"cancel": "Batal",
"deleteWorktree": "Hapus Worktree",
"deleteWorktreeDescription": "Ini akan menghapus worktree dan semua filenya.",
"deleteWarning": "Tindakan ini tidak dapat dibatalkan. Yang berikut akan dihapus:",
"deleteWarningBranch": "Branch '{{branch}}' dan semua perubahan yang belum di-commit",
"deleteWarningBranch": "Branch dan semua perubahan yang belum di-commit",
"deleteWarningFiles": "Semua file di direktori worktree",
"deleteNoticeLarge": "Jika ini adalah repositori besar, mungkin memerlukan waktu beberapa saat.",
"forceDelete": "Paksa hapus",
"worktreeIsLocked": "worktree terkunci",
"deleting": "Menghapus...",
"close": "Tutup"
"close": "Tutup",
"showInHomeScreen": "Tampilkan worktrees di layar beranda",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Beralih antar worktrees",
"description": "Git worktrees memungkinkan kamu bekerja di beberapa branch secara bersamaan. Klik untuk beralih workspace.",
"settings": "Kelola Worktrees",
"info": "Git worktrees memungkinkan kamu memiliki beberapa direktori kerja yang terhubung ke repositori yang sama."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Fornitori",
"modes": "Modalità",
"mcp": "Server MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-approvazione",
"browser": "Accesso computer",
"checkpoints": "Punti di controllo",

View file

@ -18,13 +18,12 @@
"detachedHead": "HEAD staccata",
"noBranch": "Nessun branch",
"openInCurrentWindow": "Apri nella finestra corrente",
"switchInThisWindow": "Passa a worktree",
"openInNewWindow": "Apri in una nuova finestra",
"delete": "Elimina",
"newWorktree": "Nuovo Worktree",
"createWorktree": "Crea Worktree",
"createWorktreeDescription": "Crea un nuovo worktree per lavorare su un branch separato nella sua directory.",
"branchName": "Nome del branch",
"createNewBranch": "Crea nuovo branch",
"checkoutExisting": "Fai checkout di un branch esistente",
@ -35,8 +34,9 @@
"noBranchFound": "Nessun branch trovato",
"localBranches": "Branch locali",
"remoteBranches": "Branch remoti",
"worktreePath": "Percorso del worktree",
"pathHint": "Il percorso in cui verrà creato il worktree",
"worktreePath": "In",
"selectWorktreeLocation": "Seleziona posizione Worktree",
"selectFolderForWorktree": "Seleziona cartella per il nuovo worktree",
"noIncludeFileWarning": "Nessun file .worktreeinclude",
"noIncludeFileHint": "Senza un file .worktreeinclude, file come node_modules non verranno copiati nel nuovo worktree. Potresti dover eseguire npm install dopo averlo creato.",
"create": "Crea",
@ -46,13 +46,24 @@
"cancel": "Annulla",
"deleteWorktree": "Elimina Worktree",
"deleteWorktreeDescription": "Questo rimuoverà il worktree e tutti i suoi file.",
"deleteWarning": "Questa azione non può essere annullata. Verrà eliminato quanto segue:",
"deleteWarningBranch": "Il branch '{{branch}}' e tutte le modifiche non committate",
"deleteWarningBranch": "Il branch e tutte le modifiche non committate",
"deleteWarningFiles": "Tutti i file nella directory del worktree",
"deleteNoticeLarge": "Se è un repository grande, potrebbe volerci un po'.",
"forceDelete": "Forza eliminazione",
"worktreeIsLocked": "il worktree è bloccato",
"deleting": "Eliminazione...",
"close": "Chiudi"
"close": "Chiudi",
"showInHomeScreen": "Mostra worktrees nella schermata iniziale",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Passa tra i worktrees",
"description": "I worktree di Git ti permettono di lavorare su più branch contemporaneamente. Fai clic per cambiare workspace.",
"settings": "Gestisci Worktrees",
"info": "I worktree di Git ti permettono di avere più directory di lavoro collegate allo stesso repository."
}
}

View file

@ -29,6 +29,7 @@
"providers": "プロバイダー",
"modes": "モード",
"mcp": "MCPサーバー",
"worktrees": "Worktrees",
"autoApprove": "自動承認",
"browser": "コンピューターアクセス",
"checkpoints": "チェックポイント",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "ブランチなし",
"openInCurrentWindow": "現在のウィンドウで開く",
"switchInThisWindow": "Worktree に切り替え",
"openInNewWindow": "新しいウィンドウで開く",
"delete": "削除",
"newWorktree": "新しい Worktree",
"createWorktree": "Worktree を作成",
"createWorktreeDescription": "別のブランチで作業するために、専用ディレクトリに新しい worktree を作成します。",
"branchName": "ブランチ名",
"createNewBranch": "新しいブランチを作成",
"checkoutExisting": "既存のブランチをチェックアウト",
@ -35,8 +34,9 @@
"noBranchFound": "ブランチが見つかりません",
"localBranches": "ローカルブランチ",
"remoteBranches": "リモートブランチ",
"worktreePath": "Worktree のパス",
"pathHint": "worktree を作成するパス",
"worktreePath": "In",
"selectWorktreeLocation": "Worktree の場所を選択",
"selectFolderForWorktree": "新しい worktree のフォルダーを選択",
"noIncludeFileWarning": ".worktreeinclude ファイルなし",
"noIncludeFileHint": ".worktreeinclude ファイルがない場合、node_modules などのファイルは新しい worktree にコピーされません。作成後に npm install を実行する必要があるかもしれません。",
"create": "作成",
@ -46,13 +46,24 @@
"cancel": "キャンセル",
"deleteWorktree": "Worktree を削除",
"deleteWorktreeDescription": "これにより worktree とそのファイルがすべて削除されます。",
"deleteWarning": "この操作は元に戻せません。次のものが削除されます:",
"deleteWarningBranch": "ブランチ '{{branch}}' と未コミットの変更すべて",
"deleteWarningBranch": "ブランチと未コミットの変更すべて",
"deleteWarningFiles": "worktree ディレクトリ内のすべてのファイル",
"deleteNoticeLarge": "大規模なリポジトリの場合、少し時間がかかる場合があります。",
"forceDelete": "強制削除",
"worktreeIsLocked": "worktree がロックされています",
"deleting": "削除中...",
"close": "閉じる"
"close": "閉じる",
"showInHomeScreen": "ホーム画面に worktrees を表示",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Worktree を切り替え",
"description": "Git worktrees を使うと、複数のブランチで同時に作業できます。クリックしてワークスペースを切り替えます。",
"settings": "Worktrees を管理",
"info": "Git worktrees を使うと、同じリポジトリに複数の作業ディレクトリを附属できます。"
}
}

View file

@ -29,6 +29,7 @@
"providers": "공급자",
"modes": "모드",
"mcp": "MCP 서버",
"worktrees": "Worktrees",
"autoApprove": "자동 승인",
"browser": "컴퓨터 접근",
"checkpoints": "체크포인트",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "브랜치 없음",
"openInCurrentWindow": "현재 창에서 열기",
"switchInThisWindow": "Worktree로 전환",
"openInNewWindow": "새 창에서 열기",
"delete": "삭제",
"newWorktree": "새 Worktree",
"createWorktree": "Worktree 만들기",
"createWorktreeDescription": "별도의 브랜치에서 작업하기 위해 전용 디렉터리에 새 worktree를 만듭니다.",
"branchName": "브랜치 이름",
"createNewBranch": "새 브랜치 만들기",
"checkoutExisting": "기존 브랜치 체크아웃",
@ -35,8 +34,9 @@
"noBranchFound": "브랜치를 찾을 수 없습니다",
"localBranches": "로컬 브랜치",
"remoteBranches": "원격 브랜치",
"worktreePath": "Worktree 경로",
"pathHint": "worktree가 생성될 경로",
"worktreePath": "In",
"selectWorktreeLocation": "Worktree 위치 선택",
"selectFolderForWorktree": "새 worktree의 폴더 선택",
"noIncludeFileWarning": ".worktreeinclude 파일 없음",
"noIncludeFileHint": ".worktreeinclude 파일이 없으면 node_modules 같은 파일이 새 worktree로 복사되지 않습니다. 생성 후 npm install을 실행해야 할 수도 있습니다.",
"create": "만들기",
@ -46,13 +46,24 @@
"cancel": "취소",
"deleteWorktree": "Worktree 삭제",
"deleteWorktreeDescription": "이 작업은 worktree와 그 안의 모든 파일을 제거합니다.",
"deleteWarning": "이 작업은 되돌릴 수 없습니다. 다음이 삭제됩니다:",
"deleteWarningBranch": "브랜치 '{{branch}}' 및 커밋되지 않은 모든 변경 사항",
"deleteWarningBranch": "브랜치 및 커밋되지 않은 모든 변경 사항",
"deleteWarningFiles": "worktree 디렉터리의 모든 파일",
"deleteNoticeLarge": "대규모 저장소의 경우 시간이 조금 걸릴 수 있습니다.",
"forceDelete": "강제 삭제",
"worktreeIsLocked": "worktree가 잠겨 있습니다",
"deleting": "삭제 중...",
"close": "닫기"
"close": "닫기",
"showInHomeScreen": "홈 화면에 worktrees 표시",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Worktree 간 전환",
"description": "Git worktree를 사용하면 동시에 여러 브랜치에서 작업할 수 있습니다. 클릭하여 워크스페이스를 전환합니다.",
"settings": "Worktree 관리",
"info": "Git worktree를 사용하면 동일한 저장소에 여러 작업 디렉터리를 연결할 수 있습니다."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Providers",
"modes": "Modi",
"mcp": "MCP-servers",
"worktrees": "Worktrees",
"autoApprove": "Auto-goedkeuren",
"browser": "Browser",
"checkpoints": "Checkpoints",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Geen branch",
"openInCurrentWindow": "Openen in huidig venster",
"switchInThisWindow": "Naar worktree wisselen",
"openInNewWindow": "Openen in nieuw venster",
"delete": "Verwijderen",
"newWorktree": "Nieuwe Worktree",
"createWorktree": "Worktree aanmaken",
"createWorktreeDescription": "Maak een nieuwe worktree aan om in een aparte branch in zijn eigen map te werken.",
"branchName": "Branchnaam",
"createNewBranch": "Nieuwe branch aanmaken",
"checkoutExisting": "Bestaande branch uitchecken",
@ -35,8 +34,9 @@
"noBranchFound": "Geen branch gevonden",
"localBranches": "Lokale branches",
"remoteBranches": "Remote-branches",
"worktreePath": "Worktree-pad",
"pathHint": "Het pad waar de worktree wordt aangemaakt",
"worktreePath": "In",
"selectWorktreeLocation": "Locatie Worktree selecteren",
"selectFolderForWorktree": "Map voor nieuwe worktree selecteren",
"noIncludeFileWarning": "Geen .worktreeinclude-bestand",
"noIncludeFileHint": "Zonder een .worktreeinclude-bestand worden bestanden zoals node_modules niet naar de nieuwe worktree gekopieerd. Mogelijk moet je na het aanmaken npm install uitvoeren.",
"create": "Aanmaken",
@ -46,13 +46,24 @@
"cancel": "Annuleren",
"deleteWorktree": "Worktree verwijderen",
"deleteWorktreeDescription": "Hiermee wordt de worktree en alle bestanden verwijderd.",
"deleteWarning": "Deze actie kan niet ongedaan worden gemaakt. Het volgende wordt verwijderd:",
"deleteWarningBranch": "De branch '{{branch}}' en alle niet-gecommitte wijzigingen",
"deleteWarningBranch": "De branch en alle niet-gecommitte wijzigingen",
"deleteWarningFiles": "Alle bestanden in de worktree-map",
"deleteNoticeLarge": "Als dit een grote repository is, kan dit even duren.",
"forceDelete": "Geforceerd verwijderen",
"worktreeIsLocked": "worktree is vergrendeld",
"deleting": "Bezig met verwijderen...",
"close": "Sluiten"
"close": "Sluiten",
"showInHomeScreen": "Worktrees weergeven op startscherm",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Tussen worktrees wisselen",
"description": "Met Git worktrees kun je tegelijkertijd aan meerdere branches werken. Klik om van workspace te wisselen.",
"settings": "Worktrees beheren",
"info": "Met Git worktrees kun je meerdere werkdirectories aan dezelfde repository koppelen."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Dostawcy",
"modes": "Tryby",
"mcp": "Serwery MCP",
"worktrees": "Worktrees",
"autoApprove": "Auto-zatwierdzanie",
"browser": "Dostęp komputera",
"checkpoints": "Punkty kontrolne",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Brak brancha",
"openInCurrentWindow": "Otwórz w bieżącym oknie",
"switchInThisWindow": "Przełącz na worktree",
"openInNewWindow": "Otwórz w nowym oknie",
"delete": "Usuń",
"newWorktree": "Nowy Worktree",
"createWorktree": "Utwórz Worktree",
"createWorktreeDescription": "Utwórz nowy worktree, aby pracować na oddzielnym branchu w osobnym katalogu.",
"branchName": "Nazwa brancha",
"createNewBranch": "Utwórz nowy branch",
"checkoutExisting": "Checkout istniejącego brancha",
@ -35,8 +34,9 @@
"noBranchFound": "Nie znaleziono brancha",
"localBranches": "Branche lokalne",
"remoteBranches": "Branche zdalne",
"worktreePath": "Ścieżka worktree",
"pathHint": "Ścieżka, w której zostanie utworzony worktree",
"worktreePath": "W",
"selectWorktreeLocation": "Wybierz lokalizację Worktree",
"selectFolderForWorktree": "Wybierz folder dla nowego worktree",
"noIncludeFileWarning": "Brak pliku .worktreeinclude",
"noIncludeFileHint": "Bez pliku .worktreeinclude pliki takie jak node_modules nie zostaną skopiowane do nowego worktree. Po utworzeniu może być konieczne uruchomienie npm install.",
"create": "Utwórz",
@ -46,13 +46,24 @@
"cancel": "Anuluj",
"deleteWorktree": "Usuń Worktree",
"deleteWorktreeDescription": "To usunie worktree oraz wszystkie jego pliki.",
"deleteWarning": "Tej akcji nie można cofnąć. Zostanie usunięte:",
"deleteWarningBranch": "Branch '{{branch}}' i wszystkie niezatwierdzone zmiany",
"deleteWarningBranch": "Branch i wszystkie niezatwierdzone zmiany",
"deleteWarningFiles": "Wszystkie pliki w katalogu worktree",
"deleteNoticeLarge": "Jeśli jest to duże repozytorium, może to chwilę potrwać.",
"forceDelete": "Wymuś usunięcie",
"worktreeIsLocked": "worktree jest zablokowany",
"deleting": "Usuwanie...",
"close": "Zamknij"
"close": "Zamknij",
"showInHomeScreen": "Pokaż worktrees na ekranie głównym",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Przełączaj się między worktrees",
"description": "Git worktrees pozwalają pracować jednocześnie na wielu branchach. Kliknij, aby zmienić workspace.",
"settings": "Zarządzaj Worktrees",
"info": "Git worktrees pozwalają mieć wiele katalogów roboczych dołączonych do tego samego repozytorium."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Provedores",
"modes": "Modos",
"mcp": "Servidores MCP",
"worktrees": "Worktrees",
"autoApprove": "Aprovação",
"browser": "Navegador",
"checkpoints": "Checkpoints",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Sem branch",
"openInCurrentWindow": "Abrir na janela atual",
"switchInThisWindow": "Mudar para worktree",
"openInNewWindow": "Abrir em uma nova janela",
"delete": "Excluir",
"newWorktree": "Novo Worktree",
"createWorktree": "Criar Worktree",
"createWorktreeDescription": "Crie um novo worktree para trabalhar em um branch separado no seu próprio diretório.",
"branchName": "Nome do branch",
"createNewBranch": "Criar novo branch",
"checkoutExisting": "Fazer checkout de um branch existente",
@ -35,8 +34,9 @@
"noBranchFound": "Nenhum branch encontrado",
"localBranches": "Branches locais",
"remoteBranches": "Branches remotos",
"worktreePath": "Caminho do worktree",
"pathHint": "O caminho onde o worktree será criado",
"worktreePath": "Em",
"selectWorktreeLocation": "Selecionar local do Worktree",
"selectFolderForWorktree": "Selecionar pasta para o novo worktree",
"noIncludeFileWarning": "Sem arquivo .worktreeinclude",
"noIncludeFileHint": "Sem um arquivo .worktreeinclude, arquivos como node_modules não serão copiados para o novo worktree. Você pode precisar executar npm install depois de criá-lo.",
"create": "Criar",
@ -46,13 +46,24 @@
"cancel": "Cancelar",
"deleteWorktree": "Excluir Worktree",
"deleteWorktreeDescription": "Isso removerá o worktree e todos os seus arquivos.",
"deleteWarning": "Esta ação não pode ser desfeita. O seguinte será excluído:",
"deleteWarningBranch": "O branch '{{branch}}' e todas as alterações não commitadas",
"deleteWarningBranch": "O branch e todas as alterações não commitadas",
"deleteWarningFiles": "Todos os arquivos no diretório do worktree",
"deleteNoticeLarge": "Se for um repositório grande, pode levar um pouco.",
"forceDelete": "Forçar exclusão",
"worktreeIsLocked": "worktree está bloqueado",
"deleting": "Excluindo...",
"close": "Fechar"
"close": "Fechar",
"showInHomeScreen": "Mostrar worktrees na tela inicial",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Alterne entre worktrees",
"description": "Os worktrees do Git permitem trabalhar em vários branches simultaneamente. Clique para alternar o workspace.",
"settings": "Gerenciar Worktrees",
"info": "Os worktrees do Git permitem ter vários diretórios de trabalho conectados ao mesmo repositório."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Провайдеры",
"modes": "Режимы",
"mcp": "Серверы MCP",
"worktrees": "Worktrees",
"autoApprove": "Автоодобрение",
"browser": "Доступ к компьютеру",
"checkpoints": "Контрольные точки",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Нет ветки",
"openInCurrentWindow": "Открыть в текущем окне",
"switchInThisWindow": "Перейти на worktree",
"openInNewWindow": "Открыть в новом окне",
"delete": "Удалить",
"newWorktree": "Новый Worktree",
"createWorktree": "Создать Worktree",
"createWorktreeDescription": "Создай новый worktree, чтобы работать с отдельной веткой в своём каталоге.",
"branchName": "Имя ветки",
"createNewBranch": "Создать новую ветку",
"checkoutExisting": "Переключиться на существующую ветку",
@ -35,8 +34,9 @@
"noBranchFound": "Ветка не найдена",
"localBranches": "Локальные ветки",
"remoteBranches": "Удалённые ветки",
"worktreePath": "Путь worktree",
"pathHint": "Путь, где будет создан worktree",
"worktreePath": "В",
"selectWorktreeLocation": "Выбрать расположение Worktree",
"selectFolderForWorktree": "Выбрать папку для нового worktree",
"noIncludeFileWarning": "Нет файла .worktreeinclude",
"noIncludeFileHint": "Без файла .worktreeinclude файлы вроде node_modules не будут скопированы в новый worktree. Возможно, после создания нужно будет выполнить npm install.",
"create": "Создать",
@ -46,13 +46,24 @@
"cancel": "Отмена",
"deleteWorktree": "Удалить Worktree",
"deleteWorktreeDescription": "Это удалит worktree и все его файлы.",
"deleteWarning": "Это действие нельзя отменить. Будет удалено:",
"deleteWarningBranch": "Ветка '{{branch}}' и все незакоммиченные изменения",
"deleteWarningBranch": "Ветка и все незакоммиченные изменения",
"deleteWarningFiles": "Все файлы в каталоге worktree",
"deleteNoticeLarge": "Если это большой репозиторий, это может занять некоторое время.",
"forceDelete": "Удалить принудительно",
"worktreeIsLocked": "worktree заблокирован",
"deleting": "Удаление...",
"close": "Закрыть"
"close": "Закрыть",
"showInHomeScreen": "Показывать worktrees на главном экране",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Переключайтесь между worktrees",
"description": "Git worktrees позволяют работать с несколькими ветками одновременно. Нажми, чтобы переключить workspace.",
"settings": "Управление Worktrees",
"info": "Git worktrees позволяют иметь несколько рабочих каталогов, подключенных к одному репозиторию."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Sağlayıcılar",
"modes": "Modlar",
"mcp": "MCP Sunucuları",
"worktrees": "Worktrees",
"autoApprove": "Oto-Onay",
"browser": "Bilgisayar Erişimi",
"checkpoints": "Kontrol Noktaları",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Branch yok",
"openInCurrentWindow": "Mevcut pencerede aç",
"switchInThisWindow": "Worktree'ye geç",
"openInNewWindow": "Yeni pencerede aç",
"delete": "Sil",
"newWorktree": "Yeni Worktree",
"createWorktree": "Worktree oluştur",
"createWorktreeDescription": "Kendi dizininde ayrı bir branch üzerinde çalışmak için yeni bir worktree oluştur.",
"branchName": "Branch adı",
"createNewBranch": "Yeni branch oluştur",
"checkoutExisting": "Mevcut branch'i checkout et",
@ -35,8 +34,9 @@
"noBranchFound": "Branch bulunamadı",
"localBranches": "Yerel branch'ler",
"remoteBranches": "Uzak branch'ler",
"worktreePath": "Worktree yolu",
"pathHint": "Worktree'nin oluşturulacağı yol",
"worktreePath": "In",
"selectWorktreeLocation": "Worktree Konumunu Seç",
"selectFolderForWorktree": "Yeni worktree için klasör seç",
"noIncludeFileWarning": ".worktreeinclude dosyası yok",
"noIncludeFileHint": ".worktreeinclude dosyası olmadan node_modules gibi dosyalar yeni worktree'ye kopyalanmaz. Oluşturduktan sonra npm install çalıştırman gerekebilir.",
"create": "Oluştur",
@ -46,13 +46,24 @@
"cancel": "İptal",
"deleteWorktree": "Worktree'yi sil",
"deleteWorktreeDescription": "Bu işlem worktree'yi ve tüm dosyalarını kaldırır.",
"deleteWarning": "Bu işlem geri alınamaz. Şunlar silinecek:",
"deleteWarningBranch": "'{{branch}}' branch'i ve commit edilmemiş tüm değişiklikler",
"deleteWarningBranch": "Branch'i ve commit edilmemiş tüm değişiklikler",
"deleteWarningFiles": "worktree dizinindeki tüm dosyalar",
"deleteNoticeLarge": "Bu büyük bir depo ise, biraz zaman alabilir.",
"forceDelete": "Silmeye zorla",
"worktreeIsLocked": "worktree kilitli",
"deleting": "Siliniyor...",
"close": "Kapat"
"close": "Kapat",
"showInHomeScreen": "Ana ekranda worktrees göster",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Worktree'ler arasında geç",
"description": "Git worktree'leri aynı anda birden fazla branch'te çalışmanı sağlar. Workspace'i değiştirmek için tıkla.",
"settings": "Worktree'leri Yönet",
"info": "Git worktree'leri aynı depo ile bağlantılı birden fazla çalışma dizinine sahip olmanı sağlar."
}
}

View file

@ -29,6 +29,7 @@
"providers": "Nhà cung cấp",
"modes": "Chế độ",
"mcp": "Máy chủ MCP",
"worktrees": "Worktrees",
"autoApprove": "Phê duyệt",
"browser": "Trình duyệt",
"checkpoints": "Điểm kiểm tra",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "Không có nhánh",
"openInCurrentWindow": "Mở trong cửa sổ hiện tại",
"switchInThisWindow": "Chuyển sang worktree",
"openInNewWindow": "Mở trong cửa sổ mới",
"delete": "Xóa",
"newWorktree": "Worktree Mới",
"createWorktree": "Tạo Worktree",
"createWorktreeDescription": "Tạo worktree mới để làm việc trên một nhánh riêng trong thư mục riêng của nó.",
"branchName": "Tên nhánh",
"createNewBranch": "Tạo nhánh mới",
"checkoutExisting": "Checkout nhánh hiện có",
@ -35,8 +34,9 @@
"noBranchFound": "Không tìm thấy nhánh",
"localBranches": "Nhánh cục bộ",
"remoteBranches": "Nhánh từ xa",
"worktreePath": "Đường dẫn worktree",
"pathHint": "Đường dẫn nơi worktree sẽ được tạo",
"worktreePath": "Trong",
"selectWorktreeLocation": "Chọn vị trí Worktree",
"selectFolderForWorktree": "Chọn thư mục cho worktree mới",
"noIncludeFileWarning": "Không có tệp .worktreeinclude",
"noIncludeFileHint": "Không có tệp .worktreeinclude thì các tệp như node_modules sẽ không được sao chép sang worktree mới. Bạn có thể cần chạy npm install sau khi tạo.",
"create": "Tạo",
@ -46,13 +46,24 @@
"cancel": "Hủy",
"deleteWorktree": "Xóa Worktree",
"deleteWorktreeDescription": "Thao tác này sẽ xóa worktree và tất cả các tệp của nó.",
"deleteWarning": "Không thể hoàn tác thao tác này. Những thứ sau sẽ bị xóa:",
"deleteWarningBranch": "Nhánh '{{branch}}' và tất cả thay đổi chưa commit",
"deleteWarningBranch": "Nhánh và tất cả thay đổi chưa commit",
"deleteWarningFiles": "Tất cả tệp trong thư mục worktree",
"deleteNoticeLarge": "Nếu đây là kho lớn, có thể mất một chút thời gian.",
"forceDelete": "Buộc xóa",
"worktreeIsLocked": "worktree đang bị khóa",
"deleting": "Đang xóa...",
"close": "Đóng"
"close": "Đóng",
"showInHomeScreen": "Hiển thị worktrees trên màn hình chính",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "Chuyển đổi giữa các worktrees",
"description": "Git worktrees cho phép bạn làm việc trên nhiều nhánh cùng lúc. Nhấp để chuyển workspace.",
"settings": "Quản lý Worktrees",
"info": "Git worktrees cho phép bạn có nhiều thư mục làm việc được kết nối với cùng một kho."
}
}

View file

@ -29,6 +29,7 @@
"providers": "提供商",
"modes": "模式",
"mcp": "MCP 服务",
"worktrees": "Worktrees",
"autoApprove": "自动批准",
"browser": "计算机交互",
"checkpoints": "存档点",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "无分支",
"openInCurrentWindow": "在当前窗口打开",
"switchInThisWindow": "切换到 worktree",
"openInNewWindow": "在新窗口打开",
"delete": "删除",
"newWorktree": "新建 Worktree",
"createWorktree": "创建 Worktree",
"createWorktreeDescription": "创建一个新的 worktree,在独立目录中处理单独的分支。",
"branchName": "分支名称",
"createNewBranch": "创建新分支",
"checkoutExisting": "Checkout 现有分支",
@ -35,8 +34,9 @@
"noBranchFound": "未找到分支",
"localBranches": "本地分支",
"remoteBranches": "远程分支",
"worktreePath": "Worktree 路径",
"pathHint": "worktree 将创建到的路径",
"worktreePath": "在",
"selectWorktreeLocation": "选择 Worktree 位置",
"selectFolderForWorktree": "为新 worktree 选择文件夹",
"noIncludeFileWarning": "没有 .worktreeinclude 文件",
"noIncludeFileHint": "没有 .worktreeinclude 文件时,node_modules 等文件不会复制到新 worktree。创建后你可能需要运行 npm install。",
"create": "创建",
@ -46,13 +46,24 @@
"cancel": "取消",
"deleteWorktree": "删除 Worktree",
"deleteWorktreeDescription": "这将删除 worktree 及其所有文件。",
"deleteWarning": "此操作不可逆。将删除以下内容:",
"deleteWarningBranch": "分支 '{{branch}}' 以及所有未提交的更改",
"deleteWarningBranch": "分支以及所有未提交的更改",
"deleteWarningFiles": "worktree 目录中的所有文件",
"deleteNoticeLarge": "如果这是一个大型仓库,可能需要一些时间。",
"forceDelete": "强制删除",
"worktreeIsLocked": "worktree 已锁定",
"deleting": "正在删除...",
"close": "关闭"
"close": "关闭",
"showInHomeScreen": "在主屏幕显示 worktrees",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "在 worktrees 之间切换",
"description": "Git worktrees 让你能在多个分支上同时工作。点击以切换 workspace。",
"settings": "管理 Worktrees",
"info": "Git worktrees 允许你将多个工作目录连接到同一个仓库。"
}
}

View file

@ -29,6 +29,7 @@
"providers": "供應商",
"modes": "模式",
"mcp": "MCP 伺服器",
"worktrees": "Worktrees",
"autoApprove": "自動核准",
"browser": "電腦存取",
"checkpoints": "檢查點",

View file

@ -18,13 +18,12 @@
"detachedHead": "Detached HEAD",
"noBranch": "無分支",
"openInCurrentWindow": "在目前視窗開啟",
"switchInThisWindow": "切換到 worktree",
"openInNewWindow": "在新視窗開啟",
"delete": "刪除",
"newWorktree": "新增 Worktree",
"createWorktree": "建立 Worktree",
"createWorktreeDescription": "建立新的 worktree,讓你在獨立目錄中處理單獨的分支。",
"branchName": "分支名稱",
"createNewBranch": "建立新分支",
"checkoutExisting": "Checkout 現有分支",
@ -35,8 +34,9 @@
"noBranchFound": "找不到分支",
"localBranches": "本機分支",
"remoteBranches": "遠端分支",
"worktreePath": "Worktree 路徑",
"pathHint": "worktree 將建立到的路徑",
"worktreePath": "位於",
"selectWorktreeLocation": "選取 Worktree 位置",
"selectFolderForWorktree": "為新 worktree 選取資料夾",
"noIncludeFileWarning": "沒有 .worktreeinclude 檔案",
"noIncludeFileHint": "沒有 .worktreeinclude 檔案時,node_modules 等檔案不會複製到新的 worktree。建立後你可能需要執行 npm install。",
"create": "建立",
@ -46,13 +46,24 @@
"cancel": "取消",
"deleteWorktree": "刪除 Worktree",
"deleteWorktreeDescription": "這會刪除 worktree 及其所有檔案。",
"deleteWarning": "此操作無法復原。將刪除以下內容:",
"deleteWarningBranch": "分支 '{{branch}}' 以及所有未提交的變更",
"deleteWarningBranch": "分支以及所有未提交的變更",
"deleteWarningFiles": "worktree 目錄中的所有檔案",
"deleteNoticeLarge": "如果這是大型儲存庫,可能需要一些時間。",
"forceDelete": "強制刪除",
"worktreeIsLocked": "worktree 已鎖定",
"deleting": "正在刪除...",
"close": "關閉"
"close": "關閉",
"showInHomeScreen": "在主畫面顯示 worktrees",
"selector": {
"worktree": "Worktree",
"title": "Worktrees",
"tooltip": "在 worktrees 之間切換",
"description": "Git worktrees 讓你能在多個分支上同時工作。點選以切換工作區。",
"settings": "管理 Worktrees",
"info": "Git worktrees 允許你將多個工作目錄連線到同一個儲存庫。"
}
}

View file

@ -163,7 +163,7 @@
--accent: var(--vscode-list-hoverBackground);
--accent-foreground: var(--vscode-list-hoverForeground);
--destructive: var(--vscode-errorForeground);
--destructive-foreground: var(--vscode-button-foreground);
--destructive-foreground: var(--vscode-editor-background);
--border: var(--vscode-input-border, transparent); /* --border gets theme value or transparent fallback */
--input: var(--vscode-input-background);
--ring: var(--vscode-input-border);